Files
servicedesk/CLAUDE.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

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 tinker against 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_errors to 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:
    sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run build
    
    (run from the repo root; node_modules already exists, no install needed). Verify via public/build/manifest.json picking 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 exec runs as root, not www-data. Apache's worker processes run as www-data; anything you run via a plain docker exec (php artisan test, tinker, view:cache, etc.) runs as root. On this NFS-backed mount, a file Blade compiles/caches while running as root (storage/framework/views/*.php) can't later be overwritten by www-data when a real request needs to recompile it (the source changed) — this surfaces in production as a 500 with touch(): Utime failed: Operation not permitted. If you ran php artisan test/tinker/any artisan command via docker exec in a session where you also edited Blade files afterward, finish with sudo docker exec servicedesk-app-1 php artisan view:clear to 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() in app/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 /client first-priority in defaultArea() if you add new roles/areas.
  • Mobile tables: don't rely on horizontal scroll alone for wide data tables. The established pattern (.table-cards-mobile in resources/css/app.css, @media (max-width:640px)) turns each <tr> into a card with data-label attributes 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.