Co nowego: - Podgląd logów w panelu admina (Admin > Logi) — pliki storage/logs/*.log bez potrzeby dostępu do kontenera, z filtrami poziomu/tekstu/liczby wpisów i auto-odświeżaniem. - Filtr „Bez kategorii” w kolejce operatora — izoluje zgłoszenia bez przypisanej kategorii/podkategorii. - Narzędzie importu z Heska: już nie tworzy automatycznie kont klientów dla nieznanych e-maili (pomija takie zgłoszenia zamiast zakładać konto), łączy odpowiedzi/właścicieli zgłoszeń z realnymi kontami operatorów po e-mailu, nowe flagi --assign-operators i --fix-closed-dates do donaprawiania wcześniejszych importów, dedykowany log storage/logs/hesk-import.log. - Poprawka: pulpit statystyk operatora (rozkład wg kategorii/podkategorii i filtr kategorii) pomijał zgłoszenia przypisane do samej kategorii bez podkategorii (np. z poczty IMAP) — teraz liczone poprawnie. - Poprawka: błąd JS i zawieszone w tle liczniki przy nawigacji z widoku z aktywnym licznikiem (najbardziej odczuwalne w liczniku czasu pracy operatora). - Porządki w bazie: usunięte niewykorzystywane kolumny (users.remember_token, users.email_verified_at, email_templates.trigger_label); wartości pól dodatkowych, stan triage/podsumowania AI i powiązany sprzęt Snipe-IT przeniesione z tabeli tickets do osobnych tabel (ticket_field_values, ticket_ai_summaries, ticket_snipeit_assets) — bez zmiany zachowania, ale pola dodatkowe są teraz efektywnie przeszukiwalne; dodane brakujące indeksy na 4 tabelach pivot; tickets.source/ticket_messages.source walidowane względem znanego zestawu wartości. Zaktualizowana dokumentacja: README, CLAUDE.md, ARCHITECTURE.md, CHANGELOG.md, wiki/admin, wiki/operator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
- Navigation — a segmented Klient/Operator/Administrator switcher in the
top bar (only the areas a user actually holds, current one highlighted,
hover-previews the destination before you click) replaces the old dropdown
role switcher; the profile menu shows name/e-mail/role badges above
Powiadomienia/Wyloguj się; every page sets its own browser-tab title
(ticket subject, selected queue, active admin tab, ...) anchored with the
company name; and a command-palette global search (Ctrl+K/Cmd+K, or a
"Szukaj" sidebar button) finds tickets from anywhere, scoped to what the
searching user can see, with Gmail-style
od:/temat:/treść:/numer:operators. The operator sidebar also lists the last 6 tickets they actually opened ("Ostatnio przeglądane"). - 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. The operator queue (50/page) and client dashboard (20/page, current/archive tracked separately) paginate rather than rendering every matching ticket at once.
- 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 sameTicketServicesetters 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, clickable countdown badge — click it to fetch immediately and reset the countdown) covers a dropped websocket connection. All of the refresh/poll intervals in the app, browser-side and the background scheduled commands alike, are configurable from Admin > Konfiguracja (see below).
- 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. Subcategories within a category can be reordered with up/down arrows in Admin > Kategorie; the order set there is what clients/operators see everywhere a subcategory picker is shown.
- 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. Reassigning a ticket to a team, though, is unrestricted — an operator can route a ticket to any team, not just one they belong to.
- Client search (Operator > Klienci) — find any account by name or e-mail (not just role=client — a ticket's customer can be any user), see its role badges and ticket count, and jump straight into the queue pre-filtered to that customer.
- 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).
- Log viewer (Admin > Logi) — browse
storage/logs/*.log(app, IMAP, AI automation, Hesk import, ...) from the admin panel, with level/text/entry-count filters and an optional auto-refresh, so diagnosing a scheduled integration doesn't need shell access to the container. - LDAP auth — logins bind against a directory (
config/auth.php,config/ldap.php); local accounts (e.g. the emergencyadminaccount) fall back to e-mail + local password when the LDAP bind doesn't match. A "Typ katalogu" toggle (Admin > Integracje) switches between LLDAP/OpenLDAP (default) and Active Directory, which auto-selects the right schema/login attribute (sAMAccountName+objectGUIDfor AD, vs.uid+entryUUID). - 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 1–5 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
FULLTEXTsearch (with a portableLIKEfallback) 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, a content-type filter (books/pages/
chapters, independently toggleable) and a "search by" mode (name / tags /
both), 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). A pair of
"Otaguj nową treść"/"Otaguj wszystko ponownie" buttons (also available as
php artisan bookstack:tag-content) use the AI integration below to auto-tag every book/chapter/page with matching subcategory names, so the tag-based search mode has something to find. - Generic AI integration (Admin > Integracje > "Integracja AI") (optional, off by default) — an OpenAI-compatible chat-completions client (Groq, OpenAI, or a self-hosted Ollama instance) configured by base URL, optional API key, model, and an SSL-verification toggle. Not tied to a single feature — it backs the BookStack auto-tagging above and the AI ticket triage/summary below, and is meant to be reused by anything that needs an LLM call in the future.
- AI-driven ticket triage + summary (Admin > Integracje >
"Automatyzacja AI dla zgłoszeń") (optional, off by default, requires the AI
integration above) — five independent toggles run once per new ticket, in
the background (
ai:run-ticket-automation, never synchronously at submission): assign a category/subcategory when missing, pick a subcategory when only a category is set, recheck/correct an already-categorized ticket, rewrite an unclear subject, and set a priority from the ticket's content. Every applied change is logged in the ticket's history. Separately, an AI-generated summary + suggested next action for every ticket, shown to operators only in a "Podsumowanie AI" sidebar card with a manual "Wygeneruj teraz" button, an admin-editable prompt (reset-to-default button included), and a per-transcript excerpt of the ticket's own opening body alongside the reply thread (so long tickets don't lose the original request once it scrolls out of the message window). Refreshed by the same periodic sweep by default; an optional admin toggle regenerates it immediately after every new reply/note instead of waiting for the next scheduled run. - Snipe-IT asset inventory integration (optional, off by default) — connects to a Snipe-IT instance (API address + personal API token, plus an SSL-verification bypass for self-signed instances) and adds three independently toggleable capabilities from Admin > Integracje: a client can pick which of their own Snipe-IT assets a ticket concerns while creating it (scoped to admin-selected subcategories, empty selection means it never shows — same convention as BookStack's shelf allow-lists), an operator sees the requester's own assets in a ticket-view sidebar card, and an operator can search the entire Snipe-IT inventory from that same sidebar (not a separate page) to link shared equipment the requester isn't the current owner of. Every asset is shown as "numer środka - numer seryjny - producent model" plus its Snipe-IT category; a linked asset's live status/assignment is fetched fresh on the ticket page, and unlinking stays available to an operator even if both view/search toggles are later turned off.
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), webklex/php-imap for the
optional e-mail intake fetcher (pure-PHP IMAP client, no
ext-imapneeded). - 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—app(source bind-mounted from./src, no image rebuild needed for PHP/Blade/route changes) +mariadb+reverb(same image,php artisan reverb:start) +cron(same image,php artisan schedule:work— runs the scheduled commands below without needing a host crontab), fronted by Traefik with a private-CA TLS cert (the websocket path is routed toreverbby a higher-priority Traefik rule; everything else goes toapp). Theservicedeskimage itself is built and pushed by Gitea Actions (.gitea/workflows/build.yml) to the Gitea container registry wheneverDockerfilechanges —compose.yamljust 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
./srcdirectly — 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:
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,
sudo docker exec servicedesk-app-1 php artisan migrate:fresh --seedadmin@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,
AI ticket triage/summary) + bookstack:tag-content + hesk:import
(one-time historical migration, see scripts/hesk-import/)
app/Services/ TicketService (ticket lifecycle + notifications), BookStackClient,
ImapMailboxFetcher (I/O) + ImapMessageClassifier (pure logic),
AiClient (generic LLM client), BookStackContentTagger,
TicketAiTriageService, TicketAiSummaryService, SnipeItClient
app/Ldap/ LDAP user models — LldapUser (LLDAP/OpenLDAP, default) and
AdUser (Active Directory) — plus 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
scripts/
hesk-import/ One-time Hesk 3.x ticket history import — see its own README.md