- Generic AI integration (Admin > Integracje > "Integracja AI"), optional and 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. Foundation for the two AI features below and anything else that wants an LLM call in the future. - BookStack automatic content tagging (AI): "Otaguj nową treść"/"Otaguj wszystko ponownie" buttons plus `php artisan bookstack:tag-content` (--dry-run/--force/--limit=N) tag every book/chapter/page with matching helpdesk subcategory names, idempotent by default. - BookStack search refinement: "Przeszukuj" is now three independent checkboxes (Książki/Strony/Rozdziały) instead of a single dropdown, plus a new "Szukaj po" setting (nazwa/tagi/oba) — tag matching uses the bare subcategory name, matching what auto-tagging writes. - AI-driven ticket triage + summary (Admin > Integracje > "Automatyzacja AI dla zgłoszeń", via new scheduled ai:run-ticket-automation): five toggles auto-assign/correct category+subcategory, rewrite an unclear subject, and set priority from content, once per ticket in the background; every change is logged in the ticket's history. Separately, an AI summary + suggested action for every ticket, shown to operators only, with an admin-editable prompt. - Operators can now reassign a ticket to any team, not just one they belong to. - The auto-refresh countdown badges (ticket view, operator queue) are now clickable — fetch immediately and reset the countdown. - All 7 "cyclical" intervals (3 browser refresh countdowns, the notification bell poll, and the 4 background scheduled commands) are now configurable from Admin > Konfiguracja instead of fixed in code. - Fixed: an operator viewing a ticket that's deleted or moved outside their team scope mid-session is now redirected to the operator queue instead of hitting an error. - Docs: README/ARCHITECTURE/CLAUDE/install/wiki updated for all of the above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
391 lines
22 KiB
Markdown
391 lines
22 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project are documented in this file. Format loosely
|
||
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||
|
||
## [1.2.1] - 2026-07-24
|
||
|
||
### Added
|
||
|
||
- **Generic AI integration** (Admin > Integracje > "Integracja AI"), optional
|
||
and off by default — an OpenAI-compatible `/chat/completions` client (works
|
||
against Groq, OpenAI itself, or a self-hosted Ollama instance) configured by
|
||
base URL, optional API key, model name, and an SSL-verification toggle for
|
||
self-signed local endpoints. Not tied to any one feature — it's the shared
|
||
foundation for the two AI-driven features below, and for anything else that
|
||
wants an LLM call in the future.
|
||
- **BookStack automatic content tagging (AI)** — a "Otaguj nową treść"/"Otaguj
|
||
wszystko ponownie (force)" button pair in the BookStack card, plus
|
||
`php artisan bookstack:tag-content` (`--dry-run`/`--force`/`--limit=N`) for
|
||
the command line. Uses the AI integration above to classify every
|
||
book/chapter/page's title+content against the current list of helpdesk
|
||
subcategories and tags matching ones by name — idempotent by default
|
||
(skips already-tagged content), so re-running after adding a few pages is
|
||
cheap. This is what gives the BookStack "search by tags" option below
|
||
something to actually match against.
|
||
- **BookStack search refinement** — "Przeszukuj" is now three independent
|
||
checkboxes (Książki / Strony / Rozdziały) instead of a single dropdown with
|
||
no chapter option, plus a new "Szukaj po" setting: słowa kluczowe w nazwie /
|
||
tagi / oba. Tag matching uses the bare subcategory name (e.g. "Drukarki i
|
||
skanery"), matching what the auto-tagging feature above writes.
|
||
- **AI-driven ticket triage + summary** (Admin > Integracje >
|
||
"Automatyzacja AI dla zgłoszeń", runs via a new scheduled
|
||
`ai:run-ticket-automation`) — five independent toggles: assign a
|
||
category/subcategory when a ticket has neither, pick a subcategory when it
|
||
only has a category, recheck and possibly correct an already-categorized
|
||
ticket, rewrite an unclear subject, and set a priority based on content.
|
||
Runs once per ticket in the background (never synchronously at submission,
|
||
so it adds no latency for a client), and every applied change leaves a
|
||
specific line plus a "Automatyzacja: klasyfikacja AI" entry in the ticket's
|
||
history, same convention as the existing SLA automation rules. Separately,
|
||
an AI-generated summary + suggested next action for **every** ticket, shown
|
||
only to operators in a new "Podsumowanie AI" sidebar card, refreshed
|
||
whenever the thread gets a new message — its system prompt is admin-editable
|
||
as a plain-text field with a "Resetuj" button back to the shipped default.
|
||
- Operators can now reassign a ticket to **any** team, not just one they
|
||
belong to (previously the dropdown only ever offered the operator's own
|
||
teams).
|
||
- The auto-refresh countdown badges (ticket view, operator queue) are now
|
||
**clickable** — fetch immediately and reset the countdown, instead of only
|
||
ever refreshing on their own fixed schedule.
|
||
- **All 7 "cyclical" intervals** in the app are now configurable from
|
||
Admin > Konfiguracja instead of fixed in code: the 3 browser auto-refresh
|
||
countdowns (ticket view, operator queue), the notification bell's poll,
|
||
and the 4 background scheduled commands (SLA breach check, SLA automation
|
||
rules, IMAP fetch, AI ticket automation). Defaults match the previous
|
||
hardcoded values, so nothing changes until an admin edits them.
|
||
|
||
### Fixed
|
||
|
||
- An operator viewing a ticket that gets deleted by someone else, or whose
|
||
team changes to one outside the operator's own scope (including via their
|
||
own reassignment above), is now redirected back to the operator queue
|
||
instead of hitting an error mid-session.
|
||
|
||
## [1.2.0] - 2026-07-23
|
||
|
||
### Added
|
||
|
||
- **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 (which
|
||
now hosts SMTP alongside IMAP, replacing the old "E-MAIL" tab), each with
|
||
its own host/port/encryption/credentials/folder and routed to either a
|
||
specific subcategory (routes to that subcategory's team, same as a web
|
||
ticket) or a whole category with no subcategory (a new `tickets.category_id`
|
||
column covers this case — previously a ticket's category only ever came
|
||
through a subcategory).
|
||
- A reply is matched back to its ticket via the number/checksum already
|
||
present in every notification e-mail's subject — works with either the
|
||
plain sequential number or the obfuscated checksum, whichever numbering
|
||
mode is active, no changes to outbound templates needed.
|
||
- Automatic replies (autoresponders, "out of office", bounces/mailer-daemon)
|
||
are detected via headers and common EN/PL subject phrasing and rejected
|
||
instead of creating a ticket; a per-mailbox sender blocklist covers the
|
||
rest. The "tylko użytkownicy z LDAP" restriction is enforced for e-mail
|
||
exactly like the guest web form.
|
||
- A "Pobierz teraz" button per mailbox fetches immediately, outside the
|
||
5-minute schedule — useful for testing a freshly-configured mailbox or
|
||
diagnosing why a specific e-mail didn't turn into a ticket.
|
||
- Every connection attempt and per-message decision (accepted/rejected/
|
||
matched to which ticket) is logged to a dedicated `storage/logs/imap-*.log`
|
||
file, independent of the app's own log level.
|
||
- Tickets and individual messages that came in by e-mail show a small
|
||
mail-icon badge in the operator queue and ticket view, distinguishing them
|
||
from ones created/replied to on the web.
|
||
- **Operator queue**: a "select all" checkbox in the table header
|
||
selects/deselects every ticket currently visible under the active
|
||
filter/tab in one click, instead of clicking each row's checkbox.
|
||
|
||
### Changed
|
||
|
||
- Admin's old **"E-MAIL"** tab is now **"Poczta"** and also lists/manages the
|
||
IMAP mailboxes above — the two halves of "reply by e-mail" (send/receive)
|
||
now live together instead of SMTP being off on its own.
|
||
|
||
### Fixed
|
||
|
||
- **Scheduled-command notifications were silently going nowhere.**
|
||
`AppServiceProvider`'s Settings-based config override (SMTP/LDAP/session/
|
||
timezone) used to skip itself for *any* console command, not just
|
||
`migrate` — meaning `tickets:check-sla-breaches` and `automation:run-rules`
|
||
(and now `emails:fetch-imap`) always sent their e-mails through whatever
|
||
`.env`'s `MAIL_MAILER` happened to be (`log`, i.e. nowhere) instead of the
|
||
admin-configured SMTP server, with no visible error. Now scoped to just the
|
||
`migrate` command family, so every scheduled command gets the same live
|
||
config a web request would.
|
||
- Visiting a ticket that no longer exists (most commonly: it was deleted
|
||
while the viewer had it open, and a later background refresh hit the same
|
||
URL) no longer shows Laravel's default 404 page — redirects back to the
|
||
operator queue or client dashboard instead.
|
||
- This host had no crontab entry at all for `php artisan schedule:run` —
|
||
meaning SLA breach checks and automation rules had never actually run on
|
||
their own, only ever on request. Documented and configured (see
|
||
[CLAUDE.md](CLAUDE.md)).
|
||
|
||
## [1.1.4] - 2026-07-23
|
||
|
||
### Added
|
||
|
||
- **Configurable ticket numbering** (Admin > Konfiguracja > Ogólne) — an
|
||
admin-set prefix (default `#`) and a 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 the
|
||
number shown gives no indication of ticket volume or creation order. Every
|
||
ticket gets its checksum assigned once, on creation, guaranteed unique.
|
||
When this mode is on, ticket URLs switch to the same checksum too (custom
|
||
`Ticket::getRouteKey()`/`resolveRouteBinding()`), so a link and the number
|
||
on the page it points to always match — and a URL built under the other
|
||
mode stops resolving. The REST API is unaffected; it's pinned to `id`
|
||
regardless of this setting. Search (queue/dashboard) now also matches
|
||
against the checksum. A live preview against a real ticket from the
|
||
database shows exactly how the number will look before saving.
|
||
|
||
### Changed
|
||
|
||
- **Attachments**: dropped the inline image thumbnail preview in the message
|
||
thread — every attachment (images included) now shows as just its
|
||
filename, opening in a new tab on click, consistent with how non-image
|
||
attachments already worked.
|
||
|
||
## [1.1.3] - 2026-07-22
|
||
|
||
### Added
|
||
|
||
- **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 ordered actions (set status/priority/team/
|
||
assignee, or send an e-mail). Ships with its own dedicated, freely
|
||
add/edit/delete-able trigger e-mail templates — kept separate from the
|
||
fixed, per-event system templates, which stay exactly as fixed as before.
|
||
Complements the time-based SLA automation rules rather than replacing them,
|
||
guarded against runaway loops (a depth limit plus a same-value no-op check).
|
||
- **Ticket watching** — operators can star/"Obserwuj" any ticket to follow it
|
||
regardless of assignment or team.
|
||
- **Real-time notification bell** — the bell now updates the instant a
|
||
notification is created (broadcast on a new private per-user channel),
|
||
with the existing 30s poll kept as a fallback for a dropped websocket.
|
||
Optionally also raises a native in-tab browser push notification.
|
||
- **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 bell and whether that also sends an e-mail, plus an
|
||
opt-in toggle for the browser push notifications above.
|
||
- **Admin > Integracje** — new tab hosting LDAP/AD and BookStack
|
||
configuration, split out of Konfiguracja so that tab is just general
|
||
system settings (attachments, session, timezone).
|
||
- Operator queue: three more optional columns (off by default, toggle via
|
||
"Kolumny") — Podkategoria, Zespół, Utworzono.
|
||
|
||
### Changed
|
||
|
||
- The "Obserwuj" button on the operator ticket view moved next to the
|
||
auto-refresh countdown badge, both now grouped on the right.
|
||
- `/settings/notifications`: added a "← Wróć" link back to the operator/admin
|
||
area, the browser-push card now spans the full page width, and the
|
||
preferences table sits in a bordered card like the rest of the app.
|
||
- Trigger conditions on Podkategoria/Zgłaszający now show a name dropdown
|
||
instead of a raw ID field.
|
||
- The admin panel's active tab and the operator queue's active view now
|
||
persist across a plain page refresh (bound to the URL query string), so
|
||
reloading no longer bounces back to the first tab.
|
||
|
||
## [1.1.2] - 2026-07-22
|
||
|
||
### Added
|
||
|
||
- **Real-time updates (Laravel Reverb)** — the operator ticket queue now
|
||
updates live: new tickets appear, status/priority/team/assignee changes
|
||
and new replies re-sort/refresh the affected row, and closed/deleted/
|
||
reassigned-away tickets disappear, all without a manual refresh. The
|
||
ticket message thread is now "live chat": a reply from either side
|
||
appears for the other party instantly. Clients also see status/priority/
|
||
team/assignee changes and new history entries on their own ticket live.
|
||
Backed by two private channels (`operator.queue`, `ticket.{id}`) and two
|
||
broadcast events (`TicketQueueChanged`, `TicketMessagePosted`), sent
|
||
synchronously (no queue worker needed, consistent with how e-mail
|
||
notifications already work in this app).
|
||
- **Periodic fallback refresh** — since a websocket connection can drop
|
||
silently (backgrounded tab, network blip), the operator queue and both
|
||
ticket-detail views also poll themselves every 30–60 seconds regardless
|
||
of broadcasting, with a small visible countdown badge so it's clear the
|
||
page is still refreshing on its own.
|
||
- **SLA automation rules** (Admin > Automatyzacja SLA) — configurable rules
|
||
that act on a ticket after N minutes of customer silence (optionally
|
||
scoped to a priority/category/team): change priority, status, team, or
|
||
assignee. Reuses the same `TicketService` setters a manual operator
|
||
action would, so automated changes get the same history entry,
|
||
notification, and (now) live broadcast as a human doing it. A new
|
||
scheduled command (`automation:run-rules`, every 15 minutes) evaluates
|
||
all enabled rules; each rule only fires once per ticket until a fresh
|
||
reply or a close/reopen resets it.
|
||
- **New notification: ticket landed in your team** — every operator on a
|
||
team whose subcategories match a newly created ticket now gets notified
|
||
(enabled by default; toggle like any other trigger in Admin > Szablony
|
||
e-mail).
|
||
- **BookStack knowledge-base sidebar on the client's own ticket view** —
|
||
previously only shown to operators; clients now see the same
|
||
category/subcategory-matched suggestions on their ticket page that they
|
||
saw while creating it.
|
||
- **Operator ticket view / client ticket view now show the assigned
|
||
operator and team** in the "Status i priorytet" card (client side).
|
||
- **Stats dashboard**: new breakdowns — tickets by subcategory, CSAT
|
||
average by team and by operator, top 10 clients by ticket volume (+ one
|
||
"Goście" bucket for guest submissions), and a client × subcategory
|
||
cross-tab (top 10 clients × top 5 subcategories, rest folded into
|
||
"Inne"). The whole dashboard is now organized into labeled sections
|
||
(Podsumowanie / Rozkład zgłoszeń / Obciążenie / Klienci / Ocena obsługi /
|
||
Trend) instead of one flat wall of cards.
|
||
|
||
### Changed
|
||
|
||
- BookStack suggestions (ticket-creation wizards, guest landing page,
|
||
operator/client ticket views) now load in a beat after the page's first
|
||
paint (`wire:init`) instead of blocking the initial render on BookStack's
|
||
API response.
|
||
- The notification bell shows **unread notifications only** — reading one
|
||
(by clicking it or "mark all as read") now removes it from the list
|
||
instead of just dimming it.
|
||
- The client ticket-view page is now the same width as the operator's
|
||
(1180px, up from 920px) — the main message-thread column is unaffected.
|
||
- Mobile: the theme/notifications/profile-menu dropdowns in the top nav
|
||
now expand to the full screen width below 640px instead of a fixed
|
||
narrow width that could overflow off-screen.
|
||
|
||
### Fixed
|
||
|
||
- Two bugs that silently disabled all real-time updates from the moment
|
||
Reverb was first wired in: (1) the Echo client never sent a CSRF token
|
||
when authorizing private channels, so every subscription attempt was
|
||
rejected by the app's own CSRF middleware; (2) the Echo setup script
|
||
(loaded as a deferred ES module) raced against Livewire's own
|
||
synchronously-loaded bootstrap script and could miss the `livewire:init`
|
||
event entirely, silently skipping the operator-queue subscription.
|
||
- The `mariadb` container's healthcheck was failing (`Access denied ...
|
||
using password: NO`) because its auto-generated credentials file had
|
||
world-writable permissions (an artifact of this stack's NFS-backed bind
|
||
mount) and MariaDB's client refuses to read credentials from a file
|
||
anyone can modify — the healthcheck now passes.
|
||
- Several pre-existing test-suite gaps found while working in this area:
|
||
`User::roles` (a virtual attribute backed by the `roles`/`role_user`
|
||
pivot, not a plain column) needs the `roles` table seeded before
|
||
assigning a role by key — now seeded automatically for every test in
|
||
`Tests\TestCase`. A handful of other tests were missing
|
||
`NotificationSetting`/`ReplyQuickAction` seed data, asserting a stale set
|
||
of default-enabled notification triggers, or using the wrong Notification
|
||
Fake assertion for a real (non-guest) recipient.
|
||
|
||
## [1.1.0] - 2026-07-22
|
||
|
||
### Added
|
||
|
||
- **In-app notifications** — a bell in the top bar backed by Laravel's
|
||
database notification channel, alongside existing e-mail notifications
|
||
(same per-trigger toggle drives both; ticket links now correctly point into
|
||
the recipient's own area instead of always linking to the client view).
|
||
- **Drag-and-drop attachments** on every upload form (ticket creation, replies,
|
||
internal notes), plus inline image thumbnails in the message thread instead
|
||
of a plain download link.
|
||
- **Customer satisfaction (CSAT)** rating — clients rate a closed ticket 1–5
|
||
stars with an optional comment; shown read-only to operators, surfaced as a
|
||
KPI on the stats dashboard, and linked from the "ticket closed" e-mail.
|
||
- **Saved queue views** — operators can save/apply/delete named filter+sort+
|
||
column presets in the ticket queue and mark one as their default.
|
||
- **Full-text search** — MySQL/MariaDB `FULLTEXT` search (portable `LIKE`
|
||
fallback on sqlite) across ticket subject/body and reply message bodies, now
|
||
also available on the client's own ticket list (previously operator-only,
|
||
and previously subject/number/name/email only).
|
||
- **Stats CSV export** — exports the currently filtered ticket set from the
|
||
operator stats dashboard.
|
||
- **BookStack knowledge-base integration**, optional and off by default —
|
||
suggests relevant articles by category/subcategory while creating a ticket,
|
||
and in a separate sidebar on an existing ticket for operators (with a
|
||
copy-link button). Configurable from Admin > Konfiguracja: connection + API
|
||
token (encrypted), optional SSL-verification bypass for self-signed
|
||
instances, page/book search-type filter, and two independent per-shelf
|
||
allow-lists (nothing is ever searched until specific shelves are opted in,
|
||
separately for ticket-creation suggestions vs. the operator sidebar) with a
|
||
manual refresh button for the shelf list.
|
||
|
||
### Changed
|
||
|
||
- Closed tickets are no longer shown in the "Moje zgłoszenia" / "Nieprzypisane"
|
||
/ per-team queue tabs — they now only ever appear under "Zamknięte", matching
|
||
how the "Otwarte" tab already worked.
|
||
- The operator ticket-view sidebar is ~50% wider (to fit the BookStack
|
||
suggestions panel); the page itself grew to match, so the ticket
|
||
content/thread column keeps its previous width.
|
||
- The "Resetuj" work-timer button sits below the "Zgłoszenie zamknięte —
|
||
zliczanie wstrzymane" notice instead of beside it.
|
||
|
||
### Fixed
|
||
|
||
- `TicketService::setStatus()` now checks a status's `stage` (via
|
||
`Status::stageFor()`) rather than the literal key `'closed'` to decide
|
||
whether to fire the "ticket closed" notification/stop the timer — correct
|
||
even if an admin renames or replaces which key maps to the closed stage.
|
||
|
||
## [1.0.2] - 2026-07-22
|
||
|
||
- Fixed `.gitea/workflows/build.yml`: registry login was failing with
|
||
`unauthorized` because Gitea's auto-injected `secrets.GITHUB_TOKEN` isn't
|
||
granted push access to its own container registry on this instance. Now
|
||
uses a dedicated `REGISTRY_TOKEN` secret (a Gitea access token with
|
||
`write:package`/`read:package` scope), and the workflow fails fast with a
|
||
clear `::error::` message before attempting login if that secret isn't
|
||
configured, instead of surfacing Docker's opaque `unauthorized` error.
|
||
- Fixed: opening or manually resuming a **closed** ticket no longer starts its
|
||
work timer, and closing a ticket (via the status dropdown, a reply "quick
|
||
action" transition, the REST API, or merge) now checkpoints and stops any
|
||
running timer. Time tracking only ever accrues while a ticket is open.
|
||
- Fixed: the login form accepted an empty username/password, submitting them
|
||
straight to the auth provider. The form fields are now `required` (blocks
|
||
submission client-side) and `Login::submit()` also rejects blank/
|
||
whitespace-only credentials server-side before attempting authentication,
|
||
showing "Podaj nazwę użytkownika i hasło." instead.
|
||
- Fixed: closing a ticket sent two separate notification e-mails
|
||
(`status_changed` and `ticket_closed`) for the same event. Closing now only
|
||
fires `ticket_closed`; every other status transition still fires
|
||
`status_changed` as before.
|
||
|
||
## [1.0.1] - 2026-07-22
|
||
|
||
Documentation and deployment/CI overhaul — no application behavior changes.
|
||
|
||
- Added project docs: `TESTING.md`, `CONTRIBUTING.md`, `ARCHITECTURE.md`,
|
||
`SECURITY.md`, `CLAUDE.md`; removed the unmaintained stock Laravel
|
||
`src/README.md`.
|
||
- Added `.gitea/workflows/build.yml`: Gitea Actions now builds and pushes the
|
||
`servicedesk` image to the Gitea container registry whenever `Dockerfile`
|
||
changes on `main`.
|
||
- `compose.yaml` now pulls `image: gitea.kzbikowski.pl/kzbkowski/servicedesk:${IMAGE_TAG:-latest}`
|
||
instead of building locally; `mariadb` pinned to `mariadb:12.3`; added a
|
||
`mariadb` healthcheck and `depends_on: condition: service_healthy` for
|
||
`servicedesk`.
|
||
- Added tracked templates `compose.yaml.example` and `.env.example` (root) for
|
||
the previously-untracked `compose.yaml`/`.env`.
|
||
- Documented the new pull-based deploy flow and one-time registry login in
|
||
`install.md`.
|
||
|
||
## [1.0.0] - 2026-07-21
|
||
|
||
Initial release.
|
||
|
||
- Ticketing core: categories/subcategories with per-subcategory custom fields,
|
||
statuses, priorities, teams, attachments, message threads (public replies +
|
||
internal notes), history log, merge/delete.
|
||
- SLA rules per priority with a scheduled breach check (`tickets:check-sla-breaches`,
|
||
every 15 minutes).
|
||
- Response templates, quick actions, and per-event HTML email templates/
|
||
notification toggles.
|
||
- Operator statistics dashboard (`/operator/stats`) with KPI tiles and breakdowns.
|
||
- Branding/config panel: company identity, LDAP connection + user sync, SMTP,
|
||
attachment limits, session lifetime, timezone.
|
||
- LDAP authentication (LdapRecord) with local-account fallback.
|
||
- REST API (`/api/v1/...`) via Sanctum, ability-scoped (`tickets:read`,
|
||
`tickets:write`, `dictionaries:read`, `users:read`), with Swagger docs at
|
||
`/admin/api-docs`.
|
||
- Installable PWA manifest/icons for the client-facing area.
|