Files
servicedesk/README.md
Kacper 0d116dfd98 v1.2.0
- E-mail intake (IMAP), optional and off by default: clients can create a
  ticket or reply to an existing one just by sending/replying to an e-mail.
  Configure any number of mailboxes in the new Admin > Poczta page (SMTP +
  IMAP together, replacing the old "E-MAIL" tab), each routed to a specific
  subcategory or a whole category (new tickets.category_id column). Replies
  are matched to their ticket via the number/checksum already in every
  notification subject; autoresponders/bounces are detected and rejected;
  "restrict tickets to LDAP" is enforced for e-mail like the guest web form.
  Manual "Pobierz teraz" per-mailbox fetch button; dedicated
  storage/logs/imap-*.log regardless of the app's log level; mail-icon badges
  on e-mail-originated tickets/messages in the operator queue and ticket view.
- Operator queue: "select all" checkbox in the table header for every
  currently visible ticket under the active filter/tab.
- Fixed: scheduled commands (SLA breach check, automation rules, and now IMAP
  fetch) always sent notifications through .env's default mailer instead of
  the configured SMTP server, because AppServiceProvider's Settings override
  used to skip itself for any console command, not just migrate.
- Fixed: visiting a ticket that no longer exists (deleted mid-session, or a
  stale background refresh) showed a raw 404 instead of redirecting back to
  the operator queue / client dashboard.
- Docs: README/ARCHITECTURE/CLAUDE/install/wiki updated for all of the above,
  including the previously-missing host crontab entry for schedule:run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 12:44:30 +02:00

212 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/client/README.md)**, **[wiki/operator](wiki/operator/README.md)**,
and **[wiki/admin](wiki/admin/README.md)** 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).
- **E-mail intake (IMAP)** *(optional, off by default)* — clients can create
tickets or reply to an existing one just by sending/replying to an e-mail;
configure any number of mailboxes in Admin > Poczta (e.g. one address per
team), each routed to a specific subcategory or a whole category. A reply
is matched back to its ticket via the number/checksum already present in
every notification's subject; automatic replies (autoresponders, bounces)
are detected and rejected instead of creating junk tickets, and the
"restrict tickets to LDAP" setting is enforced for e-mail exactly like the
guest web form. A manual "Pobierz teraz" button fetches immediately
outside the 5-minute schedule; all activity is logged separately to
`storage/logs/imap-*.log`. Tickets/messages that came in by e-mail show a
small mail-icon badge in the operator queue and ticket view.
- **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](ARCHITECTURE.md)), webklex/php-imap for the
optional e-mail intake fetcher (pure-PHP IMAP client, no `ext-imap` needed).
- **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.yaml``servicedesk` (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](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:
```bash
sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run build
```
- Fresh install / reset:
```bash
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, IMAP fetch)
app/Services/ TicketService (ticket lifecycle + notifications), BookStackClient,
ImapMailboxFetcher (I/O) + ImapMessageClassifier (pure logic)
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
```