Files
servicedesk/CHANGELOG.md
Kacper 4b70b910a9 v1.5.0
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>
2026-08-05 14:28:03 +02:00

570 lines
32 KiB
Markdown
Raw Permalink 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.
# 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.5.0] - 2026-08-05
### Added
- **Admin log viewer** (Admin > Logi) — browse `storage/logs/*.log` from the
admin panel without shell access to the container: a file picker (size +
last-modified, most recent first), level/free-text/entry-count filters, and
an optional 5s auto-refresh. Read-only, admin-only; reads only the tail of
large files to keep it fast.
- **"Bez kategorii" filter** in the operator queue's category dropdown —
isolates tickets with neither a category nor subcategory assigned (e.g. an
IMAP mailbox routed to nothing in particular), previously only reachable by
scanning the unfiltered list.
### Changed
- **Hesk import tool** (`scripts/hesk-import/`) no longer auto-creates client
accounts for unrecognized requester e-mails — the servicedesk user base is
now treated as authoritative, so a Hesk ticket whose requester has no
matching account is skipped instead (reported at the end, with the list of
skipped e-mails). Hesk staff replies/notes and ticket ownership are now
linked to real operator/admin accounts when the staff e-mail matches one.
Two new backfill flags cover tickets imported before these existed:
`--assign-operators` (sets `assignee_id` from Hesk's ticket owner, never
overwriting a manual reassignment) and `--fix-closed-dates` (corrects a
closed ticket's date to Hesk's own `closedat` column instead of the
drifting `lastchange`, and adds the missing "Zamknięte" history entry).
Every newly imported ticket also records its source Hesk id
(`tickets.hesk_ticket_id`, unique) as a second, DB-level guard against
duplicate imports on top of the existing state file. See
`scripts/hesk-import/README.md` for details.
- Internal database cleanup: removed three columns confirmed unused against
live data (`users.remember_token`, `users.email_verified_at`,
`email_templates.trigger_label`); moved custom field values, AI
triage/summary state, and the linked Snipe-IT asset off the `tickets` row
into three dedicated one-to-one tables (`ticket_field_values`,
`ticket_ai_summaries`, `ticket_snipeit_assets`) — no visible behavior
change, but custom field values are now efficiently queryable instead of
living only in a JSON blob, and the `tickets` row itself is narrower; added
missing reverse indexes on 4 pivot tables (`team_subcategory`, `role_user`,
`team_user`, `custom_field_subcategory`); `tickets.source` and
`ticket_messages.source` now validate against a known set of values
instead of silently accepting any string.
### Fixed
- The operator statistics dashboard's category/subcategory breakdown (and
its category filter) only counted tickets routed through a subcategory —
a ticket routed to a whole category with no subcategory (e.g. via an IMAP
mailbox routed to "całą kategorię") was silently excluded from those
charts and from filtering by that category. Now counted correctly, with
bare-category tickets shown as their own "(bez podkategorii)" row in the
subcategory breakdown.
- A JavaScript error (and stray background timers left running) could occur
when navigating away from a page with an active countdown/timer widget —
ticket/queue auto-refresh, the theme switcher, file-attachment
drag-and-drop. Most consequential in the operator ticket time tracker,
where it could throw console errors and momentarily break other UI
elements (e.g. dropdown menus) after leaving a ticket with the timer
running.
## [1.4.0] - 2026-08-05
### Added
- **Active Directory support for LDAP auth** (Admin > Integracje > "LDAP /
Active Directory") — a "Typ katalogu" dropdown switches between LLDAP/
OpenLDAP (the original, still the default) and Active Directory. AD uses a
different schema (no inetOrgPerson/posixAccount, a binary `objectGUID`
instead of `entryUUID`) and login attribute (`sAMAccountName`, not `uid`);
both are now auto-detected from the directory type instead of requiring
LLDAP's schema everywhere.
- **Operator client search** (Operator > Klienci) — a dedicated search page
(name or e-mail, any account, not just role=client) showing each match's
role badges and ticket count, linking straight into the queue pre-filtered
to that customer.
- **Paginated ticket lists** — the operator queue (50/page) and client
dashboard (20/page, current/archive tracked as separate pages so switching
tabs doesn't lose your place) no longer render every matching ticket at
once; sorting still happens over the full filtered result first.
- **Command-palette global search (Ctrl+K / Cmd+K)** — searches tickets from
anywhere in the app, scoped to what the searching user can actually see
(operators/admins search everything visible to them, clients only their
own). Supports Gmail-style operators — `od:` (reporter), `temat:`
(subject only), `treść:`/`tresc:` (message content only), `numer:`/`nr:`
(ticket number), combinable and AND'd together (`od:kacper
temat:drukarka`) — plain text with no operator still searches everything
as before. A "Szukaj" button in the operator/admin sidebars opens the same
dialog for anyone who doesn't know the shortcut.
- **Recently viewed tickets** (operator sidebar) — the last 6 tickets an
operator actually opened, most-recent first, re-bumped (not duplicated) on
a repeat visit.
- **Navbar redesign: panel switcher.** The old dropdown-only role switcher
(in the profile menu) and the plain "Panel Klienta/Operatora/Administratora"
text label are replaced by a single segmented control centered in the top
bar — Klient / Operator / Administrator — showing only the areas the
logged-in user actually holds, highlighting the current one, and sliding a
preview to whichever option is hovered before you click. Adapts to a
full-width row below the icons on narrow screens instead of overlapping
them.
- **Richer profile dropdown** — now shows the account's name, e-mail, and
role badges above the existing Powiadomienia/Wyloguj się links.
- **Per-page browser tab titles** — every page sets its own `<title>`
(e.g. the ticket subject, the selected queue, the active admin tab)
instead of every tab just showing the company name, always anchored with
the company name as a suffix so it's still identifiable once the browser
truncates a long tab title.
- **Hesk 3.x historical import** (`scripts/hesk-import/`) — a one-time,
read-only migration of tickets (with full reply/note history) from an old
Hesk helpdesk database, filtered by e-mail domain. Dry-run by default,
resumable, auto-maps categories (exact name match) and teams (when
unambiguous). See `scripts/hesk-import/README.md`.
### Fixed
- Pagination controls ("« Poprzednia" / "Następna »") were showing the raw
translation keys `pagination.previous`/`pagination.next` instead of
actual text — the app's `APP_LOCALE=pl` had no matching `lang/pl/`
translation file and no English fallback (`APP_FALLBACK_LOCALE` is also
`pl`), so Laravel had nothing to resolve those strings to.
## [1.3.0] - 2026-07-27
### Added
- **Snipe-IT asset inventory integration** (Admin > Integracje), optional and
off by default — connects to a Snipe-IT instance by API address + personal
API token (plus a "Nie sprawdzaj SSL" toggle for self-signed instances) and
surfaces three independently switchable capabilities:
- **Klient może wybrać sprzęt, którego dotyczy zgłoszenie** — while
creating a ticket, a client sees the devices checked out to them in
Snipe-IT (matched by e-mail) and can pick the one the ticket is about.
Scoped to admin-selected subcategories via a multi-select picker that
only appears once this is turned on — same "nothing shows until
explicitly opted in" convention as BookStack's shelf allow-lists.
- **Operator może zobaczyć sprzęt zgłaszającego w widoku zgłoszenia** — the
same per-requester asset list, shown in a sidebar card on the ticket
view, with a "Powiąż" button per item.
- **Zezwól operatorowi na przeszukiwanie całego inwentarza** — a search
box + button in the same sidebar (not a separate page) letting an
operator link any asset in Snipe-IT, not just the requester's own — for
shared equipment like printers.
- A linked asset shows live status/category/current assignment (fetched
fresh from Snipe-IT, not just the cached label) with an "Odepnij" button
that stays available to the operator regardless of the two toggles above
— clearing an existing link is a correction, not new Snipe-IT access.
Every asset is displayed as "numer środka - numer seryjny - producent
model" plus its Snipe-IT category, joining whichever of those pieces are
actually present.
- **AI summary: manual regenerate + regenerate on new message.** A
"Wygeneruj teraz" button now sits on the operator's "Podsumowanie AI" card
for an immediate, on-demand refresh. Separately, a new admin toggle
("Regeneruj podsumowanie od razu po każdej nowej wiadomości", off by
default) re-runs the summary right after any reply/note lands on a ticket,
instead of only ever picking it up on the next scheduled
`ai:run-ticket-automation` sweep. The transcript sent to the model now also
includes the ticket's own opening body text (previously only the reply
thread), fixing summaries silently missing the original request on long
tickets whose first message had scrolled out of the transcript window.
### Fixed
- The status dropdown in the operator ticket view could keep showing the
pre-change status after sending a reply via a status-changing quick action
(e.g. "Wyślij i oznacz jako rozwiązane") until the next full page load — a
Livewire/Alpine-morph quirk for `<select>` elements bound via `wire:change`
rather than `wire:model`. Fixed by keying the element to the status value
so the DOM node is force-replaced instead of morphed.
## [1.2.2] - 2026-07-24
### Added
- Subcategories can now be reordered within their category — up/down arrow
buttons next to "Edytuj" in Admin > Kategorie, right beside each
subcategory's edit/delete buttons. The order set there is used everywhere a
subcategory list is shown (subcategory pickers, admin listings, etc.), not
just the admin panel itself.
## [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 3060 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 15
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.