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>
8.1 KiB
CLAUDE.md
Guidance for AI coding agents working in this repo. See README.md for the feature/tech overview, ARCHITECTURE.md for how the app is put together, and install.md for full deployment instructions.
This is a live production system
The only real user today is the owner's own account (client+operator+admin roles). Treat the running database as production, not a sandbox:
- Never create/modify/delete real DB records (users, tickets, settings, etc.)
without the user explicitly asking. Don't spin up throwaway test users via
php artisan tinkeragainst this DB. - For UI/CSS verification, prefer an offline static-HTML harness reproducing the real markup+CSS, or ask for test credentials, rather than poking at prod data.
- Production is fronted by Traefik with a private-CA TLS cert (not publicly
trusted) — tools like
curl/Playwright need-k/ignore_https_errorsto hit it directly.
Container operations: use sudo, never build the image locally
All Docker commands against this stack need sudo (e.g.
sudo docker compose exec app ..., sudo docker exec servicedesk-app-1 ...).
The stack is four services sharing the one servicedesk image — app (Apache,
what actually serves HTTP), reverb (websocket server, php artisan reverb:start), cron (scheduler loop, php artisan schedule:work — see
below), and mariadb. Only app and reverb are reachable from Traefik.
Never run docker build, docker compose build, or --build. The
servicedesk image is built by CI (.gitea/workflows/build.yml, triggered on
Dockerfile changes on main) and pushed to the Gitea container registry;
compose.yaml only ever pulls a tag (sudo docker compose pull && sudo docker compose up -d, see install.md 1.3/1.3a) — building locally would
just diverge from what CI produces. The app container
(servicedesk-app-1) mounts ./src from the host over NFS
(/mnt/rabbit-containers → NFS export), so plain file edits already take effect
with no rebuild or restart:
- Blade/PHP/route edits: just edit the files — live instantly, no restart needed.
resources/css/app.css/resources/js/*(Vite/Tailwind entrypoints) do need a rebuild —public/build/assets/is static compiled output. Neither the host nor the app container has Node installed; rebuild with a throwaway container instead of touching the app image:(run from the repo root;sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run buildnode_modulesalready exists, no install needed). Verify viapublic/build/manifest.jsonpicking up a new hash.- After running artisan cache commands for diagnostics (
config:cache,view:cache), clear them again afterward (config:clear/view:clear) — this app normally runs uncached so edits apply live; leaving a cache on silently breaks that workflow. sudo docker execruns asroot, notwww-data. Apache's worker processes run aswww-data; anything you run via a plaindocker exec(php artisan test,tinker,view:cache, etc.) runs asroot. On this NFS-backed mount, a file Blade compiles/caches while running asroot(storage/framework/views/*.php) can't later be overwritten bywww-datawhen a real request needs to recompile it (the source changed) — this surfaces in production as a 500 withtouch(): Utime failed: Operation not permitted. If you ranphp artisan test/tinker/any artisan command viadocker execin a session where you also edited Blade files afterward, finish withsudo docker exec servicedesk-app-1 php artisan view:clearto flush any root-owned compiled views before ending the session — don't wait for a report of a broken page to catch it.
Scheduled commands run in the dedicated cron container
The Docker image ships no cron/supervisor of its own (see install.md),
so tickets:check-sla-breaches, automation:run-rules, emails:fetch-imap,
and ai:run-ticket-automation (all registered in routes/console.php via
Schedule::command(...)) only ever run if something calls php artisan schedule:run on a timer. As of 2026-08-04 this is the cron service in
compose.yaml — same servicedesk image, running php artisan schedule:work
(Laravel's own foreground scheduler loop, ticks every minute internally, no
external trigger needed). Before this it was a root crontab entry on the host
calling docker compose exec -T servicedesk schedule:run; that entry has been
removed from sudo crontab -l -u root now that the container replaces it —
don't re-add it, the two would double-run every scheduled command.
If the cron container isn't running (sudo docker compose ps cron), none of
the four scheduled commands fire — same failure mode as the old missing-crontab
case, just check the container instead of the crontab. IMAP-specific activity
(connect attempts, per-message accept/reject decisions, created/replied ticket
ids) is logged separately from the app's normal LOG_LEVEL to
storage/logs/imap-*.log (see the imap channel in config/logging.php) —
check there first when a mailbox isn't behaving as expected, before assuming
the scheduler itself isn't firing. ai:run-ticket-automation and
hesk:import get the same always-debug treatment via the ai/hesk_import
channels (storage/logs/ai.log/hesk-import.log). All of storage/logs/*.log
is also browsable from Admin > Logi (app/Livewire/Admin/Logs.php) if you'd
rather not docker exec in just to tail a file.
All four commands' intervals are admin-configurable (Admin > Konfiguracja —
schedule_sla_check_minutes/schedule_automation_rules_minutes/
schedule_imap_fetch_minutes/schedule_ai_automation_minutes), which is why
routes/console.php registers them as ->everyMinute()->when(fn () => Settings::dueEveryMinutes(...)) instead of a plain ->everyFifteenMinutes()/
->cron(...) call — never build a cron expression (or otherwise read
Settings) at that file's top level. routes/console.php is require'd on
every artisan boot (migrate, tinker, php artisan test, not just
schedule:run — it's wired in via bootstrap/app.php's commands: key), so
a top-level Settings::get(...) call runs before a fresh/test database
necessarily has the settings table, and crashes every single artisan
invocation, not just the scheduler. A ->when($closure) guard is the fix —
the closure is only ever evaluated later, when schedule:run processes due
events. One visible side effect: php artisan schedule:list shows
* * * * * for all four regardless of the actual configured interval, since
that only exists inside the closure — expected, not a bug worth chasing.
Apache /icons/ alias trap
The stock php:apache image enables mods-enabled/alias.conf, which defines
Alias /icons/ "/usr/share/apache2/icons/". Any app-level static directory at
public/icons/ is silently shadowed by Apache's own stock icon set — requests
404 before ever reaching Laravel, with no useful log line. This project
deliberately uses public/pwa-icons/ for PWA manifest icons for exactly this
reason — never add a public/icons/ directory.
Established UI conventions
- Post-login redirect always lands on
/client(User::defaultArea()inapp/Models/User.php), even for accounts holding operator/admin roles too — staff switch areas via the role switcher in the header (resources/views/components/profile-menu.blade.php). Keep/clientfirst-priority indefaultArea()if you add new roles/areas. - Mobile tables: don't rely on horizontal scroll alone for wide data tables.
The established pattern (
.table-cards-mobileinresources/css/app.css,@media (max-width:640px)) turns each<tr>into a card withdata-labelattributes on<td>s. Currently applied to the operator ticket queue; apply the same treatment to any other wide table you add or make mobile-relevant (admin panel tables don't have it yet).
Testing & code style
See TESTING.md and CONTRIBUTING.md — run
php artisan test and ./vendor/bin/pint before considering a change done.
There is no CI; local test runs are the only gate.