Files
servicedesk/README.md
Kacper 63178b366e v1.1.4
- Configurable ticket numbering (Admin > Konfiguracja > Ogólne): admin-set
  prefix and minimum zero-padded length for the ticket number.
- "Ukryj kolejność zgłoszeń": an opt-in mode that displays a stable,
  HMAC-derived checksum instead of the sequential ticket number, so it gives
  no indication of ticket volume or creation order. Ticket URLs switch to
  the same checksum when this is on, so a link and the number on the page it
  points to always match. The REST API is unaffected — pinned to `id`
  regardless of this setting. Search now also matches by checksum.
- Fixed: attachments no longer show an inline image thumbnail in the
  message thread — every attachment (images included) shows as just its
  filename, opening in a new tab on click.
- Docs: README/ARCHITECTURE/wiki updated for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 09:57:39 +02:00

12 KiB
Raw Permalink Blame History

Servicedesk

Helpdesk / ticketing system built with Laravel + Livewire (Polish UI). Clients submit support tickets, operators triage and resolve them inside team queues, and admins configure everything else — categories, SLA rules, templates, branding, LDAP/SMTP, and a REST API for external integrations.

Roles & areas

The app has three areas, gated by role (a user can hold more than one at once):

Area Route prefix Who What they do
Client /client client Submit tickets, track status, reply, see resolution
Operator /operator operator Work the ticket queue, reply/resolve, see team statistics
Admin /admin admin Configure categories, users, teams, SLA, templates, triggers, branding, LDAP/SMTP/BookStack

Every account gets the client role by default (see AssignDefaultRole for LDAP-provisioned accounts), and always lands on /client first after login regardless of what other roles it also holds — staff switch into Operator/Admin via the role switcher in the header. See wiki/client, wiki/operator, and wiki/admin for role-specific how-to guides.

Feature overview

  • Tickets — number, subject, body, category/subcategory, status, priority, team, assignee, custom fields (per subcategory), attachments, full message thread (public replies + internal notes), history log, merge, delete.
  • SLA — per-priority response/resolution time targets; a scheduled command (tickets:check-sla-breaches, every 15 min) flags overdue tickets and can notify the assigned operator.
  • SLA automation rules (Admin > Automatyzacja SLA) — configurable rules that change a ticket's priority/status/team/assignee after N minutes of customer silence (optionally scoped to a priority/category/team), evaluated every 15 minutes (automation:run-rules). Reuses the same TicketService setters a manual operator action would, so an automated change gets the same history entry, notification, and live broadcast as a human doing it; each rule fires once per ticket until a fresh customer reply or a close/reopen resets it.
  • Real-time updates — the operator ticket queue and both ticket-detail views (operator and client) update live over WebSockets (Laravel Reverb): new tickets, status/priority/team/assignee changes, and new replies ("live chat") all show up without a manual refresh. A periodic fallback refresh (with a visible countdown) covers a dropped websocket connection.
  • Categories & custom fields — admin-defined categories/subcategories, each with its own set of custom fields (text/textarea/select/checkbox/date/number) and an optional default priority.
  • Teams — subcategories auto-route to a team; operators only see their own team's queue (plus unrouted tickets and anything assigned to them) unless they're an admin.
  • Templates — canned response snippets for the reply box, admin-configurable "quick actions" (send + transition status in one click), and HTML e-mail templates for every ticket lifecycle event (created, status/priority/category/ team changed, closed, operator replied, SLA breached), each independently enable/disable-able.
  • Operator statistics (/operator/stats) — filterable dashboard (date range, team, priority, category, assignee) organized into sections: KPI tiles (volume, SLA breach rate, average first-response/resolution time, CSAT); breakdowns by status, priority, category and subcategory; team/operator workload; top 10 clients by ticket volume plus a client × subcategory cross-tab (top 5 subcategories, rest folded into "Inne"); CSAT average by team and by operator; and a daily created-vs-closed trend.
  • Branding & config — company name/logo/favicon/accent color, login notice, e-mail layout/footer, SMTP connection (Admin > E-MAIL), attachment limits, session lifetime, timezone (Admin > Konfiguracja), and LDAP connection + user sync + BookStack (Admin > Integracje).
  • LDAP auth — logins bind against an LDAP/LLDAP directory (config/auth.php, config/ldap.php); local accounts (e.g. the emergency admin account) fall back to e-mail + local password when the LDAP bind doesn't match.
  • Triggers (Admin > Wyzwalacze) — event-driven business rules that fire immediately on a ticket lifecycle event (created, any field updated, status/ priority/assignee/team/category changed, new public reply): AND-combined conditions gate a sequence of actions (set status/priority/team/assignee, or send an e-mail using a dedicated set of freely add/edit/delete-able trigger e-mail templates, kept separate from the fixed per-event system templates). Complements the time-based SLA automation rules above rather than replacing them.
  • Ticket watching — operators can star/"Obserwuj" any ticket to follow it regardless of assignment/team, which feeds the "Obserwowane zgłoszenia" scope in their notification preferences.
  • Per-user notification preferences (/settings/notifications) — each operator/admin chooses, per event category (new ticket, ticket update, escalation), which scope of tickets (mine, unassigned, watched, all) notifies them via the in-app bell, and whether that also sends an e-mail; plus an opt-in toggle for native in-tab browser push notifications.
  • REST API (/api/v1/..., Sanctum token auth, ability-scoped: tickets:read, tickets:write, dictionaries:read, users:read) for tickets/messages/users/ categories/statuses/priorities/teams — issued via admin-managed API clients. Interactive docs (L5-Swagger) at /admin/api-docs.
  • PWA — installable manifest + icons for the client-facing area.
  • In-app notifications — a bell in the top bar (client/operator/admin areas) backed by Laravel's database notification channel, alongside the existing e-mail notifications (same per-trigger enable toggle drives both); shows unread notifications only — reading one removes it from the list. Updates live over WebSockets the moment a notification is created (with a 30s fallback poll), and can optionally raise a native browser push notification while the tab is open (see per-user notification preferences above). Includes a dedicated trigger notifying every operator on a team whose subcategories match a newly created ticket.
  • Attachments — drag-and-drop upload (in addition to the file picker); every attachment shows in the message thread as just its filename, opening in a new tab on click (no inline image preview).
  • Configurable ticket numbering (Admin > Konfiguracja) — a custom prefix and minimum zero-padded length for the ticket number, plus an optional "hide ticket order" mode that displays a stable per-ticket checksum instead of the sequential number. When enabled, ticket URLs switch to the same checksum too, so the number in the link always matches the one on the page; the REST API is unaffected and always addresses tickets by id.
  • Customer satisfaction (CSAT) — clients rate a ticket 15 stars (+ optional comment) once it's closed; average/response-rate surfaced as a KPI on the operator stats dashboard, with a link in the "ticket closed" e-mail.
  • Saved queue views — operators can save their current filter/sort/column combination in the ticket queue, mark one as default, and switch between them.
  • Full-text search — MySQL/MariaDB FULLTEXT search (with a portable LIKE fallback) across ticket subject/body and reply message bodies, available in both the operator queue and the client's own ticket list.
  • Stats export — the operator stats dashboard can export the currently filtered ticket set as CSV.
  • BookStack knowledge-base integration (optional, off by default) — suggests relevant BookStack articles by category/subcategory while a ticket is being created, and in a separate sidebar panel on an existing ticket for both operators and clients (with a copy-link button for operators). Loads in after the page's first paint rather than blocking it. Configured entirely from Admin > Integracje: connection + API token, optional SSL-verification bypass for self-signed instances, page/book search-type filter, and two independent per-shelf allow-lists (nothing is searched until an admin opts specific shelves in, separately for ticket-creation suggestions vs. the operator/client ticket-view sidebar).

Tech stack

  • Backend: Laravel, Livewire (server-driven UI, no SPA build beyond Tailwind/Vite for CSS), LdapRecord for directory auth, Sanctum for API tokens, L5-Swagger for API docs, Laravel Reverb for WebSocket broadcasting (real-time queue/chat updates — see ARCHITECTURE.md).
  • Frontend: Blade + Livewire + a little Alpine.js for local UI state; Tailwind v4 via Vite for resources/css/app.css; Laravel Echo + Pusher-protocol client (resources/js/echo.js) for Reverb. No JS charting library — the statistics dashboard is hand-rolled inline-styled bar/column charts, so it needs no client build step beyond the CSS bundle.
  • Database: MariaDB.
  • Deployment: compose.yamlservicedesk (source bind-mounted from ./src, no image rebuild needed for PHP/Blade/route changes) + mariadb + reverb (same image, php artisan reverb:start), fronted by Traefik with a private-CA TLS cert (the websocket path is routed to reverb by a higher-priority Traefik rule; everything else goes to servicedesk). The servicedesk image itself is built and pushed by Gitea Actions (.gitea/workflows/build.yml) to the Gitea container registry whenever Dockerfile changes — compose.yaml just pulls a tag, it never builds locally.

See install.md for full step-by-step deployment instructions — both via Docker Compose (this stack) and directly on a server with Apache/Nginx, including which .env values to set (there are two separate .env files — Compose-level and Laravel-level) and the LDAP/SMTP gotcha after a fresh seed.

Local/dev notes

  • The app container mounts ./src directly — editing PHP/Blade/routes takes effect immediately, no rebuild or restart needed.
  • CSS/JS changes under resources/ do need a Vite build. Neither the host nor the app container has Node installed; rebuild with a throwaway container instead of rebuilding the app image:
    sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run build
    
  • Fresh install / reset:
    sudo docker exec servicedesk-servicedesk-1 php artisan migrate:fresh --seed
    
    Seeds real reference data (categories, custom fields, statuses/priorities/SLA, teams, quick actions, response/e-mail templates, branding/config with example SMTP+LDAP placeholders) and a single local fallback account, admin@example.com / admin — replace its password and/or the SMTP/LDAP settings before relying on this in anything but a lab environment.

Project layout

src/                     Laravel application
  app/Livewire/          Client/Operator/Admin Livewire components
  app/Models/            Eloquent models
  app/Events/            Broadcast events (TicketQueueChanged, TicketMessagePosted)
  app/Console/Commands/  Scheduled commands (SLA breach check, automation rules)
  app/Services/          TicketService (ticket lifecycle + notifications), BookStackClient
  app/Ldap/              LDAP user model + sync handlers
  database/migrations/   Schema (one file per table group, final shape)
  database/seeders/      DatabaseSeeder — reference data, no ticket data
  resources/css/         Tailwind entrypoint (needs `npm run build` after edits)
  resources/js/echo.js   Laravel Echo/Reverb client + broadcast → Livewire event bridge
  resources/views/       Blade templates
  routes/web.php         Client/Operator/Admin routes (role-gated)
  routes/api.php         REST API (Sanctum, ability-gated)
  routes/channels.php    Broadcasting channel authorization (operator.queue, ticket.{id})
wiki/
  client/                How-to guide for the Client role
  operator/               How-to guide for the Operator role
  admin/                  How-to guide for the Admin role