josh f65c259a71 Ticket IDs, audit log, markdown comments, tabbed detail page
- Tickets get a random display ID (V + 9 digits, e.g. V325813929)
- Ticket detail page has Overview / Comments / Audit Log tabs
- Audit log records every action (create, status, assignee, severity,
  reroute, title/overview edit, comment add/delete) with who and when
- Comments redesigned: avatar (initials + color), markdown rendering
  via react-markdown + remark-gfm, Write/Preview toggle
- Dashboard shows displayId and assignee avatar
- URLs now use displayId (/tickets/V325813929)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:53:37 -04:00
2026-03-30 19:38:32 -04:00

TicketingSystem

Internal ticketing system with CTI-based routing, severity levels, and n8n/automation integration.

Features

  • CTI routing — tickets are categorised by Category → Type → Item, reroutable at any time
  • Severity 15 — SEV 1 (critical) through SEV 5 (minimal); dashboard sorts by severity
  • Status lifecycle — Open → In Progress → Resolved → Closed; resolved tickets auto-close after 14 days
  • Comments — threaded comments per ticket with author attribution
  • Roles — Admin, Agent, Service (API key auth for automation accounts)
  • Admin panel — manage users and the full CTI hierarchy via UI
  • n8n ready — service accounts authenticate via X-Api-Key header

Production Deployment

Prerequisites

  • Docker + Docker Compose
  • Nginx Proxy Manager pointed at the host port (default 3080)
  • Access to your Gitea container registry

1. Copy files to your server

scp docker-compose.yml .env.example user@your-server:~/ticketing/

2. Configure environment

cd ~/ticketing
cp .env.example .env

Edit .env:

REGISTRY=gitea.thewrightserver.net
TAG=latest
POSTGRES_PASSWORD=<strong password>
JWT_SECRET=<output of: openssl rand -hex 64>
CLIENT_URL=http://tickets.thewrightserver.net
PORT=3080

Point NPM at http://<host-ip>:3080 for the proxy host.

3. Deploy

docker compose pull
docker compose up -d

5. Seed (first deploy only)

docker compose exec server npm run db:seed

This creates:

  • admin user (password: admin123) — change this immediately
  • goddard service account — API key is printed to the console; copy it now

Development

Requirements

  • Node.js 22+
  • PostgreSQL (local or via Docker)

Start Postgres

docker run -d \
  --name ticketing-pg \
  -e POSTGRES_DB=ticketing \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  postgres:16-alpine

Server

cd server
cp .env.example .env        # set DATABASE_URL and JWT_SECRET
npm install
npm run db:migrate          # creates tables + migration files
npm run db:seed             # seeds admin + Goddard + sample CTI
npm run dev                 # http://localhost:3000

Client

cd client
npm install
npm run dev                 # http://localhost:5173 (proxies /api to :3000)

n8n Integration (Goddard)

The goddard service account authenticates via API key — no login flow needed.

Create a ticket from n8n:

POST https://tickets.thewrightserver.net/api/tickets
X-Api-Key: sk_<goddard api key>
Content-Type: application/json

{
  "title": "[Plex] Backup - 2026-03-30T02:00:00",
  "overview": "Automated nightly Plex backup completed.",
  "severity": 5,
  "categoryId": "<TheWrightServer category ID>",
  "typeId": "<Automation type ID>",
  "itemId": "<Backup item ID>",
  "assigneeId": "<Goddard user ID>"
}

CTI IDs can be fetched from:

  • GET /api/cti/categories
  • GET /api/cti/types?categoryId=<id>
  • GET /api/cti/items?typeId=<id>

To regenerate the Goddard API key: Admin → Users → refresh icon next to Goddard.


CI/CD

Push to main triggers .gitea/workflows/build.yml, which builds and pushes two images in parallel:

Image Tag
$REGISTRY/josh/ticketing-server latest, <git sha>
$REGISTRY/josh/ticketing-client latest, <git sha>

Gitea repository secrets/variables required:

Name Type Value
REGISTRY Variable gitea.thewrightserver.net
REGISTRY_TOKEN Secret Gitea personal access token with write:packages

Set these under Repository → Settings → Actions → Variables / Secrets.

To deploy a specific commit SHA instead of latest:

TAG=<sha> docker compose up -d

Environment Variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string
JWT_SECRET Yes Secret for signing JWTs — use openssl rand -hex 64
CLIENT_URL Yes Allowed CORS origin (your domain)
PORT No Server port (default: 3000)
REGISTRY Deploy Container registry hostname
POSTGRES_PASSWORD Deploy Postgres password
DOMAIN Deploy Public domain for Traefik routing
TAG Deploy Image tag to deploy (default: latest)

Ticket Severity

Level Label Meaning
1 SEV 1 Critical — immediate action required
2 SEV 2 High — significant impact
3 SEV 3 Medium — standard priority
4 SEV 4 Low — minor issue
5 SEV 5 Minimal — informational / automated

Tickets are sorted SEV 1 → SEV 5 on the dashboard. Paging by severity is planned for a future release.


Ticket Status Lifecycle

OPEN → IN_PROGRESS → RESOLVED ──(14 days)──→ CLOSED
                         ↑
                    re-opens reset
                    the 14-day timer
Description
Internal ticketing system with CTI-based routing, severity levels, role-based access, and automation integration.
Readme 338 KiB
Languages
TypeScript 98%
CSS 1.1%
Dockerfile 0.5%
HTML 0.2%
JavaScript 0.2%