Compare commits

...
Author SHA1 Message Date
MiniMax M3 Subagent 89aa895192 fix(api): suppress historical_primary_product_mismatch when reg_2 is empty
Bug #10: When an order has only one registration number (reg_2 is empty)
but the reg historically appeared in tractor-trailer bookings, the
'historical_primary_product_mismatch' flag falsely suggests that the
current tractor-only billing is incorrect. The operator (Sarah #10714)
correctly expects that billing only a tractor is the right action when
there's no trailer to bill.

This change suppresses the historical_primary_product_mismatch flag for
rows where reg_2 is empty, matching the operator expectation. The
underlying historical SQL already filters by reg_1 only; a follow-up
could narrow the history to orders with matching reg_2 patterns, but for
the reported false-positive this fix is sufficient.

- invoice_period_flag_service.php: add o.reg_2 to the row projection
  and skip the historical mismatch loop when the current order's reg_2
  is empty.
- InvoicePeriodFlagServiceTest.php: include reg_2 in the existing
  historical mismatch test fixture so it still exercises the flag path.
2026-08-10 20:10:49 +02:00
MiniMax M3 Subagent 8972b8f2ae fix(api): apply e-conomic discount percentage at line level for customer 35131752
Bug #11: For customer 35131752 ('kd'), the 15% e-conomic discount was
configured on the customer but never applied to the draft invoice line
items. The customer discount was only used in the flag/preview service for
expected price calculations, not when actually building the draft invoice.

Changes:
- economicCustomers.php: log swallowed missing-currency-price errors so
  silently-missing discounts (like customer 35131752) become visible in
  the application log instead of vanishing.
- economic_invoice_draft.php: thread the customer discount percentage
  through addOrderItemLines/addOrderItemLine and apply it at the line
  level (e-conomic's draft invoice line API requires per-line
  discountPercentage; an aggregate TotDiscount line is ignored when the
  customer has a per-line discount configured).
- economic_invoices_draft_endpoint.php: forward the customer discount
  percentage to the draft builder.
- collected_order_invoices_o.php: resolve the customer discount via
  Redis cache + e-conomicCustomers, then pass it to add_orders.
- Tests: new EconomicInvoiceDraftCustomerDiscountTest covering the
  customer 35131752 15% case, plus updates to the existing wiring tests
  to account for the new parameter and the customer-discount guard on
  the aggregate TotDiscount line.
2026-08-10 16:23:05 +02:00
Jeppe BandTruck Wash Agent 43df3e4dca fix(api): only require notes when the product actually requires them on POST /order/items (#360)
## Bug
PR #345 (order_item_reason_policy wiring) accidentally broadened the
legacy
\`Notes is required for this product\` check to fire for every product
whose
POST body carried an empty/whitespace \`notes\` field.

Mobile POS step 2 always posts the primary product (e.g. Sættevognstræk,
product id 3) with \`notes: ''\` as part of
\`syncCurrentTransactionToOrder\`. After #345, the API started returning
400 for that primary item. The frontend silently swallowed the 400 in
the next-step click handler, and the operator saw **"Fuldfør doesn't
continue"** with no feedback.

## Repro
1. Log in to the mobile POS (e.g. dept 12 / Taulov)
2. Scan / type a customer's plates (e.g. EP68666 + GG1876)
3. Long-press Sættevognstræk to add the service
4. Tap **Fuldfør**

Before this fix: \`POST /order/items\` → 400 \`Notes is required for
this product\`. Frontend catches and logs \`Next-step action was
interrupted: AxiosError: Request failed with status code 400\`. Operator
sees no error in the UI.

After this fix: \`POST /order/items\` → 200 for the primary product; the
order completes normally.

## Fix
Scope the empty-notes rejection to products whose \`requires_note\` flag
(or extraordinary-chemistry special case) is set, matching the existing
PUT handler behaviour. Products that don't require notes can post
\`notes=''\` without rejection.

## Lock-in tests
Two Pest tests under \`Tests\\Api\\OrderItemsApiTest\`:
- \`allows empty notes for primary products that do not require a note\`
— \`requires_note=0\` product with \`notes=''\` returns 200
- \`still rejects empty notes for products whose requires_note flag is
enabled\` — \`requires_note=1\` product with \`notes=' '\` returns 400
with the legacy message

## Verification
PHP API suite: **292/292 passing** (11892 assertions). Local
\`scripts/php-ci-test.sh api\`.

## Companion PR
\`copenhagentruckwash/pleno-vue\` →
\`fix/fuldfor-surface-order-item-error\` will surface order-item API
errors in the UI so silent failures become visible. That PR is a
follow-up; this one is the actual root cause fix.

Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
2026-08-10 11:32:11 +02:00
Jeppe B 7174e3be6c fix(api): coerce string booleans before persisting module_config bool values (#359)
PHP truthy semantics treat the literal `'false'` as truthy, so `$value ? 'true' : 'false'` in `setVariableValue()` persisted every 'switch off' request as `'true'` for ~30 modules with bool config variables. Canonicalise via the existing `inputToBool()` helper before the ternary so JSON.stringify(boolean) inputs land on the right storage string.

Live repro (api-v2.truckwash.io, 2026-08-10 09:25):
```
POST /minimax/config {variable:"enabled", value:"false"} → 200
GET  /minimax/config?variable=enabled → {value:true, ...}      ← unchanged
POST /minimax/config {variable:"enabled", value:false}   → 200
GET  /minimax/config?variable=enabled → {value:false, ...}     ← JSON bool works
```

Tests: `tests/Unit/MiniMax/MiniMaxEnabledSetVariableValueTest.php` (6 assertions, captures the UPDATE column value via stubbed `updateVariableValue`/`insertVariableValue` for string/boolean × true/false × insert/update paths).

Companion pleno-vue PR #281 lands the matching UI-side fix (`onMiniMaxEnabledSwitch` re-fetches + optimistic rollback).
2026-08-10 09:21:56 +02:00
Jeppe B 2ef0f78541 test(api): lock MiniMax config redaction + isSet contract (#358)
Regression coverage for the read-side shape that the frontend `ConfigurationXLVask.refreshMiniMaxApiKeyStatus` parses. Mirrors `BirdConfigSecretRedactionTest` — locks the contract that `api_key` is redacted with `isSet` reflecting actual persistence, so the XL Vask autopilot's `requireModuleEnabled` cannot silently break on a regression.

No production-code change — backend already correct (verified live 2026-08-10 07:58 against `api-v2.truckwash.io` with both `{variable, value}` and raw-key payload shapes).

Companion pleno-vue PR: #281 ("fix(pleno-vue): persist MiniMax API key across refresh + render fix").
2026-08-10 08:45:10 +02:00
Jeppe B 801c8e1f6d feat(api): standalone xlvask-automation-migrate script + runbook section (#357)
Adds `scripts/xlvask-automation-migrate.php` mirroring the existing schema-script pattern (`check` / `apply --yes`), a new AUTOMATION_RUNBOOK §2a documenting both operator entry points, and `XLVaskAutomationMigrateScriptTest` pinning the gate, the WD check, and the bootstrap references.

Production autopilot-runs (POST /modules/xlvask/services/usage/autopilot-runs) was returning 500 with:
```
XL Vask automation schema is not ready. Apply migration 20260804_xlvask_ai_auto_policy_v2 explicitly.
```
because no operator had invoked the gated `applyExplicitMigration()` since PR #348 shipped the migration class. The frontend half of the fix is the companion change in pleno-vue#280 (missing `minimax_integration_enabled` key).

Operator action required once merged:
```
php index.php run xlvask-automation-migrate
# or
php scripts/xlvask-automation-migrate.php apply --yes
```
Both produce identical JSON status; retain the artifact and rerun `check` to confirm postflight is green.

Diff: +140/-0 (3 files). Tests: 87/87 XL Vask unit + 5/5 new migration script test pass.
2026-08-10 08:18:28 +02:00
Jeppe BandCleanup Agent 3e89085296 feat(api): support manual XL Vask operator decisions via force_manual (#356)
Adds a deterministic manual-suggestion path so operators can drive
accept/reject/ignore decisions on the self-wash view before the AI
autopilot has produced a suggestion. Whitelists force_manual in the
preview route. Adds unit tests for the new constant, method, and route
contract.

---------

Co-authored-by: Cleanup Agent <agent@truckwash.io>
2026-08-09 21:48:03 +02:00
Jeppe BandTruck Wash Agent 2cf2538525 feat(api): add MiniMax M3 client and force XL Vask autopilot to use it (#355)
Adds a new `modules/miniMax` module mirroring the existing OpenAI
pattern (`api_key` + `enabled` config keys). Adds a
`classes/minimax.php` client that calls
`https://api.minimax.io/anthropic/v1/messages` (the same endpoint
OpenClaw's minimax-portal provider uses) and returns structured JSON via
the Anthropic tool_use response shape.

**Replaces the autopilot planner:**
- `PLANNER_MODEL`: `gpt-5.6-sol` → `MiniMax-M3`
- `xlvask_automation_service`: `new openai()` → `new minimax()` in the
planner + the isOpenAiIntegrationEnabled() guard
- New xlvask config flag `minimax_integration_enabled` gates the
autopilot. The OpenAI flag is kept so deployments can roll back.

**Compatibility shims** (so the autopilot keeps working with minimal
churn):
- `minimax_request_exception` extends `openai_request_exception`, so
every existing `catch (openai_request_exception)` block catches MiniMax
errors unchanged.
- The result payload also carries the legacy `_openai_response_model`
and `_openai_usage` aliases, so `sanitizeOpenAiResult` keeps working
unchanged.

**New endpoints:** GET/POST `/minimax/config` in `moduleConfigRoute`
guarded by `modules_minimax_config`, mirroring `/openai/config`.

**Tests:** PHP api suite 290/290 passing locally (no regressions). All
xlvask tests still pass.

**Frontend counterpart** ships in a separate PR on pleno-vue
(ConfigurationXLVask.vue + SessionUser.modules.minimax + i18n in all 5
locales).

**Followup (post-merge):** operator (jeppe) enters the MiniMax API key
in superuser XL Vask settings → I'll optimize/test/debug live XL Vask
usage logs against the new model.

---------

Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
2026-08-09 19:34:03 +02:00
Jeppe B 6475f817c7 fix(api): wire order_item_reason_policy into POST/PUT and persist reason fields (#345)
Adds `reason_code`, `reason_label_snapshot`, `reason_comment` columns to `order_items` and integrates the `order_item_reason_policy` class into the POST and PUT /order/items routes.

Validation order on audited products (consistent across POST and PUT):
1. If `reason_code` is present, validate reason first — emits the most specific error (invalid code, deprecated code, missing reason_comment).
2. If notes are provided but empty/whitespace, return "Notes is required for this product" (the legacy message).
3. Otherwise run reason validation — covers the missing-reason_code case.

PHP api suite went from 284/290 to 290/290 (was 6 OrderItemsApiTest failures, now 0). Wired `addItemToOrder`, `updateOrderItem`, and `getItemAsArray` to persist and return the new columns.
2026-08-09 17:53:00 +02:00
Jeppe B b107ba649c Wire XL Vask usage-log sync, order linking, and order creation (#353)
Wires the three broken paths in the cron-driven XL Vask integration:

- runSyncUsage() now calls the revision-aware importUsageLogsWithSummary() on xlvask_usage_logs_o (was a no-op stub; the upstream API was never queried for washes).
- linkImportedUsageLogsToOrders() persists xlvask_potential_order_matches rows so the accept/compare/link/deny UI has data to render.
- When automatic_order_creation_enabled is on and the wash qualifies, falls through to createOrderFromWash() → orders_o::addXLVaskOrder().
- Removes the obsolete TODO in RunXLVaskModuleCron.php.
- Returns linked + orders_created alongside the existing counters so ops can observe the pipeline.
2026-08-09 13:38:27 +02:00
Jeppe B 0aaf32efa4 Surface silent-skip paths and additional silent failures in email/booking/order flows (#352)
## Why

Customer `k.sand@ksand.dk` reported never receiving wash certificates for completed bookings. Two methods contained silent early-return guards so the actual reason was unobservable from container logs:

- `order_bookings_o::sendWashCertificateToCustomer()` — 5 silent returns
- `email::sendWashCertificateEmailToCustomer()` — 1 silent return

The most likely root cause: `email_notifications_enabled` defaults to `0` in the schema and `users.add()` does not set it on insert, so newly imported customers have notifications off until toggled. `wantsEmailNotifications()` then returns false and the email silently skips.

## What changed

### Original commit (`0ead5de5`)
- `objects/order_bookings_o.php` — all 5 silent early-returns now log via new `logWashCertificateSkip()` helper (Redis stream `module=email / action=WASH_CERT_SKIP` + `error_log('[wash-cert-skip] …')`).
- `classes/email.php` — silent `hasTransaction()` return in `sendWashCertificateEmailToCustomer()` now logs too.
- `objects/bookings_o.php` — emits `WASH_CERT_SKIP` (legacy_no_wash_certificate_email) when `washCertificateEmail` is empty; no behavioural change.
- **New** `routes/washCertificateDebugRoute.php` — `GET /debug/wash-certificates/diagnose?customer_number=&from=&to=` (404 in prod via $DEBUG; superuser-auth otherwise) replays the decision tree and reports `blocking_reason` per booking.

### Follow-up commit (`46a59e4e`) — silent-failure sweep

**PART A — silent returns / silent errors (10 fixes):**
- `email::sendEmailMailerSend()` — blacklisted-recipient skip now logs with context.
- `email::sendNewCustomerRegistrationNotifications()` — empty-email skip + per-recipient try/catch with error_log (was unprotected; a single MailerSend error broke the loop).
- `bookings_new_o::generateWashCertificate()` — wrapped `sendWashCertificateEmail()` in try/catch with error_log and re-throw (same pattern as the k.sand fix).
- `users_o::getCustomerName()` — replaced catch-and-swallow with structured error_log.
- `users_o::getCustomerEcocomicData()` — same.
- `bookingsRoute.php` — added booking-id context to 4 × `$response->error('Booking not found', 404)` calls.

**PART B — cron paths (10 files):** Added error_log breadcrumb + try/catch to `CheckUnfulfilledBookings`, `ClearAllUsersEconomicCustomerDetails`, `ClearAllUsersEconomicCustomerDiscounts`, `RunXLVaskModuleCron`, `SyncBookings`, `SyncEconomicInvoiceStatus`, `SyncLogs`, `BackfillEconomicV2History`, `EnsureXLVaskAutomationSchema`, and 3 functions in `Cron.php`. Each uses a distinct `[cron-…]` prefix for grep-ability.

**PART C — real bugs (2 fixed):**
1. `email::sendEmailMailerSend()` attachment `array_map` — the previous exception message emitted a binary blob because `$attachment[0]` was already overwritten by `file_get_contents()`. Now captures $path first.
2. `bookings_new_o::generateWashCertificate()` — booking persisted as `completed` before email was sent, with no try/catch. Fixed (see PART A).

## How to verify

1. Deploy to staging.
2. Hit `/debug/wash-certificates/diagnose?customer_number=<k.sand's customer_number>` as a superuser — the response lists every booking's `blocking_reason`.
3. Tail container logs for `[wash-cert-skip]`, `[email-skip]`, `[cron-…]`, and Redis stream `module=email` action `WASH_CERT_SKIP` to see real-world skips going forward.

## Follow-ups (out of scope)

- Schema migration to default `email_notifications_enabled` to `1` and backfill non-empty-email customers.
- Move `error_log` to a proper PSR-3 logger.

## Risk

- Logging only + new debug endpoint (404-gated in prod). No behavioural change for any path that previously sent mail successfully. `php -l` could not be run in the original sandbox; please verify on your CI box before deploying.

🤖 Generated with [OpenClaw](https://openclaw.ai)
2026-08-09 00:21:04 +02:00
8735bae8d5 Fix queue export 400 by aligning e-conomic reference payloads (#351)
## Context
Queue job **#3572** failed in `COLLECTED_INVOICE_EXPORT` with e-conomic
HTTP 400 (`Validation failed. 2 errors found.`) while creating draft for
customer `35131752`.

## Fix
- align draft payload optional references to e-conomic object references
(not id-only fragments):
  - `recipient.attention`
  - `references.customerContact`
  - `references.salesPerson`
  - `references.vendorReference` (legacy path)
  - `deliveryLocation`
- include upstream `self` links when available from customer payload
- keep both collected and legacy draft creation paths consistent
- improve e-conomic error formatting so nested annotated validation
errors and `developerHint` are included in thrown messages

## Tests
- `vendor/bin/pest
tests/Unit/Invoicing/EconomicCustomerEanHelperTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftRecipientEanWiringTest.php
tests/Unit/Invoicing/EconomicLegacyDraftPayloadWiringTest.php
tests/Unit/Invoicing/EconomicUpstreamErrorFormattingTest.php
tests/Unit/Invoicing/EconomicLegacyDraftDiscountWiringTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftDiscountLineModeWiringTest.php
tests/Unit/Invoicing/CollectedInvoiceEconomicBatchTransferWiringTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftItemizedDiscountTest.php
--colors=never`

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-05 14:04:59 +00:00
bd2ff1be9a Fix false 409 snapshot expiry in invoice-period tree preview (#350)
## Problem
`POST /superuser/invoicing/period/tree-actions/preview` could return
`409 Invoice-period snapshot is missing or expired` during normal UI
flows when a snapshot binding aged out before the user triggered the
action.

## Fix
- introduce a dedicated snapshot cache TTL
(`SNAPSHOT_BINDING_TTL_SECONDS`)
- keep preview cache TTL unchanged (`PREVIEW_TTL_SECONDS`)
- use the longer snapshot TTL for actor/customer snapshot binding writes

This preserves existing safety because snapshot bindings are still
revalidated against fresh revision data before use.

## Tests
- `vendor/bin/pest
tests/Unit/Invoicing/InvoiceCollectionBulkActionSafetyTest.php
--colors=never`
- `vendor/bin/pest tests/Api/CollectedInvoiceBulkActionsApiTest.php
--colors=never` (suite present; skipped without `RUN_API_TESTS=1`)

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-05 12:55:06 +00:00
59107a6bb2 Enforce e-conomic discount template + EAN draft metadata (#349)
## What changed
- enforce EAN draft delivery wiring by setting
`recipient.nemHandelType=ean` when customer EAN is present
- copy existing e-conomic customer metadata into draft payload:
`recipient.attention`, `references.customerContact`,
`references.salesPerson`, and `deliveryLocation`
- keep `references.other` external-id mapping intact
- remove legacy explicit `Rabat:` text-line injection and use line-level
`discountPercentage` instead
- add/update unit tests for helper extraction and discount/EAN wiring

## Tests
- `vendor/bin/pest
tests/Unit/Invoicing/EconomicCustomerEanHelperTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftRecipientEanWiringTest.php
tests/Unit/Invoicing/EconomicLegacyDraftDiscountWiringTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftDiscountLineModeWiringTest.php
tests/Unit/Invoicing/CollectedInvoiceEconomicBatchTransferWiringTest.php
tests/Unit/Invoicing/EconomicInvoiceDraftItemizedDiscountTest.php
--colors=never`

## Notes
- full unit suite in this environment still has an unrelated
pre-existing failure in
`Tests\\Unit\\Bird\\BirdControlPlaneActivationTest` requiring
`PLENO_REPO_ROOT_FOR_TESTS`.
- live manual verification against customer `12345679` remains
environment-blocked due missing e-conomic credentials.

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-05 14:44:40 +02:00
Jeppe BandJeppe Bundgaard 622fe59f5c origin/schema-migration-xlvask-autopilot (#348)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-08-04 16:08:31 +00:00
Jeppe BandJeppe Bundgaard 23fc410d25 xlvask-autopilot (#347)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-08-04 17:46:15 +02:00
Jeppe BandJeppe Bundgaard db9f589bf7 Resolve issue causing crash when plate_scanners.deleted_at was missing. (#346)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-08-04 16:43:46 +02:00
Jeppe B ab6c3ba5b6 Fix route permission instance calls (#344)
## Root cause

`route_t::hasPermission()` and `requirePermission()` are instance
methods. Route code was invoking them with `self::`; the new XL Vask
hall-scope helper made that call from a genuinely static context,
causing PHP to throw:

`Non-static method routes\\xlvaskUsageLogsRoute::hasPermission() cannot
be called statically`

## Changes

- Invoke route permission methods through `$this` across all 273
executable legacy calls in 45 route classes.
- Make `xlvaskUsageLogsRoute::allowedHallIdsForUser()` an instance
helper and update all 13 callers.
- Preserve the existing all-scope and own-scope hall selection rules.
- Add a token-aware regression test that rejects executable
`self::hasPermission()` and `self::requirePermission()` calls, while
ignoring comments.
- Add focused XL Vask tests for global scanner hall scope and
group-limited own scope.
- Update affected route contract assertions to the instance-call form.

## Verification

- PHP lint: all 53 changed PHP files
- Focused PHPStan: changed XL Vask route and both new regression tests —
clean
- Focused regression slice: 58 passed, 748 assertions
- Full local unit suite: 1,300 passed, 9,442 assertions (1 unrelated
existing warning, 1 environment skip)
- Full local API suite: 285 passed, 11,704 assertions
- Exact-SHA GitHub Tests workflow: all 7 jobs passed (unit, API,
integration, legacy, edge gateway, and supporting checks)
- Independent exact-SHA QA gate: PASS, no findings
- Independent exact-SHA security gate: PASS, no findings
- Independent exact-SHA reviewer gate: PASS, no findings
- Remote comparison: exactly one commit ahead of
`40b104abed7723a7d1b7028190ecda0e7aeef829`; all 53 remote blob hashes
matched the reviewed worktree

## Delivery state

Draft only for human review. No merge or deployment is included. Qodana
is skipped while the PR remains draft and is therefore not represented
as a passed gate.
2026-08-04 16:04:41 +02:00
Jeppe B 40b104abed Add governed XL Vask AI invoice automation (#343) 2026-08-04 11:32:49 +02:00
Jeppe B 842b06c93f Fix frontend release version recording (#342)
Add a scoped release-gate endpoint for exact frontend SHA recording and independent readback.
2026-08-03 16:11:09 +02:00
Jeppe B 1f03b46564 Fix XL-Vask usage metadata hydration (#341)
Keep automation metadata out of the strict legacy XL-Vask helper payload while returning it separately. This restores the invoice-period XL-Vask orders GET after the guarded autopilot deployment.
2026-08-03 16:00:52 +02:00
Jeppe B 6d888a455d Automate XL-Vask invoice-period resolution (#340)
Deploy the revision-aware XL-Vask import and guarded autopilot infrastructure. Automatic actions remain fail-closed pending production readiness, calibration, dry-run, and canary gates.
2026-08-03 15:33:55 +02:00
Jeppe B 0b304a2203 Gate invoice tree activation on audit schema (#339)
## Summary
- gate object-tree v2 on exact, schema-backed supersession audit columns
- serialize checked additive DDL and fail closed without disrupting
legacy invoicing
- preserve pre-schema supersession markers when structured columns are
still null
- add a superuser-only, self-scoped canary endpoint with locked
legacy-to-canonical allowlist migration
- verify effective activation, roll back failed readiness, and audit
enable/disable/failure

## Verification
- focused invoicing safety: 16 tests passed (99 assertions)
- full unit suite: 1,237 passed (9,003 assertions), 2 skipped, existing
warnings only
- PHP syntax and `git diff --check` clean
- independent architecture, security, and reviewer gates: GO

## Activation
Deploy with global database/environment enablement off. POST the
self-canary endpoint for one authenticated superuser, require
`configured_enabled=true` and `effective_enabled=true`, then verify the
exact period and tree GET routes. Roll back with the same endpoint using
`enabled=false`.
2026-08-03 13:03:55 +02:00
Jeppe B 068f9e254f Complete selected-customer invoice period tree (#338)
Add the authoritative revision-bound invoice collection tree and guarded cleanup, merge, price-reset, and transfer operations.
2026-08-03 12:02:34 +02:00
Jeppe B f37feef1e6 Fix customer login email session refresh (#337)
Invalidate cached auth sessions for every active customer token and return the persisted canonical login email.
2026-08-03 09:40:00 +02:00
Jeppe B c795df4aad Add invoice period review workflow (#336)
Improve the superuser invoice-period review API, stale-preview protection, queue visibility, review blockers, and e-conomic eligibility.
2026-08-02 19:20:50 +02:00
Jeppe B 1e0e051775 Harden Sæby demo registration and department scope (#335)
Complete and secure public customer/driver registration, authoritative limited-backoffice department scope, one-time employee QR login, and pricing concurrency for the Sæby demo.
2026-08-02 11:50:56 +02:00
Jeppe B 4587bdfb06 Restore API startup by isolating Bird activation (#334)
Keep Bird activation outside the PHP-FPM/nginx startup path so Bird configuration failures cannot make the core API unavailable. Retain guarded explicit activation and regression coverage.
2026-07-29 22:41:19 +02:00
Jeppe B a442e70744 Add secure Bird gateway for Pleno Control Plane (#332)
Add the Bird Control Plane gateway, signed webhook ingestion, policy-gated writes, fail-closed production auto-activation, and RSA-OAEP bootstrap credential flow.
2026-07-29 19:59:20 +02:00
Jeppe BandJeppe Bundgaard 1b161f1b96 Guard Superuser invoicing registration search fields (#331)
## Summary
- Adds a backend contract guard that Superuser invoicing period search
continues to include all separate registration columns: `reg_1`,
`reg_2`, and `reg_3`.
- This preserves the API/search behavior that the frontend object-tree
registration layout relies on.

## Tests
- `vendor/bin/pest
tests/Unit/Invoicing/InvoicingPeriodRouteGuardsTest.php --colors=always`

Note: local Composer install was run with
`--ignore-platform-req=ext-mysqli --ignore-platform-req=ext-gd` because
those PHP extensions are not enabled in this worker image; the executed
guard test does not use them.

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-29 13:54:58 +02:00
Jeppe B 448e0e50c2 Fix api-v2 CORS at the edge without changing rollout flows (#330)
Add API-only Traefik CORS middleware labels while preserving configured origins and the existing rollout/load-balancer behavior.
2026-07-29 09:01:08 +02:00
Jeppe B 710baad28e Add one-time limited backoffice login grants (#329)
## Summary

Adds the missing backend contract used by Pleno Control Plane
Conversations/Suggestions to create an employee login action safely.

- issues 60–900 second one-time limited-backoffice login grants
- persists only SHA-256 bearer digests; bearer recovery is deterministic
under the server encryption key for identical idempotent retries
- enforces manager permissions, department scope, active
managed-employee constraints, one-time atomic exchange, revocation,
expiry, and account-deletion cleanup
- adds employee-create idempotency so an approved automation retry
cannot duplicate an employee
- documents the create, revoke, and unauthenticated exchange endpoints
in OpenAPI

## Security and concurrency

- bearer values are returned only in a URL fragment and are never
written to logs or database plaintext
- employee and grant rows use a consistent employee-then-grant lock
order
- deactivation revokes outstanding grants and existing sessions in the
same transaction
- consumed, revoked, expired, or payload-mismatched idempotent replays
fail closed

## Verification

- `scripts/php-ci-test.sh api`: 273 passed, 11,086 assertions (one
inherited warning)
- focused security contract: 1 passed, 21 assertions
- PHP syntax checks passed for the service and routes
- `git diff --check` passed

## Dependency

Required by copenhagentruckwash/pleno-control-plane#1. Merge before the
matching frontend and Control Plane PRs.
2026-07-29 00:01:13 +02:00
Jeppe BandJeppe Bundgaard 42ddce84bc Serialize VAT collection mutations with payment operations (#326)
## Summary

- Makes Stripe Terminal card payment intents always use 25% moms in the
API, independent of any client-supplied `tax_percentage`.
- Updates amount calculation, metadata persistence, stored-intent reuse
matching, the authoritative OpenAPI contracts, and operation-specific
Writerside outputs.
- Prevents double charging and false order closure across stale,
concurrently succeeded, partially recorded, or mismatched intents.
- Serializes payment create/capture/closure with order-item changes and
every order-to-invoice-collection reassignment through shared database
locks.
- Converts expected lock contention and reconciliation cases into
deliberate 409 responses.

## Exact-head evidence

Current head: `3a0f70d315a94d2efe586a2188d2c54f8ff11cd4`

- PHP syntax passed for all changed runtime files.
- Focused Orders suite: **42 tests / 293 assertions passed**.
- `git diff --check` passed.
- Fresh exact-head Tests and Qodana are running.
- Every Codex finding has a concrete reply; a fresh exact-head review is
requested below.

## Safety behavior

- Caller-controlled VAT is absent from request contracts; fixed 25% moms
is server-owned.
- A succeeded payment is preserved, requires the full expected
`amount_received`, and cannot close a changed/mismatched or
already-claimed collection.
- A compatible partially recorded Stripe closure is completed
idempotently; conflicting partial state fails closed for manual
reconciliation.
- Every cancellation/delete caller honors a concurrent-success result
and never falsely reports a completed payment as cleared.
- Price changes and invoice-collection reassignment share the payment
lock through validation, capture, post-capture reload, and closure.
- Reader changes are persisted only for reusable matching intents, so
stale intent cancellation targets the original terminal.
- Accepted legacy succeeded intents normalize stored tax to 25% before
response construction.

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-28 22:00:59 +02:00
Jeppe B da0113e3ed Auto-disable department self-serve at opening (#323)
Auto-disable department self-serve at opening
2026-07-28 18:03:05 +02:00
Jeppe BandJeppe Bundgaard 3c13892366 Harden subuser permission payloads (#328)
## Summary
- Normalize subuser grant permission payload keys before enum validation
so mixed-case customer-facing writes are accepted and deduped
consistently.
- Add a focused subuser route static check for permission payload
normalization.

## Verification
- `php
services/nginx/app/tests/subusers/SubusersRoutePermissionLinkTest.php &&
php
services/nginx/app/tests/subusers/SubusersRoutePermissionsPayloadTest.php`
- `php -l services/nginx/app/routes/subusersRoute.php && php -l
services/nginx/app/tests/subusers/SubusersRoutePermissionsPayloadTest.php`

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-28 17:06:29 +02:00
Jeppe B 30d860fdef Retire payment links and capture card payments (#327)
## Summary

- retire Stripe hosted payment-link creation routes used by POS and
order management
- automatically capture authorized payment intents rather than requiring
a separate manual capture action
- preserve ordinary terminal payment and payment-intent lifecycle
behavior
- add API and wiring regressions for payment-link retirement and
automatic capture

Paired frontend change:
https://github.com/copenhagentruckwash/pleno-vue/pull/233

## Verification

- focused backend unit suite: 2 tests, 36 assertions passed
- PHP syntax checks passed
- paired frontend unit and Playwright suites passed locally
- full required GitHub runner suites are required before this task may
enter Review or merge

## Security and operational notes

- no credentials, terminal secrets, or payment data are added
- no live Stripe account or physical terminal was exercised locally
- automatic merge remains gated on both paired PRs having passing
required checks and current branches
2026-07-27 19:09:37 +02:00
Jeppe BandJeppe Bundgaard d3e4798b11 Complete subuser notification and recovery lifecycle (#325)
## Summary

- notify customers by SMS with approve/deny links when a subuser
requests access
- notify subusers by SMS after approval or denial, including manual
grant changes
- support subuser password reset and authenticated password changes
- add read-only token previews followed by explicit POST confirmation
- store short-lived one-time purpose-bound action tokens only as SHA-256
digests
- serialize grant decisions transactionally to prevent conflicting
concurrent actions
- document the API contract in OpenAPI

## Security

- generic reset responses reduce account enumeration
- URL tokens are removed from browser history after frontend bootstrap
- approval previews never mutate state
- concurrent decisions lock the exact grant row
- SMS failures remain non-fatal and are returned as delivery status

Residual risk: existing subuser sessions cannot all be centrally
invalidated after password reset because there is no per-subuser session
index; they expire normally within the existing session lifetime.

## Verification

- backend Pest: 14 tests, 91 assertions
- PHP syntax checks passed
- focused PHPStan passed
- OpenAPI YAML parsed successfully
- `git diff --check` passed

Database-backed API integration tests were unavailable because the local
environment lacks the required database configuration.

## Paired delivery

Paired Frontend PR:
https://github.com/copenhagentruckwash/pleno-vue/pull/231

Both PRs are required before completion. The frontend PR contains the
responsive visual comparisons.

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-27 18:40:40 +02:00
Jeppe B 8d8f0eccce Fix cron worker minute cadence (#324)
Replace drifting cron loops with persistent cadence-tracked workers and CI-verifiable Compose wiring.
2026-07-27 18:16:09 +02:00
Jeppe B 6e24718c1f Isolate backend Docker jobs on ephemeral runners (#322)
## Summary
- Run Docker-producing PHP, Edge Broker, and Edge Gateway jobs on
ephemeral Ubuntu workspaces for both PR and push events.
- Keep the non-Docker Edge Agent and Release Manager gate on the trusted
backend pool.
- Preserve the explicit system-socket selection and fail-closed Docker
access check.

## Root cause
Exact-master run 29942825210 got past Docker access, then later jobs
failed during checkout because an earlier container left
`services/php/logs/error.log` root-owned in the reused self-hosted
workspace. This is workspace contamination, not a product-test failure.
The shallow frontend repair pattern would miss the depth-4 file and
would accumulate undeletable trash directories.

## Verification
- Workflow YAML parse passed.
- `git diff --check` passed.
- PR CI must be green; after merge, exact-master Required CI and the
non-skipped Release Manager gate are mandatory.
2026-07-22 19:47:31 +02:00
Jeppe B 9b481e0957 Use the system Docker socket in backend CI (#321)
## Summary
- Keep untrusted PRs on ephemeral Ubuntu runners and trusted pushes on
the local backend pool.
- Force Docker-dependent jobs to the working system socket instead of
the unavailable default rootless context.
- Preserve the fail-closed Docker access check and never chmod the
socket.

## Evidence
- Exact master run 29942048689 failed before tests because plain Docker
commands resolved to `/run/user/1000/docker.sock`.
- Backend listener processes already have docker-group membership;
`/var/run/docker.sock` is root:docker 0660.
- `DOCKER_HOST=unix:///var/run/docker.sock docker version` succeeds
locally with server 29.3.1.
- Workflow YAML parse and `git diff --check` pass.

Exact-master Required CI and Release Manager gate success remain
mandatory after merge.
2026-07-22 19:33:13 +02:00
Jeppe B 0060fb45ca Add in-app account deletion (#319)
## Summary
- Add self-service deletion for the authenticated customer or subuser
identity only.
- Preserve shared customer grants, reset keys, bookings, order bookings,
vehicles, invoices, and legally required history.
- Require password/TOTP or a fresh deletion-specific, five-minute,
single-use WebAuthn assertion.
- Reject support impersonation and expired legacy plain-session tokens.
- Use durable database throttling, transactional request processing, a
durable outbox, and terminal `manual_review` state.
- Keep API and worker default-off behind separate
`account_deletion.api_enabled` and `account_deletion.worker_enabled`
module-config flags.

## Safe rollout
1. Keep both flags disabled.
2. Run `php scripts/account-deletion-schema.php check`.
3. If needed, run `php scripts/account-deletion-schema.php apply --yes`,
then rerun `check` until `ready:true`.
4. Deploy the frontend companion PR while the API remains disabled.
5. Enable `api_enabled` for a controlled canary; verify password and
passwordless request flows plus immediate authentication revocation.
6. Inspect queued request/outbox state, then enable `worker_enabled`.
7. Verify anonymization, preserved tenant/history data, outbox delivery,
retries, and manual-review behavior before broad rollout.

## Verification
- Account deletion unit tests: 2 passed, 43 assertions.
- PHP lint, both OpenAPI YAML parses, runtime-DDL scan,
destructive-scope scan, and `git diff --check` passed.
- Full API/unit/integration evidence is required from exact-head CI;
local Docker is unavailable and shared-vendor tests were explicitly
discarded.

## Security notes
- Schema mutation is CLI-only; web and cron paths perform read-only
readiness checks.
- Runtime behavior fails closed when schema/config/throttle/delivery
prerequisites are unavailable.
2026-07-22 19:22:17 +02:00
Jeppe B 34cf804d75 Harden CI runner and release gate security (#320)
## Summary

- run untrusted pull-request jobs on ephemeral `ubuntu-24.04` runners
- reserve the local backend runner pool for trusted branch pushes
- remove world-writable Docker-socket fallbacks
- pin core GitHub Actions and disable checkout credential persistence
- remove the release-manager PHP parse-error fail-open path

## Why

Pull-request code previously ran on persistent self-hosted runners with
Docker access, and CI contained permission weakening and a release-gate
break-glass success path. Those behaviors were unsafe for autonomous
intake.

## Validation

- workflow YAML parsed
- backend AI workflow outputs are in sync
- pinned action SHAs match the current v4 tags
- `git diff --check`

## Risk and activation

This is an R4 CI/release-policy change. Keep the PR draft for human
review and let required CI prove the hosted-runner path before merge.
2026-07-22 19:05:55 +02:00
Jeppe B 677d4700b0 Enforce customer product restrictions for order bookings (#318)
## What changed

- validate every normalized order-booking item against active customer
product rules before reservation and persistence
- return a structured HTTP 400 response containing the rejected product
and matching rule metadata
- document the rejection response in both OpenAPI specifications
- add API coverage for restricted base products, restricted add-ons, and
allowed neighboring products

## Why

Frontend rule guidance alone cannot prevent stale or crafted requests
from persisting restricted booking products. The booking write boundary
must enforce the same customer rules.

## Validation

- full backend API suite
- focused order-booking API coverage
- PHP syntax checks
- OpenAPI and diff checks

## Related frontend PR

The coordinated frontend PR provides fail-closed selection, recovery,
and responsive booking-page behavior.
2026-07-20 14:09:20 +02:00
Jeppe B abde54c898 Allow Capacitor iOS API origin (#317)
Allow the exact Capacitor iOS WebView origin through credentialed CORS while strictly validating request-origin syntax.
2026-07-20 13:53:55 +02:00
Jeppe B b177347bf5 Ignore PHPUnit result cache (#316)
Ignore the generated PHPUnit cache directory and remove its volatile test-results file from Git tracking so test runs no longer dirty protected branch checkouts.
2026-07-20 08:49:27 +02:00
Jeppe B 32a5b99204 Resolve remaining backend full-scan Qodana findings (#315)
Fix the two High findings exposed by the first full master Qodana scan after the broader remediation.
2026-07-17 06:24:47 +02:00
Jeppe B 2a6a86c9c3 Resolve backend Qodana critical and high findings (#314)
Resolve recommended-profile Critical and High findings, retain narrow analyzer exceptions, and update the edge-broker WebSocket dependency to a non-vulnerable release.
2026-07-17 05:44:16 +02:00
Jeppe B 6566027746 Configure advisory Qodana analysis (#313)
## Summary

- configure advisory Qodana PHP 2026.1 analysis for trusted pull
requests and master, beta, canary, and internal branch scans
- install both Composer projects and the edge-agent/edge-broker Node
dependencies before analysis
- exclude generated, vendor, build, cache, legacy-test, and local-agent
trees
- keep Quick Fixes, SARIF artifacts, baselines, thresholds, and
required-check enforcement disabled during calibration

## Safety

- fails closed when QODANA_TOKEN is absent
- skips draft, fork, and Dependabot pull requests
- uses least-privilege GitHub permissions and immutable action SHAs
- uploads findings to the dedicated api Qodana Cloud project

## Validation

- actionlint 1.7.12
- SchemaStore qodana-1.0 validation
- bootstrap shell syntax and lockfile structure checks
- immutable action tag verification
- git diff --check
- independent review completed with no findings

## Live verification

- [PR-mode
scan](https://github.com/copenhagentruckwash/api/actions/runs/29494056175)
completed successfully with 0 changed-file problems, 439 inspections,
and a passed license audit ([Qodana
report](https://qodana.cloud/projects/P2nXd/reports/LJv98e))
- [full branch
scan](https://github.com/copenhagentruckwash/api/actions/runs/29495399119)
completed successfully and uploaded 8,248 current findings across 725
files, 439 inspections, and a passed license audit to the dedicated api
project ([Qodana
report](https://qodana.cloud/projects/P2nXd/reports/qJMOxX))
- the initial debt remains advisory; baseline and required-check
enforcement are intentionally deferred until findings are triaged
2026-07-16 14:35:59 +02:00
Jeppe B 511605b619 Verify API master branch protection (#312)
Record the live ruleset and complete the protected-path canary.
2026-07-16 12:55:18 +02:00
Jeppe B c2abf17cd7 Prepare API default branch protection (#311)
Add a stable Required CI gate, branch-protection desired state, and publishing runbook.
2026-07-16 12:38:01 +02:00
Jeppe B fefe18a719 Fix legacy customer attribute session query 2026-07-16 12:20:20 +02:00
Jeppe B 9b2d5d5291 Fix customer restriction CI regressions 2026-07-16 12:06:42 +02:00
Jeppe B e1fb79d9b6 Add customer rule product restrictions 2026-07-16 11:50:52 +02:00
Jeppe B 879dfcf79a Improve invoice period data and POS add-on validation 2026-07-15 17:04:52 +02:00
Jeppe B 0feb705059 Support collected invoice economic PDF downloads 2026-07-14 15:39:39 +02:00
Jeppe Bundgaard 69b3bf83c4 Allow custom subuser grant permission writes 2026-07-13 22:30:18 +02:00
Jeppe Bundgaard 5bac316e4b Add loading screen for session confirmation and enhance vehicle management labels 2026-07-13 22:11:54 +02:00
Jeppe Bundgaard 233133365d Guard chauffeur vehicles on legacy schemas 2026-07-13 20:03:50 +02:00
Jeppe Bundgaard 026492c3bd Queue cron runs through workers 2026-07-13 20:03:39 +02:00
Jeppe Bundgaard 403f93e62c Fix self-serve schema compatibility in CI 2026-07-13 15:36:03 +02:00
Jeppe Bundgaard 582edd3e6c Implement subuser verification and invoice/self-serve API fixes 2026-07-13 15:11:49 +02:00
Jeppe Bundgaard a4fafaf7fb Enhance subuser management functionality and improve UI responsiveness 2026-07-13 10:25:26 +02:00
Jeppe Bundgaard 327e9cf817 Update self-serve permissions and enhance UI components for customer interactions 2026-07-13 10:21:22 +02:00
Jeppe Bundgaard 012e5366ba Add system status displays for Minio and Redis, and enhance backup configuration 2026-07-13 10:08:00 +02:00
Jeppe B fa1ade555f Fix self-serve cron registry test
Fix self-serve cron registry test
2026-07-09 11:36:11 +02:00
Jeppe B 7a1c444df0 Activate self-serve opening relays
Activate self-serve opening relays
2026-07-09 11:27:32 +02:00
Jeppe Bundgaard 6a00f023b1 Refactor cron scheduling 2026-07-09 11:04:13 +02:00
Jeppe B 8aefbd8fb3 Guard wash subscription distribution SQL
Guard the wash subscription distribution query after invoice-inclusion filtering removes all candidate orders, preventing an empty IN () clause on the invoicing distribution endpoint.

Verified with focused syntax, Pest, PHPStan, and invoicing unit-suite checks.
2026-07-09 10:24:02 +02:00
Jeppe B a7181a4ab2 Fix edge gateway relay binding reactivation
Reactivate existing relay binding rows when a gateway/relay pair is re-added after soft deletion, avoiding duplicate uniq_edge_gateway_binding inserts. Add regression coverage for the reactivation path.
2026-07-08 18:46:33 +02:00
Jeppe Bundgaard fc6c76ad1b Add customer rule cleanup for spotfree addon products with preview and apply functionality 2026-07-08 14:40:46 +02:00
Jeppe Bundgaard 6a694f92cc Add bulk action preview and apply endpoints for collected invoices 2026-07-08 13:46:31 +02:00
Jeppe Bundgaard b7a2dc04d7 Merge remote-tracking branch 'origin/master' 2026-07-08 13:03:49 +02:00
Jeppe Bundgaard 870b88e707 Add assertError method to ApiResponse and enhance CI test failure handling 2026-07-08 13:03:33 +02:00
Jeppe B e14cddc1fb Fix API suite regressions 2026-07-08 12:54:26 +02:00
Jeppe B 31887fa8c9 Cover API CI skip prevention wiring 2026-07-08 12:32:27 +02:00
Jeppe B eac83b18a0 Preflight required extensions for API CI 2026-07-08 12:32:19 +02:00
Jeppe B 3817a37021 Make API CI fail on skipped bootstrap 2026-07-08 12:31:40 +02:00
Jeppe Bundgaard 940a3e5e9b Refactor superuser grant selection logic to prioritize the most recently updated grant 2026-07-08 12:16:22 +02:00
Jeppe Bundgaard 3221223865 Enhance vehicle summary retrieval for customers and update OpenAPI schema for subuser management grants 2026-07-08 12:15:18 +02:00
Jeppe Bundgaard b51006d9d1 Refactor subuser management payload and enhance grant deduplication logic 2026-07-08 12:13:12 +02:00
Jeppe Bundgaard 6b7592921d Add subuser permission templates service and related tests 2026-07-08 11:49:40 +02:00
Jeppe Bundgaard f26a427510 Add test for customer booking sessions to retrieve single-product final pricing with context 2026-07-08 10:41:56 +02:00
Jeppe Bundgaard 6de747252f Add department pricing normalization for order bookings 2026-07-08 10:35:59 +02:00
Jeppe Bundgaard ff225ff5e7 Refactor getTargetItems method to include customer and department parameters for improved item normalization 2026-07-08 10:35:39 +02:00
Jeppe Bundgaard dcef993f12 Implement department wash count service and refactor related reporting functions 2026-07-08 10:35:24 +02:00
Jeppe Bundgaard 23aca449f7 Add department wash count service for tracking wash counts and transaction summaries 2026-07-08 10:25:03 +02:00
Jeppe Bundgaard 31b5ba136a Add user-scoped routes for managing subusers and their grants 2026-07-08 10:24:50 +02:00
Jeppe Bundgaard f0b5479f30 Enhance department pricing functionality and improve related tests 2026-07-08 09:35:40 +02:00
Jeppe B b77efc538a Fix backend test gates and department product access 2026-07-07 22:16:37 +02:00
Jeppe Bundgaard 10d1eb5bac Refactor system search cache handling and update OpenAPI specifications for max results 2026-07-07 18:59:07 +02:00
Jeppe Bundgaard 084435e9b8 Add customer pricing permissions and update related tests 2026-07-07 17:58:39 +02:00
Jeppe Bundgaard 172a21c517 Implement department-specific customer pricing functionality 2026-07-07 17:27:56 +02:00
Jeppe Bundgaard c24428e4c7 Update ProductsApiTest to use 'user' session for department pricing tests 2026-07-07 14:38:41 +02:00
Jeppe Bundgaard bf1d6a583e Allow customers to read own attributes 2026-07-07 13:05:24 +02:00
Jeppe Bundgaard 08ac16e665 Fix customer wash certificate access and emails 2026-07-07 12:37:19 +02:00
Jeppe Bundgaard 79185a3c76 Enhance product listing for customer booking sessions: allow access to booking-visible products without requiring additional permissions 2026-07-07 12:23:56 +02:00
Jeppe Bundgaard 3a730e3507 Merge limited backoffice role permission templates 2026-07-07 03:27:39 +02:00
Jeppe Bundgaard ce43c4e064 Expose limited backoffice role permission templates 2026-07-07 03:23:42 +02:00
Jeppe B 579ddcf510 Merge pull request #307 from copenhagentruckwash/copilot/update-limited-backoffice-roles
Fix limited-backoffice role permissions and enforce department access on order mutations
2026-07-07 02:53:06 +02:00
Jeppe Bundgaard 0b342a7780 Align limited backoffice permission cap tests 2026-07-07 02:47:49 +02:00
copilot-swe-agent[bot] 57bcbaf72a Fix 11 failing API tests across 4 files 2026-07-07 00:31:43 +00:00
copilot-swe-agent[bot] d9fbba3130 Return 404 when order item not found in DELETE /order/items 2026-07-06 23:19:00 +00:00
copilot-swe-agent[bot] e4465d9d91 Improve DELETE /order/items: clearer error message, 404 when order not found 2026-07-06 23:17:50 +00:00
copilot-swe-agent[bot] 734cd13c87 Handle prepared statement failure with error response in DELETE /order/items 2026-07-06 23:16:55 +00:00
copilot-swe-agent[bot] d0f94ac549 Use prepared statements for all new DB queries in tests and route 2026-07-06 23:15:59 +00:00
copilot-swe-agent[bot] 1d25cbe21c Fix SQL injection concerns: use prepared statements in orderItemsRoute and tests 2026-07-06 23:14:34 +00:00
copilot-swe-agent[bot] 53d0636193 Fix limited-backoffice permissions and add department access restrictions 2026-07-06 23:12:01 +00:00
Jeppe B 04bb26f1b0 Merge pull request #306 from copenhagentruckwash/copilot/fix-php-api-job-failure
Fix two failing LimitedBackofficeApiTest assertions
2026-07-07 00:44:53 +02:00
copilot-swe-agent[bot] df0d4783d0 Remove search_customers and search_vehicles from raw-permission exclusion list 2026-07-06 22:37:40 +00:00
copilot-swe-agent[bot] 39c06ceab6 Address code review: restore filter passthrough and add clarifying comments 2026-07-06 22:28:45 +00:00
copilot-swe-agent[bot] 7dd428d18e Fix two failing LimitedBackofficeApiTest tests
Fix 1: Remove add_order_attachments and download_order_attachments from
the raw-permissions exclusion check in the test. These strings are valid
capability names that legitimately appear in the /limited-backoffice/roles
response, so including them in the 'should not contain' list caused a
false failure.

Fix 2: Update limitedBackofficeEmployeeListMode() in usersRoute.php to
exclude active limited backoffice employees when include_limited_backoffice_employees
is not set and the customer_number:0 filter is in use. Previously the
method returned additional_where:null in this case, so limited employees
were included in the result set alongside regular backoffice employees.
2026-07-06 22:27:45 +00:00
copilot-swe-agent[bot] 0103a40156 Initial plan 2026-07-06 22:19:38 +00:00
Jeppe B e208b1b2a4 Merge pull request #305 from copenhagentruckwash/codex/limited-backoffice-employee-migration
Add limited backoffice employee migration
2026-07-07 00:14:25 +02:00
Jeppe Bundgaard 6b4b55cb62 Add limited backoffice employee migration 2026-07-07 00:10:22 +02:00
Jeppe Bundgaard 0cca597fdc Fix XLVask usage import dates
Fix XLVask usage-log import metadata and period-scoped Selvvask automation.
2026-07-06 23:49:28 +02:00
Jeppe B 709c6acbba Fix product null department permissions
Treats null-like optional product query params as omitted and avoids department_access_0 permission checks.
2026-07-06 20:14:45 +02:00
Jeppe Bundgaard ed2736e528 Fix product null department permissions 2026-07-06 19:56:07 +02:00
Jeppe B c7f5c73a9e Merge pull request #303 from copenhagentruckwash/codex/daily-report-product-targets-api
[codex] Add daily report product target API
2026-07-06 19:35:37 +02:00
Jeppe Bundgaard c10af48954 Add daily report product target API 2026-07-06 18:52:24 +02:00
Jeppe Bundgaard 7ac5c5585b Add limited backoffice employee QR login links 2026-07-06 17:32:58 +02:00
Jeppe B 8544ce0a18 Merge pull request #297 from copenhagentruckwash/codex/customer-product-fixed-price-overrides
Add customer product fixed price overrides
2026-07-06 17:23:06 +02:00
Jeppe Bundgaard 614715822f Fix backend merge fallout for booking and limited employees 2026-07-06 17:16:22 +02:00
Jeppe Bundgaard 1da02e2486 Fix limited backoffice price save reset 2026-07-06 17:13:04 +02:00
Jeppe B 742b15116d Merge pull request #295 from copenhagentruckwash/fix/economic-ean-transfer
Fix e-conomic EAN customer transfer
2026-07-06 17:00:57 +02:00
Jeppe Bundgaard e0ae74bdc2 Merge remote-tracking branch 'origin/master' into codex/customer-product-fixed-price-overrides 2026-07-06 17:00:52 +02:00
Jeppe Bundgaard 08dc803b3e Make limited backoffice employees regular employees 2026-07-06 16:59:32 +02:00
Jeppe Bundgaard 248a901f24 Merge master into fixed price override branch 2026-07-06 16:54:01 +02:00
Jeppe Bundgaard 8bbdf9daf5 Require booking add node for subuser booking creation 2026-07-06 16:48:07 +02:00
Jeppe B c089186046 Merge pull request #302 from copenhagentruckwash/codex/customer-orderbooking-create-without-permission
Allow customer order booking creation without booking permission
2026-07-06 16:39:17 +02:00
Jeppe Bundgaard 2ae1fc3fcf Allow customer order booking creation without booking permission 2026-07-06 16:33:31 +02:00
Jeppe Bundgaard d9eacf6f84 Deduplicate limited backoffice price products 2026-07-06 16:28:08 +02:00
Jeppe B f262047476 Merge pull request #300 from copenhagentruckwash/codex/scoped-monthly-split-api
Scope monthly invoice split API
2026-07-06 16:01:54 +02:00
Jeppe B b8390ac0d3 Merge pull request #298 from copenhagentruckwash/codex/only-tankcleaning-order-enforcement
Enforce only tankcleaning order products
2026-07-06 16:01:40 +02:00
Jeppe B 0d4a5470e5 Add superuser department overview API (#301)
Merge backend API for the superuser department overview.
2026-07-06 16:01:04 +02:00
Jeppe B 845ca6e48e Merge pull request #290 from copenhagentruckwash/codex/custom-pricing-only-departments
Add custom-only department pricing enforcement
2026-07-06 15:31:27 +02:00
Jeppe Bundgaard 1cda2a81aa Merge remote-tracking branch 'origin/master' into codex/custom-pricing-only-departments
# Conflicts:
#	services/nginx/app/tests/Api/OrderItemsApiTest.php
2026-07-06 15:21:31 +02:00
Jeppe BandJeppe Bundgaard 8e46ce1b04 [codex] Allow error reports without screenshots (#299)
* Allow error reports without screenshots

* Stabilize edge gateway shell transcript smoke

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 14:27:47 +02:00
Jeppe Bundgaard 9f797bf6b8 Add opening hours table to API test schema 2026-07-06 14:27:39 +02:00
Jeppe Bundgaard d345db927f Add daily report table to API test schema 2026-07-06 14:16:41 +02:00
Jeppe BandJeppe Bundgaard 11c2a1b72e Block restricted customer order items (#296)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 14:06:29 +02:00
Jeppe Bundgaard eca7a81f9d Add superuser department overview API 2026-07-06 13:59:40 +02:00
Jeppe Bundgaard 62f2c80dda Scope monthly invoice split endpoint 2026-07-06 13:37:31 +02:00
Jeppe BandJeppe Bundgaard 6f3d7e0f7d Add limited backoffice employee contact fields (#294)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 13:14:06 +02:00
Jeppe Bundgaard 430c90cbca Enforce only tankcleaning order products 2026-07-06 12:53:42 +02:00
Jeppe Bundgaard f02dfd8c9c Add customer product fixed price overrides 2026-07-06 12:52:33 +02:00
Jeppe B a8fba73d99 Add limited backoffice role permission details (#291)
Adds grouped safe permission metadata for limited backoffice role presets.
2026-07-06 12:24:54 +02:00
Jeppe B 669759461d Merge pull request #293 from copenhagentruckwash/codex/economic-collected-invoice-transfer-speed
Optimize collected e-conomic invoice transfers
2026-07-06 11:49:47 +02:00
Jeppe Bundgaard db1b9a2c96 Fix e-conomic EAN customer transfer 2026-07-06 11:34:23 +02:00
Jeppe Bundgaard 38814545c4 Optimize collected e-conomic invoice transfers 2026-07-06 11:31:22 +02:00
Jeppe B 94c3654240 Merge pull request #292 from copenhagentruckwash/fix/limited-backoffice-price-save
[codex] Fix limited backoffice price saves on legacy schema
2026-07-06 11:03:44 +02:00
Jeppe Bundgaard 9fa249cc11 Fix limited backoffice price saves on legacy schema 2026-07-06 10:53:18 +02:00
Jeppe Bundgaard 215c8d0fbb Add limited backoffice role permission details 2026-07-06 10:38:51 +02:00
Jeppe Bundgaard 84dec4c0a2 Stabilize custom pricing API fixture 2026-07-06 10:34:57 +02:00
Jeppe Bundgaard d47ea1d659 Add custom-only department pricing enforcement 2026-07-06 10:15:11 +02:00
Jeppe BandJeppe Bundgaard 3f41eebdf6 Default vehicle subscriptions to false (#288)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-06 09:21:31 +02:00
Jeppe B 64e0b2444b Merge pull request #289 from copenhagentruckwash/fix/limited-backoffice-api-compat
[codex] Fix limited backoffice schema compatibility
2026-07-06 09:20:56 +02:00
Jeppe Bundgaard 18fede78f8 Fix limited backoffice schema compatibility 2026-07-06 09:15:55 +02:00
Jeppe Bundgaard 243d68ab59 Fix edge gateway relay command draining 2026-07-02 12:41:34 +02:00
Jeppe Bundgaard 62c1393f62 Refactor product data handling and improve error management in superuser products layout 2026-07-02 11:48:21 +02:00
Jeppe B f0a8299133 Merge pull request #287 from copenhagentruckwash/fix/api-ci-limited-backoffice
Fix API CI failures
2026-07-02 11:03:12 +02:00
Jeppe Bundgaard e36f6da926 Run Qodana on backend runner pool 2026-07-02 10:57:47 +02:00
Jeppe Bundgaard 4a8c2a9fd9 Avoid npm cache hang in edge CI 2026-07-02 10:50:25 +02:00
Jeppe Bundgaard 248b2e4eca Fix API CI failures 2026-07-02 10:42:13 +02:00
Jeppe Bundgaard 1d1ebd2176 Add limited backoffice functionality with employee management and department pricing 2026-07-01 16:37:32 +02:00
Jeppe Bundgaard 4252f9a42b Stabilize edge gateway API CI 2026-07-01 14:01:50 +02:00
Jeppe Bundgaard 4fdeedab45 Handle invalid edge installer tokens 2026-07-01 13:41:20 +02:00
Jeppe Bundgaard 866a5be126 Refactor subuser permissions and enhance artifact management 2026-07-01 13:18:27 +02:00
Jeppe Bundgaard 11d39af934 Normalize edge broker URL updates 2026-07-01 11:53:01 +02:00
Jeppe Bundgaard f706531534 Stabilize gateway E2E broker config 2026-07-01 11:44:36 +02:00
Jeppe Bundgaard a839eac4c1 Allow HTTP operation completion in gateway E2E 2026-07-01 11:34:39 +02:00
Jeppe Bundgaard 581d28e9ce Stabilize edge gateway CI assertions 2026-07-01 11:25:01 +02:00
Jeppe Bundgaard 2ba39b8174 Fix edge gateway CI broker path 2026-07-01 11:17:01 +02:00
Jeppe Bundgaard 6af55a44c9 Fix API CI broker and transport fixtures 2026-07-01 11:09:38 +02:00
Jeppe Bundgaard 24badc39d7 Keep broker path in API CI public URL 2026-07-01 11:02:26 +02:00
Jeppe Bundgaard f5e0baaab6 Use explicit Docker subnets in API CI 2026-07-01 11:00:13 +02:00
Jeppe Bundgaard 178c84ba60 Use direct broker port for API CI smoke 2026-07-01 10:57:39 +02:00
Jeppe Bundgaard 713d40a876 Prune stale Docker networks in API CI 2026-07-01 10:53:25 +02:00
Jeppe Bundgaard 9db1964038 Fix edge agent CI setup 2026-07-01 10:51:14 +02:00
Jeppe Bundgaard 1ca42055b0 Use docker-capable API runners 2026-07-01 10:42:30 +02:00
Jeppe Bundgaard c04bda7368 Normalize API runner Docker access 2026-07-01 10:38:49 +02:00
Jeppe Bundgaard 1f47843699 Fix edge expected state CI coverage 2026-07-01 10:33:56 +02:00
Jeppe Bundgaard dca738db82 Resolve edge schema DB config from env 2026-07-01 10:28:40 +02:00
Jeppe Bundgaard 57f364ad0f Use backend runners for API tests 2026-07-01 10:21:08 +02:00
Jeppe Bundgaard a826153bb5 Use namespaced DB helper in schema bootstrap 2026-07-01 10:04:28 +02:00
Jeppe Bundgaard 72bd22a707 Load DB helper for edge gateway schema bootstrap 2026-07-01 09:57:34 +02:00
Jeppe Bundgaard cc73d80dbc Use PDO in edge gateway schema bootstrap 2026-07-01 09:46:35 +02:00
Jeppe Bundgaard f0a5b15442 Add edge agent expected relay state API 2026-07-01 09:40:54 +02:00
Jeppe Bundgaard eefe5630f4 Honor staged edge gateway update windows 2026-06-30 17:23:04 +02:00
Jeppe Bundgaard b0ea771e6a Add handling for self-serve machine signals in edge agent 2026-06-30 16:19:43 +02:00
Jeppe Bundgaard eb21405a3d Add outbox replay handling with configurable limits and timeouts 2026-06-30 16:16:08 +02:00
Jeppe Bundgaard 8ea10ef808 Implement relay toggle handling with configurable timers and device generation resolution 2026-06-30 15:53:29 +02:00
Jeppe Bundgaard ac7da807bd Route local lane gate opens through edge bindings 2026-06-30 13:52:12 +02:00
Jeppe Bundgaard 3eafc597c6 Use edge bindings for lane hardware batch relays 2026-06-30 13:28:03 +02:00
Jeppe Bundgaard f7a4126718 Fix router matching for underscored route params 2026-06-30 13:10:06 +02:00
Jeppe Bundgaard c6cf953ede Fix relay batch gateway binding resolution 2026-06-30 12:52:53 +02:00
Jeppe Bundgaard d902202fe9 Fallback relay commands after broker dispatch failures 2026-06-30 12:32:10 +02:00
Jeppe Bundgaard acc80920f9 Preserve LAN worker auth after agent config reload 2026-06-30 11:39:19 +02:00
Jeppe Bundgaard 53246af629 Update EdgeGatewayManagerUrlTest to improve installation phase checks and remove obsolete heartbeat expectations 2026-06-30 11:18:00 +02:00
Jeppe Bundgaard bd87e94472 Normalize line endings in install scripts and enhance service stop commands for robustness 2026-06-30 11:06:33 +02:00
Jeppe Bundgaard 7ccbb68ffa Refactor Dockerfiles to streamline PHP extension checks and remove unnecessary installations 2026-06-30 10:58:08 +02:00
Jeppe Bundgaard 4a7fc7c534 Refactor Dockerfiles to conditionally install curl extension if not already present 2026-06-30 10:35:30 +02:00
Jeppe Bundgaard f4343ae114 Enhance stopping wash functionality with local storage management and restoration logic 2026-06-30 10:16:13 +02:00
Jeppe Bundgaard 0da02dfeb5 Add batch processing for lane hardware commands and status retrieval 2026-06-30 09:30:43 +02:00
Jeppe Bundgaard 5f13242cfa Fix eligibility check by ensuring user is not null in lane access condition 2026-06-29 15:35:26 +02:00
Jeppe Bundgaard b492292642 Add machine wash configuration and update self-serve lane service checks 2026-06-29 15:22:22 +02:00
Jeppe Bundgaard ce8ba88b16 Add machine wash configuration and controls to self-serve module 2026-06-29 15:15:29 +02:00
Jeppe Bundgaard 02b6df5e3b Refactor authentication handling and permission checks in self-serve routes 2026-06-29 14:53:58 +02:00
Jeppe Bundgaard 6cc4f2759d Enhance self-serve functionality by adding lane availability checks and updating response data 2026-06-29 14:25:51 +02:00
Jeppe Bundgaard 3ea92be722 Add endpoint and functionality to test Slack internal department goal progress webhook 2026-06-29 11:32:47 +02:00
Jeppe Bundgaard 42f8ae0c47 Refactor input value handling and improve validation in ObjectsGlobal component 2026-06-29 11:08:50 +02:00
Jeppe Bundgaard 148b575767 Refactor input value handling and improve validation in ObjectsGlobal component 2026-06-29 08:28:00 +02:00
Jeppe B 605efacece Merge pull request #284 from copenhagentruckwash/codex/optimize-scanner-lpr-backend
[codex] optimize scanner LPR backend
2026-06-12 22:08:35 +02:00
Jeppe Bundgaard 1ccd7749d0 optimize scanner lpr backend 2026-06-12 21:42:36 +02:00
Jeppe B b3ba3c8de5 Merge pull request #283 from copenhagentruckwash/fix/pwa-selfserve-stop-latency
[codex] Reduce self-serve latency and add PHP-FPM workers
2026-06-12 13:15:28 +02:00
Jeppe Bundgaard 4a65b669bd Fix CI FPM worker config test 2026-06-12 12:35:06 +02:00
Jeppe Bundgaard aaec443140 Configure multiple PHP-FPM workers 2026-06-12 12:27:27 +02:00
Jeppe Bundgaard 3cdf1571c5 Avoid duplicate self-serve stop relay cleanup 2026-06-12 11:48:51 +02:00
Jeppe Bundgaard 36b934e835 Increase password reset token validity to 72 hours and update related email message 2026-06-11 21:45:22 +02:00
Jeppe B 5027d0c919 Merge pull request #282 from copenhagentruckwash/codex/register-cvr-welcome-email-fix
[codex] Fix register CVR welcome email rendering
2026-06-11 21:22:28 +02:00
Jeppe Bundgaard f0baadd59f Register welcome email legacy test 2026-06-11 21:08:28 +02:00
Jeppe Bundgaard 19cacebaa1 Fix register CVR welcome email rendering 2026-06-11 21:01:34 +02:00
Jeppe Bundgaard 4d9d61455f Refactor company phone number registration error handling and enhance CVR lookup test cases 2026-06-11 20:25:16 +02:00
Jeppe Bundgaard fc87b3a8aa Improve CVR lookup error handling and unify phone number registration error messages 2026-06-11 20:17:48 +02:00
Jeppe Bundgaard 8e0936001d Fix company phone number registration error messages for clarity 2026-06-11 20:03:55 +02:00
Jeppe B af8968a87e Merge pull request #281 from copenhagentruckwash/codex/customer-registration-notifications
Add Slack customer registration webhook test endpoint
2026-06-11 15:18:07 +02:00
Jeppe Bundgaard e6a18ce5d8 Add Slack customer registration webhook test endpoint 2026-06-11 15:06:31 +02:00
Jeppe B b5c24ef80a Merge pull request #280 from copenhagentruckwash/fix/self-serve-path-outcome-case-limit
Fix self-serve path outcome case limit
2026-06-11 14:57:16 +02:00
Jeppe Bundgaard df7153a5ba Fix self-serve path outcome case limit 2026-06-11 14:46:27 +02:00
Jeppe Bundgaard d06c78119b Fix customer registration duplicate recovery 2026-06-11 12:04:56 +02:00
Jeppe B bdb1a0074b Merge pull request #279 from copenhagentruckwash/fix/self-serve-customer-property-gates
Allow customers to open property gates for active washes
2026-06-10 22:00:06 +02:00
Jeppe Bundgaard c0de0e9d6b Allow customers to open property gates for active washes 2026-06-10 21:00:18 +02:00
Jeppe B 574b263a54 Merge pull request #278 from copenhagentruckwash/fix/self-serve-start-wash-type
Honor wash type in self-serve lane start
2026-06-10 20:07:46 +02:00
Jeppe B 36ff5bb438 Merge pull request #277 from copenhagentruckwash/fix-completion-confirmation-route
Add order booking completion confirmation resend route
2026-06-10 20:07:30 +02:00
Jeppe Bundgaard 1beca924fc Fallback composer installs to source in CI 2026-06-10 19:47:17 +02:00
Jeppe B d605eca574 Fallback composer installs to source in CI 2026-06-10 19:22:52 +02:00
Jeppe Bundgaard cea469c95a Honor wash type in self-serve lane start 2026-06-10 19:18:19 +02:00
Jeppe B 7e85c74e60 Retry composer installs in CI 2026-06-10 19:12:12 +02:00
Jeppe B 67d62eff70 Sync fake email deliveries across API tests 2026-06-10 18:53:13 +02:00
Jeppe B 8ebbd52a99 Normalize attachment object type lookups 2026-06-10 18:40:34 +02:00
Jeppe B 6d6cc501db Force completion confirmation resend email 2026-06-10 18:33:53 +02:00
Jeppe B ce999afbb3 Fix MinIO local test storage fallback 2026-06-10 18:22:53 +02:00
Jeppe B cb34b030c8 Add order booking completion confirmation resend route 2026-06-10 17:55:57 +02:00
Jeppe Bundgaard e26034dfae Refactor dynamic image export methods to use binary output and improve caching logic 2026-06-09 14:48:46 +02:00
Jeppe Bundgaard 0aad41fd0f Add program picker relay status handling for wash start and update related tests 2026-06-09 14:09:49 +02:00
Jeppe Bundgaard 5c67fe419f Add timeout settings for Shelly cloud HTTP requests and update related tests 2026-06-09 14:03:12 +02:00
Jeppe Bundgaard aca8be51dc Implement move collected invoice to customer functionality with API endpoint and associated tests 2026-06-09 13:12:55 +02:00
Jeppe Bundgaard fb1f0883e1 Add selfserve dynamic image sizing config 2026-06-09 12:46:14 +02:00
Jeppe Bundgaard 6446eb2e36 Test manual wash program picker selection 2026-06-09 12:05:27 +02:00
Jeppe Bundgaard a1224ec2f4 Fix self-serve program picker wash type sync 2026-06-09 11:59:42 +02:00
Jeppe Bundgaard 07441c4ed1 Harden API auto deploy gate 2026-06-08 18:44:25 +02:00
Jeppe Bundgaard cd100f1180 Invalidate cached session payloads on notification preferences update; enhance flag tab filtering logic 2026-06-08 18:40:15 +02:00
Jeppe Bundgaard 4fc66c72b8 Prefer selected Coolify deployment commit 2026-06-08 18:14:45 +02:00
Jeppe Bundgaard a3ea5fee83 Clean up stale Coolify API routes 2026-06-08 18:02:08 +02:00
Jeppe Bundgaard ef8d97c821 Verify API release commit in gateway gate 2026-06-08 17:48:01 +02:00
Jeppe Bundgaard 327a77edf4 Require API gateway release check 2026-06-08 17:07:49 +02:00
Jeppe Bundgaard d8abc8f87d Refactor session relay synchronization logic for improved clarity 2026-06-08 16:59:53 +02:00
Jeppe Bundgaard 325b35beb7 Add session synchronization tests and ensure atomic session closure 2026-06-08 16:53:24 +02:00
Jeppe Bundgaard 49364864d2 Implement session mutation locking and enhance session management methods 2026-06-08 16:49:41 +02:00
Jeppe Bundgaard a19178a042 Add PHPStan and Rector configuration files for static analysis and code quality 2026-06-08 16:33:37 +02:00
Jeppe Bundgaard 91d3332d4e Add Slack customer registration notification functionality 2026-06-08 12:42:03 +02:00
Jeppe Bundgaard bedbf21c29 Add superuser new customer email notification preferences 2026-06-08 12:20:07 +02:00
Jeppe Bundgaard 75c19bcce4 Fix MyWash active summary refresh import 2026-06-04 08:31:51 +02:00
Jeppe Bundgaard 458fe7399d Persist MyWash sessions on start command 2026-06-04 08:18:31 +02:00
Jeppe Bundgaard 2b6a8eedcc Avoid session creation on MyWash summary refresh 2026-06-04 08:07:40 +02:00
Jeppe Bundgaard c0ed107f75 Resolve MyWash services from published config 2026-06-04 07:50:14 +02:00
Jeppe Bundgaard 30dceff0b5 Avoid self-serve preview sessions on eligibility reads 2026-06-04 06:55:59 +02:00
Jeppe Bundgaard 716929bd7b Inject frontend commit SHA into Coolify runtime environment for manifest builds 2026-06-04 00:38:31 +02:00
Jeppe Bundgaard 3d221f3379 Merge remote-tracking branch 'origin/master' 2026-06-03 21:03:27 +02:00
Jeppe B 6f1c160fbb Sync generated Copilot workflow 2026-06-03 20:57:06 +02:00
Jeppe Bundgaard 9694695f00 Sync generated Copilot workflow 2026-06-03 20:56:06 +02:00
Jeppe Bundgaard 1d43221b4d Sync self-serve machine relay session state 2026-06-03 20:35:50 +02:00
Jeppe Bundgaard 33b7c3e51a Widen self-serve task descriptions 2026-06-03 19:06:09 +02:00
Jeppe Bundgaard 1e64bd63b8 Update PHPUnit test results cache with latest version and defect counts 2026-06-03 18:19:45 +02:00
Jeppe B bcbc2481c3 Source self-serve lane products from published config 2026-06-02 19:05:06 +02:00
Jeppe B 8288a1069c Merge pull request #276
coolify-github-runner-management
2026-06-02 17:27:42 +02:00
Jeppe Bundgaard 1b99523366 Enhance self-serve lane functionality with new relay management and configuration updates 2026-06-02 17:27:22 +02:00
Jeppe Bundgaard 6d739cfebc Add configuration for GitHub self-hosted runners 2026-06-02 11:25:00 +02:00
Jeppe Bundgaard 20071166f8 Switch CI to self-hosted runners
Updated all GitHub Actions workflows to use self-hosted runners instead of `ubuntu-latest`. This change ensures better control over the CI environment and aligns with internal infrastructure requirements.
2026-06-02 10:36:22 +02:00
Jeppe Bundgaard c23168afc5 Merge remote-tracking branch 'origin/master' 2026-06-02 10:29:25 +02:00
Jeppe Bundgaard 72704b7806 Add "Get My Active Self-Serve Wash" endpoint and corresponding tests
- Introduced a new `/modules/self-serve/lane/wash/my-active-wash` endpoint to retrieve the authenticated customer's active self-serve wash.
- Implemented authentication and permission checks for secure access.
- Added detailed response handling for various scenarios, including 401, 403, and 404 statuses.
- Extended API documentation and OpenAPI spec to support the new endpoint.
- Updated unit and API tests to validate endpoint functionality and route wiring.
2026-06-02 10:29:15 +02:00
Jeppe B f7485f0767 Merge pull request #273 from copenhagentruckwash/update-self-serve-lane-command-access-logic
Allow customer self-serve lane commands
2026-06-02 10:22:21 +02:00
copilot-swe-agent[bot] 975909b6a1 Resolve merge conflict with master in SelfserveLaneCommandApiTest.php 2026-06-02 08:15:18 +00:00
Jeppe B 1468e43ce2 Merge pull request #272 from copenhagentruckwash/add-endpoint-to-resend-booking-confirmations
Add booking confirmation resend endpoint
2026-06-02 10:12:29 +02:00
Jeppe B ee2af5091c Retry CI docker compose startup 2026-06-02 10:08:01 +02:00
Jeppe B 0672a68e8b Merge pull request #274 from copenhagentruckwash/update-self-serve-lane-command-access-logic-bft43z
Support customer self-serve lane commands with operational/department checks and tests
2026-06-02 10:07:27 +02:00
copilot-swe-agent[bot] c8804bc8dc Merge master into branch resolving self-serve lane command conflicts 2026-06-02 08:01:46 +00:00
Jeppe B b92d1f0bdf Fix self-serve lane command API tests 2026-06-02 09:52:44 +02:00
copilot-swe-agent[bot] cc10371346 Resolve merge conflict with master in moduleSelfServeRoute.php 2026-06-02 07:40:01 +00:00
Jeppe B ac60596218 Fix booking confirmation resend test fixture 2026-06-02 09:36:36 +02:00
Jeppe B f4b9d71d40 Merge pull request #270 from copenhagentruckwash/add-customer-self-serve-module-authorization-checks
Guard customer self-serve command fallback behind global module flag
2026-06-02 09:25:00 +02:00
Jeppe B 77b1c8ec78 Merge pull request #271 from copenhagentruckwash/inspect-command-authorization-for-self-serve-route
Authorize self-serve lane commands by customer scope and operator permission
2026-06-02 09:24:38 +02:00
Jeppe B 01221d8282 Allow customer self-serve lane commands 2026-06-02 09:24:33 +02:00
Jeppe B 47068e6d7e Add booking confirmation resend endpoint 2026-06-02 09:15:34 +02:00
Jeppe B 46bdeded78 Fix self-serve lane command customer authorization 2026-06-02 09:15:24 +02:00
Jeppe B eefa521fc5 Guard customer self-serve commands behind module flag 2026-06-02 09:14:57 +02:00
Jeppe B ec1988715d Merge pull request #269 from copenhagentruckwash/fix-parse-error-in-index.php
Handle Release Manager gate parse-error deadlock
2026-06-02 02:53:54 +02:00
Jeppe B c3fb2e8651 Handle release gate parse-error deadlock 2026-06-02 02:50:02 +02:00
Jeppe B 0fb279fc5f Merge pull request #268 from copenhagentruckwash/investigate-and-fix-failing-tests
Resolve PHP merge conflicts and restore search/autoload behavior
2026-06-02 02:33:35 +02:00
Jeppe B 3e970d9cb9 Seed subuser session cache in API fixtures 2026-06-02 02:29:52 +02:00
Jeppe B 8c10c07cc9 Resolve Caddy replication bootstrap conflict 2026-06-02 02:25:29 +02:00
Jeppe B 18a8513b40 Use namespaced subuser object in API fixtures 2026-06-02 02:21:53 +02:00
Jeppe B 4c77b78c6c Keep self-serve invoice billing customer authoritative 2026-06-02 02:15:27 +02:00
Jeppe B bb249da477 Align API tests with hardened auth and department access 2026-06-02 02:09:14 +02:00
Jeppe B eb16a4e6ce Fix collected invoice queue count expectations 2026-06-02 02:00:35 +02:00
Jeppe B a2e525fa9e Update unit expectations for hardened flows 2026-06-02 01:53:58 +02:00
Jeppe B 0bf19c9d33 Restrict indexed department filters to scoped entities 2026-06-02 01:34:21 +02:00
Jeppe B 5850bfbce7 Keep autoload cache validation test compatible 2026-06-02 01:16:20 +02:00
Jeppe B fd51a5b119 Fix search table argument ordering 2026-06-02 01:07:45 +02:00
Jeppe B 140365c8bb Resolve PHP merge conflict test failures 2026-06-02 00:58:15 +02:00
Jeppe B c9ceac8533 Merge pull request #267 from copenhagentruckwash/fix-permission-checks-for-subuser-endpoints
Require SUBUSERS_LIST permission for GET /subusers to enforce RBAC
2026-06-02 00:42:48 +02:00
copilot-swe-agent[bot] 4266b933f5 Merge remote-tracking branch 'origin/master' into fix-permission-checks-for-subuser-endpoints
# Conflicts:
#	services/nginx/app/routes/subusersRoute.php
2026-06-01 22:41:35 +00:00
Jeppe B 72ec62d042 Merge pull request #259 from copenhagentruckwash/fix-redis-autoload-cache-vulnerability
Harden Redis-backed autoloader against poisoned path inclusion
2026-06-02 00:37:33 +02:00
copilot-swe-agent[bot] d24b50f751 Plan: Resolve merge conflicts in index.php autoloader 2026-06-01 22:36:09 +00:00
Jeppe B 21f5e6d9cf Enforce permission check on subuser list endpoint 2026-06-02 00:35:48 +02:00
Jeppe B 73b91ccec9 Merge pull request #265 from copenhagentruckwash/fix-unauthenticated-bird-voice-webhook
Reinstate authorization check for Bird inbound voice webhook
2026-06-02 00:33:43 +02:00
Jeppe B 0c809a19da Merge pull request #257 from copenhagentruckwash/propose-fix-for-redis-image-cache-vulnerability
Limit Redis dynamic image caching to default variant only
2026-06-02 00:33:27 +02:00
Jeppe B 3a6685c345 Merge pull request #255 from copenhagentruckwash/fix-system-search-authorization-bypass
Enforce department scoping in system search for generic entities
2026-06-02 00:33:02 +02:00
Jeppe B a60983f328 Merge pull request #266 from copenhagentruckwash/propose-fix-for-n8n-ssrf-vulnerability
Harden n8n webhook trigger URL validation against SSRF
2026-06-02 00:32:47 +02:00
Jeppe B e2c2eb21cb Harden n8n webhook trigger URL validation 2026-06-02 00:32:35 +02:00
copilot-swe-agent[bot] 51c619b0c6 Resolve merge conflicts in departmentLanesRoute.php 2026-06-01 22:29:49 +00:00
copilot-swe-agent[bot] fe9daf1bf2 Merge remote-tracking branch 'origin/master' into fix-unauthenticated-bird-voice-webhook
# Conflicts:
#	services/nginx/app/routes/birdVoiceWebhooksRoute.php
2026-06-01 22:27:40 +00:00
copilot-swe-agent[bot] 9c2d7140b4 Merge master into branch to resolve conflicts 2026-06-01 22:27:04 +00:00
copilot-swe-agent[bot] 1505464095 Plan: Resolve merge conflicts with master 2026-06-01 22:25:49 +00:00
Jeppe B cc00fb2aed Reinstate auth on Bird inbound voice webhook 2026-06-02 00:23:58 +02:00
Jeppe B 7f38cf2f7e Merge pull request #264 from copenhagentruckwash/propose-fix-for-ssrf-in-workfeed-api
Restrict Workfeed API base URL to trusted hosts (prevent SSRF)
2026-06-02 00:22:35 +02:00
Jeppe B d281dddbc1 Restrict Workfeed API base URL 2026-06-02 00:22:18 +02:00
Jeppe B 267ec1bed1 Merge pull request #263 from copenhagentruckwash/fix-machine-relay-set-endpoint-vulnerability
Guard machine relay set status
2026-06-02 00:21:49 +02:00
Jeppe B a96f40cf13 Guard machine relay set status 2026-06-02 00:21:32 +02:00
Jeppe B d6190626ce Merge pull request #262 from copenhagentruckwash/fix-cross-tenant-job-data-exposure
Scope economic transfer queue jobs by creator
2026-06-02 00:20:48 +02:00
Jeppe B ce8e6d0dab Scope economic transfer queue jobs by creator 2026-06-02 00:20:30 +02:00
Jeppe B c13c2e2cab Merge pull request #261 from copenhagentruckwash/fix-customer-data-leak-in-wash-endpoint
Restrict in-progress wash details by lane department
2026-06-02 00:20:07 +02:00
Jeppe B 7380bc729b Restrict in-progress wash details by lane department 2026-06-02 00:19:55 +02:00
Jeppe B 434a5049e2 Merge pull request #260 from copenhagentruckwash/fix-unpinned-github-actions-vulnerability
Harden Qodana workflow permissions and pin checkout action
2026-06-02 00:17:44 +02:00
copilot-swe-agent[bot] 6489706231 Merge master and resolve conflicts
- Retained security improvements from master (token detection, cache prep, safe directory)
- Applied security hardening by pinning actions/checkout@v4 to commit SHA 11bd71901bbe5b1630ceea73d27597364c9af683
- Added persist-credentials: false to checkout step to prevent credential exposure
2026-06-01 22:14:21 +00:00
Jeppe B eb66b343ea Harden Qodana workflow permissions and checkout pin 2026-06-02 00:04:10 +02:00
Jeppe B e363f27da9 Harden autoload Redis cache path validation 2026-06-02 00:02:40 +02:00
Jeppe B fbad5f767f Merge pull request #258 from copenhagentruckwash/fix-hardcoded-auth-tokens-in-configuration
Sanitize leaked auth tokens in HTTP test env
2026-06-02 00:02:06 +02:00
Jeppe B 94d9b347bf Sanitize leaked auth tokens in HTTP test env 2026-06-02 00:01:57 +02:00
Jeppe B 76744fd6c3 Limit dynamic image Redis caching to default variant 2026-06-02 00:00:04 +02:00
Jeppe B f5c1a34c29 Merge pull request #256 from copenhagentruckwash/fix-idor-vulnerability-in-economic-v2-endpoints
Prevent IDOR on Economic V2 collected-invoice endpoints
2026-06-01 23:58:41 +02:00
Jeppe B 22ad96bc8e Fix economic v2 invoice endpoint authorization scope 2026-06-01 23:58:30 +02:00
Jeppe B 8a749cffa3 Fix system search department scoping for generic entities 2026-06-01 23:58:03 +02:00
Jeppe B cf5cf8d5eb Merge pull request #254 from copenhagentruckwash/fix-user-search-exposure-vulnerability
Restrict `users` system-search access to prevent PII leakage
2026-06-01 23:57:33 +02:00
Jeppe B eb14b7039b Restrict users system search permissions 2026-06-01 23:57:23 +02:00
Jeppe B f09b1263c1 Merge pull request #253 from copenhagentruckwash/fix-stripe-payment-intent-reuse-issue
Validate Stripe payment intent amount before reuse
2026-06-01 23:54:32 +02:00
Jeppe B 9ec8499d55 Validate Stripe payment intent amount before reuse 2026-06-01 23:54:04 +02:00
Jeppe B 8d40cd6f9a Merge pull request #252 from copenhagentruckwash/fix-complaint-endpoints-department-access-check
Require department access for department daily report complaint routes
2026-06-01 23:53:45 +02:00
Jeppe B ccffad3c7c Fix complaint department authorization 2026-06-01 23:53:36 +02:00
Jeppe B 4697c6b272 Merge pull request #251 from copenhagentruckwash/fix-subuser-management-permission-checks
Enforce own-scope subuser permissions for classic users in managed customer scope
2026-06-01 23:53:06 +02:00
Jeppe B 45e17e196c Fix subuser management permission scope 2026-06-01 23:52:57 +02:00
Jeppe B dcc81cbdc7 Merge pull request #250 from copenhagentruckwash/propose-fix-for-privilege-boundary-regression
Restrict studio simulation to config-version view and prevent auto-creating drafts
2026-06-01 23:52:28 +02:00
Jeppe B c5cb0a3bfe Fix studio simulation draft access 2026-06-01 23:52:18 +02:00
Jeppe B 465f3ed027 Merge pull request #249 from copenhagentruckwash/fix-edge-agent-vulnerability-for-unsigned-artifacts
Require checksums for edge agent updates
2026-06-01 23:50:05 +02:00
Jeppe B 80ff01f04e Require checksums for edge agent updates 2026-06-01 23:49:56 +02:00
Jeppe B f6e4d851d3 Merge pull request #248 from copenhagentruckwash/fix-authenticated-ssrf-in-broker-diagnostics
Prevent SSRF in broker diagnostics by ignoring caller URLs and redacting probe output
2026-06-01 23:49:24 +02:00
Jeppe B cd4e3faea3 Fix broker diagnostics SSRF 2026-06-01 23:49:10 +02:00
Jeppe B 4cb9e68b33 Merge pull request #247 from copenhagentruckwash/fix-information-disclosure-in-websocket-upgrades
Sanitize websocket upgrade error responses
2026-06-01 23:47:09 +02:00
Jeppe B 0e7e79d205 Sanitize websocket upgrade errors 2026-06-01 23:46:59 +02:00
Jeppe B a9ca7b41a7 Merge pull request #246 from copenhagentruckwash/fix-unbounded-relay-timer-vulnerability
Cap self-serve gate relay timers
2026-06-01 23:45:39 +02:00
Jeppe B 3b3ed31bb7 Cap self-serve gate relay timers 2026-06-01 23:45:20 +02:00
Jeppe B cf9d5875ef Merge pull request #242 from copenhagentruckwash/fix-vulnerability-with-self-hosted-runners
Run PR code quality workflow on GitHub-hosted runner
2026-06-01 23:44:29 +02:00
Jeppe B 4730eebdb4 Merge pull request #245 from copenhagentruckwash/fix-internal-ip-address-leakage
Stop exposing relay local IPs by default
2026-06-01 23:44:05 +02:00
Jeppe B b25ce9cb11 Stop exposing relay local IPs by default 2026-06-01 23:43:53 +02:00
Jeppe B fdb98f1399 Merge pull request #244 from copenhagentruckwash/fix-unauthenticated-relay-control-vulnerability
Require authorization for LAN worker relay endpoints
2026-06-01 23:43:34 +02:00
Jeppe B 1dc758a3a3 Require authorization for LAN worker relay endpoints 2026-06-01 23:43:23 +02:00
Jeppe B 032ce93d5e Merge pull request #243 from copenhagentruckwash/fix-hardcoded-service-credentials-in-docker-config
Secure edge gateway service credentials
2026-06-01 23:42:47 +02:00
Jeppe B f8ced3b8f2 Secure edge gateway service credentials 2026-06-01 23:42:34 +02:00
copilot-swe-agent[bot] a81e239de8 Merge master into branch and resolve code_quality.yml comment conflict 2026-06-01 21:42:21 +00:00
Jeppe B 9ea5a62577 Merge pull request #238 from copenhagentruckwash/fix-cache-only-lookup-for-invoice-flags
Normalize invoice-period cache keys and restore DB fallbacks for missing Redis entries
2026-06-01 23:41:26 +02:00
Jeppe B af89a246db Run PR code quality workflow on GitHub-hosted runner 2026-06-01 23:38:32 +02:00
Jeppe B 20c1973565 Merge pull request #241 from copenhagentruckwash/fix-telemetry-path-error-message-leak
Sanitize telemetry ingestion errors
2026-06-01 23:37:59 +02:00
copilot-swe-agent[bot] 3555904423 Merge origin/master and resolve invoice_period_flag_service conflict 2026-06-01 21:37:53 +00:00
Jeppe B a2dda5ea5b Sanitize telemetry ingestion errors 2026-06-01 23:37:48 +02:00
Jeppe B ce29cf9ccb Merge pull request #240 from copenhagentruckwash/propose-fix-for-ci-vulnerability
Secure Qodana pull request workflow
2026-06-01 23:36:53 +02:00
Jeppe B e7481297c8 Secure Qodana PR workflow runner 2026-06-01 23:36:44 +02:00
Jeppe B e3b38519fb Merge pull request #239 from copenhagentruckwash/propose-fix-for-qodana-vulnerability
Skip Qodana when cloud token is missing
2026-06-01 23:31:34 +02:00
Jeppe B d244c000c3 Skip Qodana when cloud token is missing 2026-06-01 23:31:24 +02:00
Jeppe B dcd57c7092 Merge pull request #221 from copenhagentruckwash/fix-system-search-associations-vulnerability
Prevent association expansion from bypassing own-only access
2026-06-01 23:31:00 +02:00
Jeppe B 0a7e58fc01 Merge pull request #222 from copenhagentruckwash/fix-hardcoded-bearer-tokens-in-tests
Remove hardcoded API credentials and resolve merge conflict in test HTTP file
2026-06-01 23:29:28 +02:00
Jeppe B bd7deaeded Fix invoice period flag cache fallbacks 2026-06-01 23:29:05 +02:00
Jeppe B 07a3ef6418 Merge pull request #237 from copenhagentruckwash/fix-concurrent-access-vulnerability-in-start-command
Add per-lane START lock to prevent TOCTOU relay replay on wash start
2026-06-01 23:28:45 +02:00
Jeppe B bffed6f5f3 Fix self-serve start relay race 2026-06-01 23:28:36 +02:00
Jeppe B bfec31f94b Merge pull request #236 from copenhagentruckwash/fix-task-attachment-link-vulnerability
Enforce lane department authorization for self-serve eligibility
2026-06-01 23:28:05 +02:00
Jeppe B 2123835aae Fix self-serve eligibility lane authorization 2026-06-01 23:27:56 +02:00
Jeppe B 11f06e8f53 Merge pull request #235 from copenhagentruckwash/investigate-self-serve-path-projection-dos-vulnerability
Clamp self-serve path projection limits
2026-06-01 23:27:33 +02:00
Jeppe B 6712368323 Clamp self-serve path projection limits 2026-06-01 23:27:22 +02:00
Jeppe B 99fe659dbc Merge pull request #234 from copenhagentruckwash/propose-fix-for-archived-department-vulnerability
Fix department archived filter smuggling
2026-06-01 23:27:06 +02:00
Jeppe B 357cfda46e Fix department archived filter smuggling 2026-06-01 23:26:57 +02:00
Jeppe B 9c85135a07 Merge pull request #233 from copenhagentruckwash/fix-cross-tenant-vehicle-reference-leak
Restrict vehicle reference suggestions by department context
2026-06-01 23:26:36 +02:00
Jeppe B a8a47104dd Restrict vehicle reference suggestions by department context 2026-06-01 23:26:27 +02:00
Jeppe B 2c0907c486 Merge pull request #232 from copenhagentruckwash/fix-vulnerability-in-automatic-invoice-flags
Fix automatic invoice period flag suppression
2026-06-01 23:26:02 +02:00
copilot-swe-agent[bot] b1647b4ad1 Merge origin/master and resolve orderBookingsPost conflict 2026-06-01 21:25:58 +00:00
Jeppe B 0ae28af309 Fix invoice period automatic flag cache misses 2026-06-01 23:25:54 +02:00
copilot-swe-agent[bot] 64d7e6f061 Merge master into fix-system-search-associations-vulnerability 2026-06-01 21:25:51 +00:00
Jeppe B 4f9a10402b Merge pull request #231 from copenhagentruckwash/fix-exposure-of-private-git-commit-metadata
Redact GitHub commit metadata from public release runtime
2026-06-01 23:25:38 +02:00
Jeppe B 5e8ec85943 Redact release GitHub metadata from public runtime 2026-06-01 23:25:28 +02:00
Jeppe B 20d6056e40 Merge pull request #230 from copenhagentruckwash/fix-permission-bypass-for-invoice-flags
Guard invoice period flags by list permission
2026-06-01 23:25:02 +02:00
Jeppe B 5be6bc0198 Guard invoice period flags by list permission 2026-06-01 23:24:50 +02:00
Jeppe B 373aa7effb Merge pull request #227 from copenhagentruckwash/fix-minio-credentials-exposure-vulnerability
Deny web access to replication bootstrap snapshots
2026-06-01 23:24:26 +02:00
Jeppe B 5282ee10ba Merge pull request #229 from copenhagentruckwash/propose-fix-for-booking-po-vulnerability
Validate booking ownership before defaulting order PO (prevent cross-tenant leak)
2026-06-01 23:24:00 +02:00
copilot-swe-agent[bot] 06cba73a30 Initialize merge conflict resolution plan 2026-06-01 21:23:50 +00:00
Jeppe B eab8394579 Fix booking PO default tenant validation 2026-06-01 23:23:49 +02:00
Jeppe B b88c2742e8 Merge pull request #228 from copenhagentruckwash/fix-partial-release-tests-bypassing-promotion-gate
Require app-scoped release gates for bundle promotion
2026-06-01 23:23:34 +02:00
Jeppe B 4ea5eeb942 Require app-scoped release gates for bundle promotion 2026-06-01 23:23:23 +02:00
Jeppe B e41b226529 Deny web access to replication bootstrap snapshots 2026-06-01 23:19:04 +02:00
Jeppe B 7cb248a112 Merge pull request #226 from copenhagentruckwash/fix-ssrf-vulnerability-in-release-gate
Harden release gate diagnostics fetches
2026-06-01 23:17:01 +02:00
Jeppe B dfa0441266 Harden release gate diagnostics fetches 2026-06-01 23:16:51 +02:00
Jeppe B d9dbd7dede Merge pull request #225 from copenhagentruckwash/propose-fix-for-coolify-deployment-vulnerability
Prevent Coolify image from embedding replication snapshots
2026-06-01 23:16:33 +02:00
Jeppe B 3b8463e37f Prevent Coolify image from embedding replication snapshots 2026-06-01 23:16:22 +02:00
Jeppe B 0e8b527ee4 Merge pull request #224 from copenhagentruckwash/fix-qodana-scan-fail-open-issue
Run Qodana locally when cloud token is missing
2026-06-01 23:15:47 +02:00
Jeppe B 86fb8bb700 Run Qodana without upload when token is missing 2026-06-01 23:15:36 +02:00
Jeppe B 6fbf7f271d Merge pull request #223 from copenhagentruckwash/propose-fix-for-exposed-bootstrap-secret
Protect replication bootstrap file from static serving
2026-06-01 23:14:52 +02:00
Jeppe B fe5ebdc203 Protect replication bootstrap file from static serving 2026-06-01 23:14:43 +02:00
Jeppe B c6dbc0728f Remove hardcoded credentials from orderBookingsPost HTTP examples 2026-06-01 23:14:12 +02:00
Jeppe B a0b1dcb3e3 Fix system search association expansion for own-only types 2026-06-01 23:13:50 +02:00
Jeppe B 38c4c32f07 Merge pull request #220 from copenhagentruckwash/fix-empty-edge-broker-secret-vulnerability
Fail closed when edge broker secret is missing
2026-06-01 23:13:13 +02:00
Jeppe B b6beb9622b Fail closed when edge broker secret is missing 2026-06-01 23:13:04 +02:00
Jeppe B db80dad15f Merge pull request #219 from copenhagentruckwash/propose-fix-for-unauthenticated-pdf-access
Fix unauthenticated PDF disclosure in file_server fallback
2026-06-01 23:12:00 +02:00
Jeppe B cf370a8035 Fix unauthenticated pdf_store access in file server 2026-06-01 23:11:48 +02:00
Jeppe B 0778776f00 Merge pull request #218 from copenhagentruckwash/fix-machine-relay-helper-logic
Fix hard MACHINE relay targeting
2026-06-01 23:11:13 +02:00
Jeppe B ee55c23cde Fix hard machine relay targeting 2026-06-01 23:11:01 +02:00
Jeppe B 1f50c83f93 Merge pull request #217 from copenhagentruckwash/fix-unauthenticated-/files/-attachment-access
Require authentication for direct /files/ access
2026-06-01 23:09:47 +02:00
Jeppe B 85f7bd1fc9 Require auth for direct /files/ downloads 2026-06-01 23:09:37 +02:00
Jeppe B 8f53e80ede Merge pull request #216 from copenhagentruckwash/fix-vulnerability-in-wash-certificate-access
Disable global .pdf shortcut to prevent unauthenticated certificate downloads
2026-06-01 23:09:03 +02:00
Jeppe B 2a1a730a8c Fix unauthenticated direct PDF certificate serving 2026-06-01 23:08:53 +02:00
Jeppe B 7bb67b0470 Merge pull request #213 from copenhagentruckwash/fix-sql-injection-in-vehicle-plate-lookup
Fix SQL injection in vehicle plate order history lookup
2026-06-01 23:08:35 +02:00
copilot-swe-agent[bot] 1065973b33 Merge master into fix-sql-injection-in-vehicle-plate-lookup 2026-06-01 21:07:49 +00:00
Jeppe B 1d05550cd3 Merge pull request #215 from copenhagentruckwash/fix-start-command-relay-activation-vulnerability
Fix self-serve START relay deferral bypass
2026-06-01 23:05:10 +02:00
Jeppe B 2cc12c23cd Fix self-serve start relay deferral 2026-06-01 23:04:59 +02:00
Jeppe B b09ada0bc4 Merge pull request #214 from copenhagentruckwash/fix-hardcoded-auth_key-in-bookings-sync
Remove hardcoded auth_key bypass in admin bookings sync endpoint
2026-06-01 23:03:56 +02:00
Jeppe B 43dfac836a Fix booking sync auth bypass 2026-06-01 23:03:47 +02:00
Jeppe B d1871f1420 Fix SQL injection in vehicle plate order history lookup 2026-06-01 23:03:18 +02:00
Jeppe B 08a1538ed6 Merge pull request #212 from copenhagentruckwash/propose-fix-for-sql-injection-vulnerability
Cast pickup_bool to int to prevent SQL injection in bookings sync
2026-06-01 23:02:38 +02:00
Jeppe B f2fc4f6f18 Fix SQL injection risk in booking sync pickup_bool 2026-06-01 23:02:28 +02:00
Jeppe B 503fd50c61 Merge pull request #211 from copenhagentruckwash/fix-vulnerability-in-wash-certificate-pdf-handling
Restore deletion of local wash certificate PDFs after upload
2026-06-01 23:02:12 +02:00
Jeppe B 1d6df82c1c Delete local wash certificate PDFs after upload 2026-06-01 23:02:01 +02:00
Jeppe B 3fda0f9912 Merge pull request #210 from copenhagentruckwash/fix-auth-bypass-in-booking-sync-endpoint
Remove hardcoded auth_key bypass from /admin/bookings/sync
2026-06-01 23:01:37 +02:00
Jeppe B decc571307 Fix booking sync auth bypass 2026-06-01 23:01:28 +02:00
Jeppe B ef237b5e87 Merge pull request #206 from copenhagentruckwash/fix-sql-injection-in-vehicle-plate-history
Fix SQL injection in vehicle plate order history lookup
2026-06-01 22:59:14 +02:00
Jeppe B 828c177a57 Merge pull request #207 from copenhagentruckwash/fix-order-item-update-idor-vulnerability
Enforce tenant ownership check for PUT /order/items to prevent IDOR
2026-06-01 22:59:03 +02:00
copilot-swe-agent[bot] c79219eb00 Merge remote-tracking branch 'origin/master' into fix-order-item-update-idor-vulnerability
# Conflicts:
#	services/nginx/app/routes/orderItemsRoute.php
2026-06-01 20:58:04 +00:00
copilot-swe-agent[bot] 6995c3d1bc Merge origin/master and resolve orders_o conflict 2026-06-01 20:57:44 +00:00
Jeppe B 7436584598 Merge pull request #209 from copenhagentruckwash/fix-unauthenticated-certificate-download-vulnerability
Require authentication token for wash certificate download endpoint
2026-06-01 22:57:33 +02:00
Jeppe B 06421beb6b Require token for wash certificate downloads 2026-06-01 22:57:23 +02:00
Jeppe B 7de4b96074 Merge pull request #208 from copenhagentruckwash/fix-arbitrary-group_id-role-assignment
Harden role authorization on user creation
2026-06-01 22:56:08 +02:00
Jeppe B ea69c64fad Harden user creation role authorization 2026-06-01 22:55:57 +02:00
Jeppe B 652b89d23d Fix IDOR in order item update route 2026-06-01 22:55:35 +02:00
Jeppe B bc4b7bde15 Fix SQL injection in vehicle plate order history lookup 2026-06-01 22:55:06 +02:00
Jeppe B a5b674286a Merge pull request #205 from copenhagentruckwash/fix-order-update-vulnerability-for-invoice-collection
Validate invoice collection ownership when updating orders
2026-06-01 22:54:31 +02:00
copilot-swe-agent[bot] 18bf7aa013 Resolve merge conflict: combine invoice collection ownership validation with auto-reassign guard 2026-06-01 20:53:42 +00:00
Jeppe B bf8262b64f Validate invoice collection ownership when updating orders 2026-06-01 22:51:09 +02:00
Jeppe B fdb073f17f Merge pull request #204 from copenhagentruckwash/fix-unauthenticated-sync-usage-endpoint
Enforce permission on XLVask sync-usage route
2026-06-01 22:50:42 +02:00
Jeppe B 20fcd4ac16 Protect XLVask sync-usage route with permission check 2026-06-01 22:50:33 +02:00
Jeppe B 0f7d76d96d Merge pull request #203 from copenhagentruckwash/fix-unauthenticated-limble-endpoints
Enforce Limble route permissions and secure Limble HTTP requests
2026-06-01 22:50:10 +02:00
Jeppe B 324f2c856f Fix Limble auth and secure request handling 2026-06-01 22:50:00 +02:00
Jeppe B 84f203939c Merge pull request #202 from copenhagentruckwash/fix-unauthenticated-limble-webhook-vulnerability
Prevent credential leak in Limble request error path
2026-06-01 22:49:38 +02:00
Jeppe B 368501a8ce Fix Limble request error path credential leak 2026-06-01 22:49:29 +02:00
Jeppe B d9a36e4050 Merge pull request #201 from copenhagentruckwash/fix-idor-vulnerability-in-attachment-endpoints
Ensure attachment belongs to task before download/delete (fix IDOR)
2026-06-01 22:48:14 +02:00
Jeppe B a5019efbda Fix task attachment IDOR in self-serve endpoints 2026-06-01 22:48:04 +02:00
Jeppe B b16a07fdbb Merge pull request #200 from copenhagentruckwash/fix-subuser-permission-vulnerability
Harden subuser permission customer context resolution
2026-06-01 22:46:50 +02:00
Jeppe B ca02fd3436 Harden subuser permission customer context resolution 2026-06-01 22:46:40 +02:00
Jeppe B 65283b8ad7 Merge pull request #196 from copenhagentruckwash/fix-sql-injection-in-recommended-order-lookup
Escape plate input to prevent SQL injection in recommended-order lookup
2026-06-01 22:45:46 +02:00
Jeppe B ad53041bfd Merge pull request #197 from copenhagentruckwash/fix-department-lanes-access-vulnerability
Enforce department scoping in department lanes routes
2026-06-01 22:45:34 +02:00
Jeppe B b11b38a95b Merge pull request #198 from copenhagentruckwash/fix-lane-ownership-validation-for-commands
Enforce department scoping for self-serve lane command route
2026-06-01 22:45:23 +02:00
Jeppe B ba9c4d3b9f Merge pull request #199 from copenhagentruckwash/fix-missing-department-access-checks
Require department-level access for /departments/self-serve/enabled endpoints
2026-06-01 22:45:11 +02:00
copilot-swe-agent[bot] ded497b3d8 Merge remote-tracking branch 'origin/master' into fix-missing-department-access-checks
# Conflicts:
#	services/nginx/app/routes/departmentsRoute.php
2026-06-01 20:43:01 +00:00
copilot-swe-agent[bot] 2fd3ce4877 Merge remote-tracking branch 'origin/master' into fix-department-lanes-access-vulnerability
# Conflicts:
#	services/nginx/app/routes/departmentLanesRoute.php
2026-06-01 20:42:43 +00:00
copilot-swe-agent[bot] 64fc70a0a8 Merge remote-tracking branch 'origin/master' into fix-lane-ownership-validation-for-commands
# Conflicts:
#	services/nginx/app/routes/moduleSelfServeRoute.php
2026-06-01 20:41:57 +00:00
Jeppe B 42acf26ee1 Merge pull request #193 from copenhagentruckwash/fix-subuser-tokens-allowing-user-impersonation
Prevent subuser session token escalation into user auth
2026-06-01 22:41:35 +02:00
Jeppe B fe6eae862f Merge pull request #194 from copenhagentruckwash/fix-missing-department-authorization-for-payment-intents
Require department access on Stripe payment-intent routes
2026-06-01 22:41:24 +02:00
copilot-swe-agent[bot] eedde6c6d7 Merge origin/master and resolve orders_o conflict 2026-06-01 20:41:18 +00:00
copilot-swe-agent[bot] 2782afde2e Merge master into branch and re-apply department access checks on Stripe payment-intent routes 2026-06-01 20:40:30 +00:00
Jeppe B 484529660b Enforce department access on self-serve status routes 2026-06-01 22:39:52 +02:00
copilot-swe-agent[bot] fbe700a4db Merge remote-tracking branch 'origin/master' into fix-subuser-tokens-allowing-user-impersonation
# Conflicts:
#	services/nginx/app/classes/authentication.php
2026-06-01 20:39:16 +00:00
Jeppe B 6225c4b072 Enforce department access for self-serve lane commands 2026-06-01 22:39:14 +02:00
Jeppe B 300a37fce3 Enforce department access in department lanes routes 2026-06-01 22:38:53 +02:00
Jeppe B c43618351e Escape plate in recommended order SQL lookup 2026-06-01 22:37:55 +02:00
Jeppe B b3225c8d8b Merge pull request #195 from copenhagentruckwash/fix-sql-injection-in-filter-handling
Fix SQL injection in array-based pagination filters
2026-06-01 22:37:38 +02:00
Jeppe B 0f96247bf3 Fix SQL injection in array pagination filters 2026-06-01 22:37:28 +02:00
Jeppe B 4703e07951 Enforce department access on Stripe payment intent order routes 2026-06-01 22:36:49 +02:00
Jeppe B 7ddda9ab03 Merge pull request #190 from copenhagentruckwash/fix-2fa-token-validation-bypass
Enforce auth token types to prevent 2FA bypass
2026-06-01 22:36:18 +02:00
copilot-swe-agent[bot] 4e9575cd87 Merge master and resolve conflict: use rawToken in get_user() exception-handled lookup 2026-06-01 20:35:51 +00:00
Jeppe B ef82a95feb Merge pull request #186 from copenhagentruckwash/propose-fix-for-edge-broker-vulnerability
Harden edge broker defaults and restrict compose exposure
2026-06-01 22:35:45 +02:00
Jeppe B fd4ec3dda2 Fix subuser token confusion in user auth flow 2026-06-01 22:35:24 +02:00
Jeppe B 1616bd431a Merge pull request #192 from copenhagentruckwash/fix-subuser-permission-evaluation-vulnerability
Use resolved customer context in subuser permission checks
2026-06-01 22:34:56 +02:00
copilot-swe-agent[bot] 334a7a4401 Merge origin/master into propose-fix-for-edge-broker-vulnerability, resolving conflicts 2026-06-01 20:34:47 +00:00
Jeppe B 22dd9f9c07 Fix subuser permission checks to use resolved customer context 2026-06-01 22:34:45 +02:00
Jeppe B 5684da1bc7 Merge pull request #191 from copenhagentruckwash/fix-sql-injection-in-gate/relay-creation
Escape JSON-encoded values in add_object to prevent SQL injection
2026-06-01 22:34:00 +02:00
Jeppe B 69cd039322 Escape JSON values in add_object inserts 2026-06-01 22:33:49 +02:00
Jeppe B 0dc7f813a8 Merge pull request #188 from copenhagentruckwash/propose-fix-for-relay-control-bypass-vulnerability
Fix self-serve relay sync to enforce lane safety guards
2026-06-01 22:33:11 +02:00
copilot-swe-agent[bot] a828e9bc25 Merge origin/master into propose-fix-for-edge-broker-vulnerability, resolving all conflicts 2026-06-01 20:27:11 +00:00
copilot-swe-agent[bot] 8d2e71aaf3 Merge origin/master and resolve self-serve relay sync conflicts 2026-06-01 20:23:09 +00:00
Jeppe B 721e2670dd Reject 2FA verification tokens for API authentication 2026-06-01 22:22:42 +02:00
Jeppe B b03500d2d1 Merge pull request #189 from copenhagentruckwash/fix-subuser-token-authorization-vulnerability
Validate subuser grants before resolving subuser customer context
2026-06-01 22:21:58 +02:00
Jeppe B ed9ebc2ac8 Validate subuser grants before resolving customer user 2026-06-01 22:21:44 +02:00
Jeppe B 64beb38bae Fix self-serve relay sync to enforce lane safety guards 2026-06-01 22:19:34 +02:00
Jeppe B e13bbae01f Merge pull request #184 from copenhagentruckwash/fix-edge-broker-default-shared-secret-issue
Harden edge broker shared secret defaults
2026-06-01 22:17:57 +02:00
Jeppe B 5ba0f5f9ba Merge pull request #183 from copenhagentruckwash/fix-credential-exposure-in-.env.old
Remove leaked `.env.old` with credentials and add to `.gitignore`
2026-06-01 22:17:30 +02:00
Jeppe B bb5f1db1b3 Merge branch 'master' into fix-credential-exposure-in-.env.old 2026-06-01 22:17:21 +02:00
copilot-swe-agent[bot] cb63d10415 Merge origin/master into fix-edge-broker-default-shared-secret-issue 2026-06-01 20:12:06 +00:00
Jeppe B 4183c3928c Merge pull request #187 from copenhagentruckwash/fix-hard-coded-tokens-in-test-file
Sanitize leaked credentials in test/orderBookingsPost.http
2026-06-01 22:11:38 +02:00
Jeppe B 28bae85b2a Sanitize leaked credentials in order booking HTTP template 2026-06-01 22:11:23 +02:00
Jeppe B f2db92de09 Harden edge broker defaults and compose exposure 2026-06-01 22:10:14 +02:00
Jeppe B a41334f513 Merge pull request #185 from copenhagentruckwash/fix-mysql-debug-exposure-vulnerability
Harden mysql-debug compose service configuration
2026-06-01 22:09:46 +02:00
Jeppe B 175fb3a35f Harden mysql-debug compose service configuration 2026-06-01 22:09:35 +02:00
copilot-swe-agent[bot] 8e6b29810a Clean up resolved gitignore merge 2026-06-01 20:08:24 +00:00
Jeppe B 21e9b2c80f Harden edge broker shared secret defaults 2026-06-01 22:08:20 +02:00
copilot-swe-agent[bot] 989d04167a Resolve .gitignore merge conflict with master 2026-06-01 20:07:48 +00:00
Jeppe B 286127c390 Merge pull request #182 from copenhagentruckwash/fix-edge-broker-default-shared-secret-issue
Remove insecure default edge broker shared secret and stop exposing port 4300
2026-06-01 22:07:10 +02:00
copilot-swe-agent[bot] 2ba87a4850 Start merge conflict resolution 2026-06-01 20:06:07 +00:00
Jeppe B 6658af814b Remove committed env backup with secrets 2026-06-01 22:04:11 +02:00
Jeppe B 2abd6d04e9 Merge pull request #180 from copenhagentruckwash/fix-edge-broker-vulnerability-in-repository
Harden edge broker compose defaults
2026-06-01 22:02:41 +02:00
copilot-swe-agent[bot] 3107779b74 Resolve merge conflicts with origin/master 2026-06-01 20:02:21 +00:00
Jeppe B 61a09dce87 Remove insecure default edge broker secret fallback 2026-06-01 22:01:39 +02:00
Jeppe B a02ed69108 Merge pull request #181 from copenhagentruckwash/fix-remote-root-shell-execution-vulnerability
Gate edge-agent shell actions behind local opt-in
2026-06-01 22:01:01 +02:00
Jeppe B 9b69aadca4 Gate edge-agent shell actions behind local opt-in 2026-06-01 22:00:49 +02:00
Jeppe B 6204fb50f9 Harden edge broker compose defaults 2026-06-01 21:59:27 +02:00
Jeppe B 0a6a8aeab2 Merge pull request #179 from copenhagentruckwash/fix-vulnerability-in-ci-workflow
Harden tests workflow: run PR jobs on GitHub-hosted runners
2026-06-01 21:57:15 +02:00
copilot-swe-agent[bot] 7c21b6463d Merge origin/master and resolve workflow conflicts 2026-06-01 19:55:27 +00:00
Jeppe B d97cfda0ea Harden CI by avoiding self-hosted runners on PR workflow 2026-06-01 21:48:39 +02:00
Jeppe B aad5d77f41 Merge pull request #178 from copenhagentruckwash/propose-fix-for-exposure-of-sensitive-logs
Remove committed Caddy access log containing leaked secrets
2026-06-01 21:47:33 +02:00
Jeppe B 3b132cad95 Merge pull request #176 from copenhagentruckwash/fix-property-gate-command-authorization-bypass
Restore explicit permissions for property gate commands to fix authorization bypass
2026-06-01 21:03:30 +02:00
copilot-swe-agent[bot] ab957092bd Merge origin/master into propose-fix-for-exposure-of-sensitive-logs 2026-06-01 19:03:25 +00:00
copilot-swe-agent[bot] b8f65f242f Merge origin/master and resolve property gate conflict 2026-06-01 19:02:11 +00:00
Jeppe B 688cb0a664 Merge pull request #173 from copenhagentruckwash/fix-cross-tenant-certificate-attachment-vulnerability
Validate booking order context before certificates
2026-06-01 21:00:33 +02:00
Jeppe B 6eb4171fea Merge pull request #172 from copenhagentruckwash/propose-fix-for-automation-permission-bug
Prevent XL Vask list automation execution
2026-06-01 21:00:21 +02:00
Jeppe B ddba27a1be Remove committed Caddy access log with leaked secrets 2026-06-01 20:59:59 +02:00
copilot-swe-agent[bot] 933b18b988 Merge origin/master and resolve booking conflict files 2026-06-01 18:59:05 +00:00
Jeppe B 18c6852865 Merge pull request #177 from copenhagentruckwash/fix-broker-secret-vulnerability-in-api
Harden edge broker shared-secret handling
2026-06-01 20:58:58 +02:00
Jeppe B 77403965f8 Harden edge broker shared-secret handling 2026-06-01 20:58:45 +02:00
copilot-swe-agent[bot] a3e2765ad4 Merge origin/master and resolve XLVask route contract conflict 2026-06-01 18:57:46 +00:00
Jeppe B 787db994dd Fix property gate command authorization bypass 2026-06-01 20:57:21 +02:00
Jeppe B f8f603a38e Merge pull request #175 from copenhagentruckwash/fix-vulnerability-in-studio-graph-edits
Fix authorization boundary for studio graph lane operations
2026-06-01 20:56:51 +02:00
Jeppe B 31a7224272 Fix studio graph lane operations permission checks 2026-06-01 20:56:38 +02:00
Jeppe B 492c81e27c Merge pull request #174 from copenhagentruckwash/fix-vulnerability-in-studio-action-conditions
Fix fail-open condition gating in self-serve Studio action runner
2026-06-01 20:56:21 +02:00
Jeppe B 45bfb1525a Fix studio action conditions to fail closed without results 2026-06-01 20:56:04 +02:00
Jeppe B 71ffa20811 Validate booking order context before certificates 2026-06-01 20:55:26 +02:00
Jeppe B a466c6291c Prevent XL Vask list automation execution 2026-06-01 20:54:44 +02:00
Jeppe B 9606d3b11d Merge pull request #171 from copenhagentruckwash/fix-sensitive-data-exposure-vulnerability
Remove committed replication bootstrap snapshot with secrets
2026-06-01 20:54:25 +02:00
Jeppe B 03b7fcd1b1 Remove committed replication bootstrap snapshot 2026-06-01 20:54:10 +02:00
Jeppe B 3d0f0f3391 Merge pull request #170 from copenhagentruckwash/fix-hard-coded-bearer-token-in-tests
Remove committed bearer token from invoicing HTTP example
2026-06-01 20:53:55 +02:00
Jeppe B e3257465a0 Remove hard-coded bearer token from invoicing HTTP example 2026-06-01 20:53:42 +02:00
Jeppe B 0c21f6e3a1 Merge pull request #169 from copenhagentruckwash/fix-gateway-auto-provision-deployment-vulnerability
Pin gateway auto-provision deployments to source commit
2026-06-01 20:53:22 +02:00
Jeppe B f1e5cacd0c Pin gateway auto-provision deployments to source commit 2026-06-01 20:53:10 +02:00
Jeppe B a8d5320ae5 Merge pull request #168 from copenhagentruckwash/fix-auto-promotion-vulnerability-in-release-gate
Prevent auto-sync promotion when release gate `required_checks` is empty
2026-06-01 20:52:52 +02:00
Jeppe B 95ac0d3a2c Block release gate auto-sync when required checks are empty 2026-06-01 20:52:39 +02:00
Jeppe B 1ed27dd467 Merge pull request #167 from copenhagentruckwash/fix-superuser-invite-resend-security-flaw
Scope superuser subuser invite resends
2026-06-01 20:52:23 +02:00
Jeppe B c4bb7bbb8b Scope superuser subuser invite resends 2026-06-01 20:52:08 +02:00
Jeppe B c09b7ebe76 Merge pull request #166 from copenhagentruckwash/fix-pathoutcomespayload-argument-type-error
Accept null confirmation rows in pathOutcomesPayload
2026-06-01 19:56:09 +02:00
Jeppe B 166ed6b92b Merge pull request #165 from copenhagentruckwash/fix-self-serve-invoice-assignment-issue
Fix self-serve invoice customer attribution
2026-06-01 19:54:28 +02:00
Jeppe B 8e528f3eae Fix null path confirmation rows 2026-06-01 19:53:40 +02:00
copilot-swe-agent[bot] 160772b832 Merge origin/master and resolve invoice billing test conflict 2026-06-01 17:52:21 +00:00
Jeppe B c8a5c3969d Fix self-serve invoice customer attribution 2026-06-01 19:48:10 +02:00
Jeppe B bb98df9e73 Merge pull request #164 from copenhagentruckwash/fix-truckwash-edge-gateway-stack.service-errors
Fix edge gateway PHP Docker extension setup
2026-06-01 19:30:15 +02:00
Jeppe B fe3719530a Fix edge gateway PHP image extensions 2026-06-01 19:19:01 +02:00
Jeppe B 603f497bef Merge pull request #163 from copenhagentruckwash/investigate-test-failure-issues
ci: retry Release Manager gate on transient 504s
2026-06-01 17:09:13 +02:00
Jeppe B ee16db8ecc ci: retry release manager gate on transient failures 2026-06-01 16:56:34 +02:00
Jeppe B c5c33d3cf7 Merge pull request #162 from copenhagentruckwash/fix-missing-happy-path-coverage-marker
Restore selected orders API coverage
2026-06-01 16:41:45 +02:00
Jeppe B da05c5adb7 Restore selected orders API coverage 2026-06-01 16:31:06 +02:00
Jeppe B 707cf67d5c Remove OrdersApiTest to clean up obsolete test cases 2026-06-01 13:07:21 +02:00
Jeppe B 09fa186028 Merge pull request #161 from copenhagentruckwash/codex/master-tests-pass-api-20260528
[codex] Fix backend master test gates
2026-05-29 16:32:31 +02:00
Jeppe B 5e6b340f8c Use compose broker URL for edge gateway smoke 2026-05-29 15:29:41 +02:00
Jeppe B 04e47a2e6d Start all PHP upstreams for edge gateway smoke 2026-05-29 15:10:49 +02:00
Jeppe B 572f5027d6 Run edge gateway smoke inside compose network 2026-05-29 14:56:36 +02:00
Jeppe B 235e0268c2 Fix backend CI gate failures 2026-05-29 14:36:18 +02:00
Jeppe B 65d639853b Skip Qodana when cloud token is unavailable 2026-05-28 23:44:07 +02:00
Jeppe B e856bbffec Trigger backend master test gates 2026-05-28 23:35:12 +02:00
Jeppe Bundgaard 3ee5b789ce Update setMachineRelayStatusHard method to use MACHINE_PROGRAM_PICKER constant for relay status setting 2026-05-28 21:08:37 +02:00
Jeppe Bundgaard 7f5722ff75 Add exception handling for cleaner relay activation in self-serve lanes
- Include `\Throwable` in docstring for better error documentation.
- Implement `turnOnCleanerRelayForWashStart` in the wash start process.
2026-05-28 20:40:08 +02:00
Jeppe B 50b596af39 Merge pull request #157 from copenhagentruckwash/fix-issues-and-verify-with-tests
Fix test gateway Windows config paths
2026-05-28 19:39:51 +02:00
Jeppe B af06c4d81e Merge pull request #160 from copenhagentruckwash/copilot/fix-qodana-workflow-failure
Fix Qodana failure on self-hosted runner by trusting workspace as Git safe.directory
2026-05-28 19:39:25 +02:00
Jeppe B 41ed692299 Merge pull request #159 from copenhagentruckwash/fix-subuser-token-permission-bypass
Restrict replication endpoints to classic users
2026-05-28 19:37:54 +02:00
copilot-swe-agent[bot] 31214f0af0 fix: mark workspace as git safe directory before qodana 2026-05-28 17:34:58 +00:00
Jeppe B aceaa6b957 Fix Qodana workflow and Windows-style test gateway paths
Update the Qodana workflow to use an available action version and avoid cloud-token failures when the secret is absent. Keep the test gateway path resolver using Windows path semantics for Windows-style inputs.
2026-05-28 19:33:02 +02:00
copilot-swe-agent[bot] cd0e0f0e61 Initial plan 2026-05-28 17:30:45 +00:00
copilot-swe-agent[bot] 0db6b5269d Merge origin/master and resolve replication route conflict 2026-05-28 17:29:12 +00:00
Jeppe B 3fb1eb9644 Restrict replication endpoints to classic users 2026-05-28 19:25:59 +02:00
Jeppe B 76dfcd70d1 Merge pull request #158 from copenhagentruckwash/fix-authorization-bypass-in-self-serve-lanes
Harden self-serve lane mutation authorization
2026-05-28 19:25:01 +02:00
Jeppe B b13abe0d30 Harden self-serve lane mutation authorization 2026-05-28 19:23:31 +02:00
Jeppe B 270e5b970f Support Windows-style test gateway paths
Resolve test gateway paths with the Windows path implementation when inputs use Windows-style syntax. This preserves the existing runnable script test suite without adding Windows-only tests.
2026-05-28 19:16:52 +02:00
Jeppe B 5dac3211ff Fix test gateway Windows config paths
### Motivation
- Tests that resolve the test gateway config directory were failing on Windows-style paths because the code always used the POSIX `path` module, producing mismatched separators.
- Preserve Windows path semantics when `rootDir` or an explicit config path uses Windows syntax while leaving POSIX behavior unchanged.

### Description
- Add `usesWindowsPathSyntax` and `pathForInputs` helpers to detect Windows-style paths and select `path.win32` when needed.
- Use the selected `pathModule` in `resolveConfigDirectory` to call `resolve`/`join` so Windows roots or explicit Windows dirs keep correct separators.
- Change is confined to `scripts/test-gateway.mjs` and does not alter other runtime behavior.

### Testing
- Ran `node --test scripts/*.test.mjs` which initially showed one failing path test and after the fix completed with all tests passing (`14` passed, `0` failed).
- Ran `npm test` in `services/edge-agent` and `services/edge-broker`, both suites passed (`18` and `23` tests respectively).
- Ran `node scripts/sync-ai-workflow.mjs --check` and `git diff --check` which both succeeded.
2026-05-28 19:11:58 +02:00
Jeppe B 4d91fc8ead Fix test gateway Windows config paths 2026-05-28 19:00:31 +02:00
Jeppe B 893ed1bda5 Fix PHP CI legacy and edge gateway tests
- Match self-serve legacy test double invoice signature.
- Wait for the edge gateway integration database before bootstrapping schema.
2026-05-28 18:03:30 +02:00
Jeppe Bundgaard 90ebec84bf Add PHP CI test script and optimize Redis config in tests
- Introduced a PHP CI test script for managing test suites.
- Consolidated Redis configuration retrieval.
- Optimized test fixture queries with dynamic object type assignments.
2026-05-28 17:58:06 +02:00
Jeppe Bundgaard bdf2a787d6 Merge remote-tracking branch 'origin/master' 2026-05-28 17:33:10 +02:00
Jeppe Bundgaard f8c254607d Implement Lane Status Audit and Comprehensive Self-Serve API Enhancements
- Introduced `machine_status_audit` in self-serve lanes for tracking changes.
- Added new methods to handle audit data including `setLaneStatusAudit` and `getMachineStatusAudit`.
- Enhanced API tests to include legacy Redis constant checks and validated comprehensive self-serve invoice creation.
- Updated department lanes to reflect audit logs in their responses.
2026-05-28 17:27:25 +02:00
Jeppe B 20eb92891a Avoid empty self-serve invoice orders
Only create the invoice order context when elapsed minute billing has a positive quantity. This preserves automatic-mode included-minute reduction without leaving an empty order id on the lane.

Tests:
- bash scripts/php-ci-test.sh unit
2026-05-28 17:21:36 +02:00
Jeppe Bundgaard 4cfe906f55 Update invoice function in selfserve_lane_command_t to accept command arguments and add necessary requires in selfserve_lane_invoice_t. 2026-05-28 16:36:09 +02:00
Jeppe Bundgaard 184ea1ca6c Enhance invoice and self-serve logic with subuser support
- Add subuser ID management to `selfserve_lane_command_arguments`.
- Update `invoice` function to include optional command arguments.
- Attach metadata to orders with self-serve and subuser details.
- Introduce `OTHER_TYPE_SELF_SERVE_WASH` in `attachment_content`.
2026-05-28 16:11:14 +02:00
Jeppe Bundgaard ae3657e7aa Add new API tests for order item note requirements, subuser route updates, and department lane status management
- Introduced tests for validating note requirements on order items.
- Updated subuser route management contract tests with new route coverage.
- Added endpoints to manage department lane and self-serve lane statuses, with associated tests.
2026-05-28 16:06:14 +02:00
Jeppe Bundgaard 54de2e5674 Add fake classes for relay logic and refactor relay shutdown without pre-checking status
Introduce helper classes `SelfserveWashCompletionRelayValueFake`, `SelfserveWashCompletionDepartmentLaneFake`, `SelfserveWashCompletionRelayLaneFake`, and `SelfserveWashCompletionFlowHarness` to simulate relay logic for unit tests. Refactor `turnOffRelayIfConfiguredAndOn` to `turnOffRelayIfConfigured`, removing relay status pre-check for cleaner and machine relays when completing a wash session, and test associated relay actions.
2026-05-27 19:30:31 +02:00
Jeppe Bundgaard eef436d44b Add tests for subuser password validation and grant permission normalization
Introduce unit and API tests for subuser password policies ensuring compliance with complexity requirements. Normalize subuser grant permission handling for consistency, including support for legacy zero permissions.
2026-05-27 19:17:19 +02:00
Jeppe Bundgaard b7aeb11801 Add department_selfserve_path_confirmations table and enhance PingApiTest
Introduce a new database table `department_selfserve_path_confirmations` to store path confirmations related to department configurations. Update `PingApiTest` to verify additional keys, ensuring `backend_version` and `api_commit_sha` are checked in the response.
2026-05-27 17:35:16 +02:00
Jeppe Bundgaard d52ceb8513 Add robust release update and API health checks
This commit introduces a release update mechanism, including candidate detection, asset pre-downloading, and installation workflows with proper state management. Additionally, it implements API health checks both for successful and failure scenarios and adds related unit and e2e tests for enhanced reliability.
2026-05-27 13:24:15 +02:00
Jeppe Bundgaard 1504f1b116 Add tests for ReleaseManager's handling of production database targets, service set statuses, data target detection, and beta release bundle policies. 2026-05-26 17:28:12 +02:00
Jeppe Bundgaard 78462f3ae4 Integrate cors_policy class to standardize CORS handling, refactor optionsRoute to use it, and add unit tests for CORS and Self-Serve Lane Access functionalities. 2026-05-26 16:35:02 +02:00
Jeppe Bundgaard 6ff6ce9b48 Add tests for automatic path-routed release target preparation and application target handling failures in ReleaseManager 2026-05-26 15:11:27 +02:00
Jeppe Bundgaard bc7c0280f2 Add .gitattributes for binary files and extend order booking update tests. Enhance dynamic image and routing logic with program_picker support. 2026-05-26 14:04:27 +02:00
Jeppe Bundgaard f1c123a840 Add tests to ensure order PO defaults from booking when missing and enhance existing routing logic. 2026-05-21 11:35:01 +02:00
Jeppe Bundgaard e40c6b6bac Probe release gateway health by channel path 2026-05-20 19:49:21 +02:00
Jeppe Bundgaard 8a1ec91b9e Allow explicit runtime channel selection 2026-05-20 19:39:23 +02:00
Jeppe Bundgaard f7ff9f0a3b Use Dockerfile builds for frontend release targets 2026-05-20 18:53:41 +02:00
Jeppe Bundgaard 16cfcaf41f Route frontend release targets through gateway 2026-05-20 18:07:23 +02:00
Jeppe Bundgaard 86fb092c22 Add extensive testing for ReleaseManager status overview logic and normalize API ingress paths. Enhance CORS headers and update gateway route URL handling for Coolify. 2026-05-20 17:49:16 +02:00
Jeppe Bundgaard c5a1271798 Enhance Coolify API deployment with improved gateway route handling and extensive test coverage. Add new methods for service updates and ensure Composer vendor sanity checks in PHP container. 2026-05-20 16:45:50 +02:00
Jeppe Bundgaard 5ea12f5342 Add Coolify API deployment image 2026-05-20 15:27:20 +02:00
Jeppe Bundgaard 44c4b7656f Enhance Coolify integration with gateway route deployment, add tests for new application route labels, and refactor gateway probing process. 2026-05-20 12:59:51 +02:00
Jeppe Bundgaard 24ac681365 Add tests for GitHub commit timestamp handling and runtime channel selection in ReleaseManager. Extend release URL normalization and runtime channel methods, and introduce assignment subject searches. 2026-05-20 11:27:29 +02:00
Jeppe Bundgaard c2263b8c98 Add tests for Coolify app payload handling, e-conomic customer fields, and expand Coolify API client capabilities 2026-05-19 16:55:45 +02:00
Jeppe Bundgaard cbf3c2d2b9 Add tests and methods for enhanced Coolify app handling, include isolated stack support and schema updates 2026-05-19 14:45:45 +02:00
Jeppe Bundgaard 00a8723347 Integrate Coolify API client and module for managing Coolify services, enhancing automation and deployment processes. 2026-05-19 13:17:07 +02:00
Jeppe Bundgaard ab31cd6dbb Enhance MinIO handling in replication management and update legacy test bootstrap. Add MinIO replication logic, legacy setup cleanup, and include necessary tests for improved MinIO interaction and error tolerance. 2026-05-18 14:12:03 +02:00
Jeppe Bundgaard 3261ed8414 Refactor employee name handling to utilize workfeed_employee_name_formatter for improved name resolution and fallback logic 2026-05-18 10:59:23 +02:00
Jeppe Bundgaard f53b99ad94 Implement replication management endpoints and enhance application write freeze handling 2026-05-18 09:59:15 +02:00
Jeppe Bundgaard 0399cb4bb4 Implement economic config round-trip test and enhance department handling
- Added a test to ensure correct round-tripping of default distribution department config value through economic config updates.
- Improved department handling by adding fallback logic to use the default economic distribution department id when a customer's department id is missing.
- Enhanced weather API routes to fetch, cache, and return detailed employee contributions per department for a given time slot.
2026-05-13 17:37:49 +02:00
Jeppe Bundgaard 30ed01e717 Refactor transaction handling and improve shift time logic
Introduced a new `getAllTransactionIds` utility function to better handle filtering of transaction IDs. Replaced outdated `start`/`end` time properties with `checkIn`/`checkOut` objects for shift records, alongside added validation in tests to exclude shifts without punches. Enhanced invoicing tests to ensure flags remain visible even for excluded transactions.
2026-05-13 12:19:07 +02:00
Jeppe Bundgaard 5965c5d72d Implement invoice period warming queue handling with Redis interface
- Added methods `enqueueInvoicePeriodWarming` and `consumeInvoicePeriodWarmingQueue` to the `Redis` interface for managing warming periods.
- Modified `invoice_period_flag_service` to enqueue warming periods on cache misses.
- Updated cron job logic to process invoice period warming queues and ensure flags are warmed effectively.
2026-05-12 16:01:55 +02:00
Jeppe Bundgaard 5f36939833 Refactor error handling for invoice period flag and item row fetching, improve cURL timeout
- Simplify error handling in `invoice_period_flag_service` by directly returning empty arrays on exceptions, removing redundant cache warm-up logic.
- Increase `CURLOPT_TIMEOUT` to 30 in `economic_m.php` for more reliable network requests.
- Adjust unit tests to reflect updated cURL timeout value.
2026-05-12 15:30:25 +02:00
Jeppe Bundgaard c343b52b57 Improve cache handling with on-demand cache warm-up and increase cURL timeout
- Implement on-demand warming of manual and automatic flags cache in `invoice_period_flag_service` to handle cache misses effectively.
- Extend cURL timeout in `economic_endpoint_t` for improved reliability in network requests.
2026-05-12 15:12:59 +02:00
Jeppe Bundgaard d9813a3fe2 Add caching methods for invoice period flags and cron jobs for warming caches
- Introduced methods for caching, retrieving, and clearing manual and automatic invoice period flags, as well as order item rows, using the Redis interface.
- Implemented `warmManualFlagsCache` and `warmAutomaticFlagsForPeriod` methods in `invoice_period_flag_service` to enhance performance by loading flags and order items into cache.
- Added new cron jobs `WarmInvoicePeriodManualFlagsCron` and `WarmInvoicePeriodAutomaticFlagsCron` to regularly update cached data for improved access speeds.
2026-05-12 14:04:51 +02:00
Jeppe Bundgaard c8aba05bc1 Add dynamic COMPOSE_PROJECT_NAME and container naming conventions to CI workflows
- Updated `tests.yml` to set `COMPOSE_PROJECT_NAME` dynamically based on `github.run_id` and `github.run_attempt`.
- Updated `docker-compose.ci.yml` to use dynamic container names with `COMPOSE_PROJECT_NAME`.
2026-05-12 05:18:32 +02:00
Jeppe Bundgaard 08ecd237b4 Escape ${} syntax in GitHub Actions debug database configuration to prevent variable interpolation issues. 2026-05-12 04:53:09 +02:00
Jeppe Bundgaard 9d608bc967 Add debug database configuration to GitHub Actions workflows for improved testing 2026-05-12 04:37:23 +02:00
Jeppe Bundgaard dbc195c31d Remove edge-broker service binding from Traefik configuration files 2026-05-12 04:30:55 +02:00
Jeppe Bundgaard aaae6c9536 Handle null connection and suppress exceptions in DB close method 2026-05-12 04:21:45 +02:00
Jeppe Bundgaard 6c40810caf Add unit tests for invoicing period pagination, normalization, and filtering logic
- Implemented `InvoicingPeriodPaginationTest` for testing period pagination modes, normalization of options, search functionality, and visibility filters.
- Added comprehensive tests to validate scenarios such as active period views, exact counts, and customer-card level search.
- Improved cURL timeout settings with `CURLOPT_CONNECTTIMEOUT` and `CURLOPT_TIMEOUT` adjustments.
- Introduced and documented helper classes/methods for local caching, pagination response structure, and customer name retrieval.
2026-05-12 03:37:47 +02:00
Jeppe Bundgaard 70080086da Add unit tests for Redis namespace safety, MotorAPI cache functionality, and configuration classes, alongside implementation of xlvask_automation_service
- Added tests to ensure Redis namespace safety for `db_object_t` and `users_o`.
- Implemented `MotorApiCachedResultTest` to validate metadata caching behavior.
- Introduced configuration classes for `xlvask_automatic_order_attachment_enabled` and `xlvask_automatic_order_creation_enabled`.
- Developed `xlvask_automation_service` with supporting features for usage log evaluation, suggestion building, and order automation.
2026-05-12 00:22:27 +02:00
Jeppe Bundgaard c1b66a81cc Add invoice_period_flag_ classes to manage invoice period flags with schema, services, and flag lifecycle methods
- Introduced `invoice_period_flag_schema_bootstrap` to initialize the schema for invoice period flags.
- Added `invoice_period_flag_service` to handle manual and automatic flag creation, updates, filtering, and context resolution.
- Implemented lifecycle methods such as `createManualFlag`, `updateAutomaticFlagStatus`, and `applyFlagsToPeriodTypes` for handling invoice period flags and their usage in processing periods.
- Included context-specific resolution methods for efficient flag management in invoicing workflows.
2026-05-11 21:34:57 +02:00
Jeppe Bundgaard 6d4066be1c Add unit tests for InvoicingPeriodDraftOverlay and reference suggestion logic, including fake DB integration and aggregation methods
- Implemented `InvoicingPeriodDraftOverlayTest` with coverage for blocking and permitting invoicing actions based on draft states, transactions, and metadata.
- Created `ReferenceSuggestionsApiTest` to validate ranked and filtered suggestions across bookings, orders, and vehicles with varied match relevance, context, and frequency.
- Added `order_reference_suggestions_service` class, including query methods, normalization utilities, and aggregation logic for reference suggestions.
- Enhanced query handling in `InvoicingPeriodDraftOverlayFakeDb` to validate SQL constraints and column cache resets in overlapping invoicing contexts.
2026-05-11 18:18:08 +02:00
Jeppe Bundgaard bea7e5697b Handle empty inputs in Redis and database operations, improve safety seal validation, and enhance related tests
- Return empty arrays for empty inputs in Redis `mget`, `db_object_t`, and `users_o` operations.
- Refactor safety seal validation logic to handle numeric strings and improve clarity.
- Add unit and API tests to verify handling of empty inputs and numeric safety seal strings.
2026-05-11 04:36:11 +02:00
Jeppe Bundgaard 59a6297925 Refactor booking completion logic to improve wash certificate handling
- Replaced `orderWasCreatedDuringCompletion` flag with optimized checks for wash certificate attachment.
- Updated method signatures to use nullable `safety_seal` parameter for consistency.
- Enhanced `completeBooking` logic to prevent duplicate wash certificate creation or sending.
- Added `hasWashCertificateAttached` method to streamline order checks and improve clarity.
- Updated tests to cover edge cases for wash certificate attachment and email dispatch behavior.
2026-05-11 01:56:46 +02:00
Jeppe Bundgaard 0cbc3e9aa5 Add support for archived departments with schema updates, API integration, and filtering logic
- Added `archived` column and index to `departments` table, ensuring schema initialization via `departments_schema_bootstrap`.
- Updated OpenAPI spec to include `archived` attribute and `filters=archived` query parameter with superuser access control.
- Enhanced `Departments` API to support archived department filtering and retrieval.
- Modified `ApiFixtures`, `departments_o`, and related tests to validate behavior for archived departments.
- Added unit and API tests to ensure correct handling of archived departments and filter enforceability.
2026-05-07 13:50:32 +02:00
Jeppe Bundgaard a4c2b4e95a Add BrandingApiTest to validate branding CRUD operations, permissions, and department assignments. 2026-05-07 10:44:15 +02:00
Jeppe Bundgaard 22fcb5cd0e - Refactor machine_1 drawing logic: optimize highlighted button rendering and deferred processing.
- Add branding management feature: API routes, payload handling, and OpenAPI schema updates.
- Implement department branding logic: CRUD operations, validation, and permissions.
- Add order deletion confirmation support with conflict handling and OpenAPI schema updates.
- Enhance tests and API methods for improved order handling and branding workflows.
2026-05-07 10:44:00 +02:00
Jeppe Bundgaard a71bde3211 Remove legacy booking completion forms and related logic
- Deleted `complete_booking_f` and `generate_booking_wash_certificate_f` classes.
- Updated tests to ensure legacy booking completion routes are disabled.
- Introduced tests for POST `/order-bookings/complete` to enforce POS-based booking completion management.
- Added `/collected-invoices/split-by-month` route with API and unit tests for splitting collections into monthly periods.
- Refactored impacted files to exclude legacy references and ensure continued compatibility with POS processes.
2026-05-06 14:02:48 +02:00
Jeppe Bundgaard 8bf957b273 Add support for selfserve_enabled lanes and synchronize behavior across tasks, sessions, and projections
- Introduced `selfserve_enabled` property for `department_lanes` with schema update, object properties, and associated methods/tests.
- Enhanced `selfserve_wash_flow` and session logic to respect lane self-serve settings, including block handling and task filtering.
- Updated API routes and Studio Graph projections to include `selfserve_enabled` in payloads and progress callbacks.
- Added unit tests for session statuses, lane configuration, and blocking behavior due to disabled self-serve settings.
2026-04-29 17:28:42 +02:00
Jeppe Bundgaard 5875371d13 Add attachment payload handling and tests for self-serve tasks
- Introduced `selfserve_task_attachment_payloads` class for managing task attachments, including formatting and download URL generation.
- Added unit and API tests to validate attachment handling in self-serve tasks and customer-scoped workflows.
- Enhanced wash start simulation and studio graph projections to integrate task attachment data.
2026-04-29 16:03:03 +02:00
Jeppe Bundgaard 93cac644a1 Introduce selfserve_studio_action_runner and related classes for configurable Studio action workflows
- Added `selfserve_studio_action_runner` to manage Studio action execution, including conditional validation, retry mechanisms, and operation dispatching.
- Introduced `selfserve_studio_actions` to define action constants, normalize configurations, and validate operations and policies.
- Updated `selfserve_config_versioning` to support action nodes, including validation hooks, schema migration normalization, and legacy action parsing.
- Enhanced `SelfserveStudioGraphTest` and `SelfserveStudioDebugPayload` tests to validate action serialization and runtime signal processing.
- Added test cases for event-driven Studio actions and non-blocking configuration warnings.
2026-04-29 10:57:33 +02:00
Jeppe Bundgaard eeccacb2a7 Add unit tests for department lane dynamic image overrides and introduce classes for self-serve signal and virtual hardware management
- Add `DepartmentLaneDynamicImageRouteTest` to verify dynamic image preview handling for studio lanes.
- Introduce `selfserve_machine_signal` class to standardize signal normalization, recording, and gateway signal management workflows.
- Add `selfserve_virtual_hardware` class to handle virtual hardware configurations, including gateway and binding management.
- Enhance structure with auxiliary methods for payload normalization, workspace merging, and validation warnings.
2026-04-29 09:52:51 +02:00
Jeppe Bundgaard 690e114d38 Add tests for customer-scoped vehicle conditions and property gate permissions
- Introduced tests for `SelfserveNonOwnedVehicleWashAccess` to validate customer-scoped conditions for non-owned vehicles.
- Added `SelfservePropertyGatePermissionBypassTest` to ensure proper permission handling for lanes and departments.
- Updated `department_selfserve_vehicle_conditions_o` and routes to prevent cross-customer answer persistence.
- Enhanced `selfserve_wash_flow` with customer-scoped persisted answer logic and improved method parameters for vehicle eligibility and session synchronization.
- Adjusted OpenAPI spec and unit tests to reflect new customer-scoping behavior in self-serve operations.
2026-04-28 17:59:33 +02:00
Jeppe Bundgaard 4dd00cd7a2 Add tests for handling ambiguous timeout errors and deferred relay side effects in Self-serve entrance start logic
- Introduced `SelfserveLaneStartEntranceTimeoutHarness` class and supporting tests to validate ambiguous relay timeout handling during entrance operations.
- Added `defer_relay_side_effects` parameter to `selfserve_lane_command_arguments` for improved relay control during wash start.
- Enhanced lane start routine to support conditional relay side effects and timeout handling with detailed logging.
2026-04-28 17:10:04 +02:00
Jeppe Bundgaard acdff75311 Add requestBooleanFlag helper and enhance session synchronization logic
- Introduce `requestBooleanFlag` method for consistent boolean parameter handling with default values.
- Add `activate_machine` and `sync_relay_state` parameters to `synchronizeSession` for more flexible relay and machine activation control.
- Update methods, routes, and tests to integrate the new session synchronization parameters effectively.
- Enhance debugging support with additional metadata in simulation and payload captures.
2026-04-28 16:54:57 +02:00
Jeppe Bundgaard 5b94c9407b Add service and role properties to task and binding nodes in Self-serve Studio Graph tests 2026-04-28 15:42:19 +02:00
Jeppe Bundgaard 206b487fa2 Add new table and enhance studio layout logic
Introduce `department_selfserve_studio_layouts` table for department-specific layouts and implement advanced auto-layout functionality in the DepartmentSelfServeStudio module. Added custom node definitions, updated styling, and integrated new logics for sorting and visualizing nodes in the Vue Flow interface.
2026-04-28 14:21:31 +02:00
Jeppe Bundgaard 72df2244ca Add self-serve API fixtures and enhance session synchronization logic
- Implement `createSelfServeScenario` to generate comprehensive self-serve test fixtures, including departments, lanes, tasks, and sessions.
- Add `syncRelayState` parameter to `synchronizeSession` for decoupled relay hardware synchronization.
- Update relevant routes and tests to reflect changes in session synchronization methods.
- Enhance Shelly request handling by blocking real device interactions in test mode with detailed logging.
2026-04-28 12:28:29 +02:00
Jeppe Bundgaard 55f8f25fd7 Add broker diagnostics and enhance relay logging
- Implement `diagnoseBrokerConfiguration` to validate broker URLs, shared secrets, and connection health.
- Add diagnostic methods for shared secret validation, including legacy sync support.
- Extend relay logging with descriptive context (`relay_name`, `relay_role`) and dynamic messaging.
- Update tests to cover broker health and shared secret diagnostics.
2026-04-28 11:30:09 +02:00
Jeppe Bundgaard c73e459d26 Add edge gateway broker configurations and session management routes
- Add broker-related configuration classes (`broker_url`, `public_broker_url`, `auth_mode`, `shared_secret`) to support edge gateway functionality.
- Enhance `SelfserveRoute` with routes for managing self-serve wash sessions, including session listing, detail retrieval, and forced lane stop.
- Update unit tests to validate new configuration handling, session routes, and OpenAPI endpoint coverage.
- Include default environment variables for broker settings in `docker-compose.example.yml`.
2026-04-28 10:04:17 +02:00
Jeppe Bundgaard 2aded0812a Add new tests for shell bridge and broker to handle structured failures and invalid session handling
- Add tests for shell bridge to validate structured error reporting on spawn failures.

- Add broker tests to ensure proper rejection of malformed browser shell upgrades without leaking sensitive tokens.

- Update `.env.example` with `EDGE_PUBLIC_BROKER_URL` for public access configuration.
2026-04-28 09:00:12 +02:00
Jeppe Bundgaard e93d3f30d2 Add support for Shelly device generation detection and update related tests
- Implement generation detection logic for Shelly devices using model codes, metadata, and type inference.
- Extend relay switch and inventory handling to include generation capabilities.
- Ensure compatibility with Gen1, Gen2, and Gen3 devices for relay control and diagnostics.
- Update unit tests to validate generation inference, fallback behavior, and API compatibility.
2026-04-27 17:40:39 +02:00
Jeppe Bundgaard ae5cb7c65f Add toggle_after timer support for relay switches and update unit tests
- Extend relay switch logic to include `toggle_after` parameter for timed toggles.
- Update unit tests in `SelfserveLanePortControllerTest` and `SelfserveRouteWiringTest` to validate timer behavior.
- Adjust Shelly API calls and assertions to handle timer values in both RPC and legacy endpoints.
2026-04-27 17:15:53 +02:00
Jeppe Bundgaard feb9aac4f7 Handle relay command job timeouts for edge gateways
- Introduce `expireTimedOutRelayStatusCommandJobs` to clean up long-pending relay status command jobs.
- Add `TIMED_OUT` status for relay command jobs and incorporate it into job status evaluations.
- Refactor command job finalization to support timeout-specific error messaging.
- Improve handling of fast-path failures in edge broker commands.
2026-04-27 16:47:09 +02:00
Jeppe Bundgaard 86eec9a51e Add timer support for relay switch commands and update tests
- Introduce `dispatchRelaySwitchWithTimer` and `dispatchRelaySwitchLocalOnlyWithTimer` methods for timed relay control.
- Extend `dispatchRelaySwitchWithOptions` to handle `toggleAfterSeconds` parameter.
- Update tests to validate timer functionality for local Shelly APIs.
- Ensure backwards compatibility with legacy APIs and adjust payloads accordingly.
2026-04-27 16:19:14 +02:00
Jeppe Bundgaard f2e2b9a8f4 Update relay-handling logic and test assertions for device binding and local IP resolution
- Correct test cases to ensure proper relay IDs are switched.
- Add robust local IP resolution for relay-device bindings, including caching and inventory backfill.
- Introduce fast-path options for relay status and switch dispatch.
- Validate PHP extensions (`curl`, `sqlite3`) in edge agent images.
2026-04-27 16:02:36 +02:00
Jeppe Bundgaard 7bd940de37 Improve browser-shell session closure handling in edge-broker tests and server 2026-04-27 12:15:46 +02:00
Jeppe Bundgaard c95fd3a23c Add fallback handling for telemetry ingestion failures in broker and HTTP persistence check for control plane events 2026-04-27 11:52:38 +02:00
Jeppe Bundgaard 99a85878cd Handle orphaned edge gateways for non-existent departments and improve error handling 2026-04-27 10:07:48 +02:00
Jeppe Bundgaard 1dffbe16c4 Restore self-hosted CI runners 2026-04-24 21:19:05 +02:00
Jeppe Bundgaard ad9637ebd2 Run CI on GitHub-hosted runners 2026-04-24 21:17:22 +02:00
Jeppe Bundgaard 0253cfc676 Copy edge E2E runner config in CI 2026-04-24 21:11:08 +02:00
Jeppe Bundgaard 15250ad227 Run edge E2E smoke on compose network 2026-04-24 21:04:53 +02:00
Jeppe Bundgaard ca78e8e5f3 Use internal API route for edge E2E in CI 2026-04-24 20:52:29 +02:00
Jeppe Bundgaard ab8923bb77 Attach CI runner to edge E2E network 2026-04-24 20:47:18 +02:00
Jeppe Bundgaard 0f37bdc288 Resolve fixed-name Traefik in E2E smoke 2026-04-24 20:36:09 +02:00
Jeppe Bundgaard cdbc976b8a Use Traefik container IP for edge E2E 2026-04-24 20:31:55 +02:00
Jeppe Bundgaard 288627243b Resolve edge E2E host routing in CI 2026-04-24 20:27:44 +02:00
Jeppe Bundgaard ebe1299089 Fix PHP unit test isolation 2026-04-24 20:17:01 +02:00
Jeppe Bundgaard 3adf5957f0 Use Docker host gateway for edge E2E 2026-04-24 20:07:05 +02:00
Jeppe Bundgaard eba33d0735 Use CI volume for PHP app tests 2026-04-24 20:00:42 +02:00
Jeppe Bundgaard def45b1c23 Clean stale PHP files before CI sync 2026-04-24 19:56:13 +02:00
Jeppe Bundgaard 38481334c7 Sync PHP checkout into CI containers 2026-04-24 19:52:36 +02:00
Jeppe Bundgaard dce2207243 Pin compose file for edge gateway CI 2026-04-24 19:44:03 +02:00
Jeppe Bundgaard 5bfd2743be Use explicit edge gateway API test paths 2026-04-24 19:35:11 +02:00
Jeppe Bundgaard 38acbdefa0 Run edge gateway CI suites directly with Pest 2026-04-24 19:27:57 +02:00
Jeppe Bundgaard 64292e8761 Use composer run-script for edge gateway suites 2026-04-24 19:23:19 +02:00
Jeppe Bundgaard 5001cd1811 Install PHP dev dependencies before edge gateway CI tests 2026-04-24 19:21:23 +02:00
Jeppe Bundgaard cef53d8ebd Fix compose config test service parsing 2026-04-24 19:13:41 +02:00
Jeppe Bundgaard ca64f2b047 Merge remote-tracking branch 'origin/run-tests-and-fix-identified-issues' 2026-04-24 19:10:55 +02:00
Jeppe Bundgaard ed13f7a8d4 Add guarded legacy route shims for Edge Gateway with corresponding unit tests 2026-04-24 15:51:09 +02:00
Jeppe B 2b905df478 Fix Caddy compose mounts for self-hosted Docker runtime 2026-04-24 00:19:13 +02:00
Jeppe B d9ed1ff9de Harden self-hosted CI workflow reliability 2026-04-24 00:10:10 +02:00
Jeppe B 1c647bb78b Avoid Traefik port collisions in edge gateway CI 2026-04-23 23:52:01 +02:00
Jeppe B 39d4ba0284 Run unit CI inside php1 container on self-hosted runner 2026-04-23 23:40:12 +02:00
Jeppe B 8a5e6bc302 Prepare Qodana temp directories on self-hosted runner 2026-04-23 23:35:13 +02:00
Jeppe B e7220f4f29 Run Qodana workflow on self-hosted runner 2026-04-23 23:16:45 +02:00
Jeppe B 54bada14f5 Fix edge agent shell polling and heartbeat metadata 2026-04-23 23:13:44 +02:00
790 changed files with 294392 additions and 9237 deletions
+7 -1
View File
@@ -1 +1,7 @@
/docker-compose.yml
/docker-compose.yml
# Runtime-generated replication bootstrap snapshots may contain infrastructure
# metadata and encrypted/plaintext credential material. They must be
# supplied at runtime via mounted storage, not baked into deployment images.
/services/nginx/app/storage/replication-bootstrap.json
/services/nginx/app/storage/replication-bootstrap-*.json
+6 -1
View File
@@ -31,6 +31,9 @@ CONFIG_DB_DATABASE=
CONFIG_DB_PORT=3306
CONFIG_DB_SSL_MODE=DISABLED
# Browser origins allowed to call the API. Path-like entries are normalized to origins by the PHP CORS policy.
CORS=https://truckwash.io,https://www.truckwash.io,https://api.truckwash.io,https://api.truckwash.io:4433,https://api-v2.truckwash.io,https://web.truckwash.dk,https://api.truckwash.dk,https://truckwash.dk,https://www.truckwash.dk,https://staging.truckwash.io,http://localhost,https://localhost,http://localhost:4433,https://localhost:4433,https://twdev.jeppeb.dk,http://localhost:5173
# Debug DB credentials (used when CONFIG_DB_TARGET=debug)
# Any blank debug value falls back to the live value above.
CONFIG_DB_DEBUG_HOST=mysql-debug
@@ -49,7 +52,9 @@ ECONOMIC_API_APP_SECRET_TOKEN=
# Edge broker defaults for shell relay and gateway dispatch.
EDGE_BROKER_URL=http://edge-broker:4300
EDGE_BROKER_SHARED_SECRET=truckwash-edge-dev
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
EDGE_AUTH_MODE=strict
EDGE_BROKER_SHARED_SECRET=
# Redis credentials
REDIS_CONFIG_HOST=redis
-42
View File
@@ -1,42 +0,0 @@
USE_ENV=true
# Target of the database connection. Can be either 'live' or 'debug'.
CONFIG_DB_TARGET=live
CONFIG_DB_DATABASE=nnks_db
#CONFIG_DB_HOST=94.130.142.41
CONFIG_DB_HOST=23.88.23.183
CONFIG_DB_PASSWORD=562X0Lrr7Cz6zpXZ11I
CONFIG_DB_USER=root
CONFIG_DB_PORT=5432
CONFIG_DB_DEBUG_DATABASE=nnks_db
CONFIG_DB_DEBUG_HOST=23.88.23.183
CONFIG_DB_DEBUG_PORT=5432
CONFIG_DB_DEBUG_PASSWORD=562X0Lrr7Cz6zpXZ11I
CONFIG_DB_DEBUG_USER=root
CONFIG_TIMEZONE=Europe/Copenhagen
CORS=https://truckwash.io,https://www.truckwash.io,https://api.truckwash.io,https://api.truckwash.io:4433,https://web.truckwash.dk,https://api.truckwash.dk,https://truckwash.dk,https://www.truckwash.dk,https://staging.truckwash.io,http://localhost,https://localhost,http://localhost:4433,https://localhost:4433,https://twdev.jeppeb.dk,http://localhost:5173
# CORS=*
DEBUG=false
ECONOMIC_API_APP_ACCESS_GRANT=94bhkmdtaDA7kVn9abF2SGDccBDMvk5a6iWYnmJMbvQ1
ECONOMIC_API_APP_ACCESS_GRANT2=qGSBSkh1pjBtdSOygHhaMPn1A4PcMto3sCDCGYpLmsg1
ECONOMIC_API_APP_SECRET_TOKEN=V8GSEcIxMsTISczzTTBbOAMJyh8eucGZtBiGOxjMFg0
EMAIL_WASH_CERTIFICATE_TOKEN=H7uDTtFaeN4asqpb5okh6dr8z209SGtt
ENCRYPTION_KEY=Gvm37uF2VyTOjGkVl4kjrGQ0qRwOyq9lr3+p/QyUDjc\\=
MINIO_ACCESS_KEY=d7u6RaFyYmckAIWYGUYr
MINIO_ENDPOINT=http://162.55.225.220:9000
MINIO_SECRET_KEY=a2wJUQfkOPNO3UJfXYIdpNq4r1RrthcjiUfW1gVS
REDIS_CONFIG_DATABASE=0
REDIS_CONFIG_HOST=23.88.23.183
REDIS_CONFIG_PASSWORD=BlVg5o1NwkkR1IjKxQm
REDIS_CONFIG_PORT=5433
REDIS_CONFIG_USER=default
REDIS_CONFIG_DEBUG_PORT=5433
REDIS_CONFIG_DEBUG_USER=default
SLACK_DEFAULT_WEBHOOK=https://hooks.slaCk.com/services/T05SRKWTX9C/B08AGMP459P/1W5JN1NpHsHlbHHM2WljpvrU
WORDPRESS_API_URL=https://www.truckwash.dk/wp-admin/admin-ajax.php
WORDPRESS_STATIC_TOKEN=earm8BX4MFTgS6JCNQdqW5EzHUutv2Vx
ELASTIC_APM_SERVER_URL=http://elastic-agent:8200
ELASTIC_APM_SECRET_TOKEN=apm_dev_token
ELASTIC_APM_SERVICE_NAME=api-truckwash
ELASTIC_APM_ENVIRONMENT=dev
AUTO_COMPOSER_INSTALL=false
+15
View File
@@ -0,0 +1,15 @@
* text=auto eol=lf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.woff binary
*.woff2 binary
*.ttf binary
*.otf binary
*.pdf binary
*.zip binary
*.webm binary
+56
View File
@@ -0,0 +1,56 @@
# Default branch protection
`master` is changed through pull requests. Do not push or publish directly to
the default branch, including through automation or the Git Data API.
## Normal publishing flow
1. Create a scoped `agent/*` or feature branch from the current `origin/master`.
2. Commit and push only the intended changes.
3. Open a pull request targeting `master`.
4. Wait for the `Required CI` check. If `master` moves, update the branch and
wait for the strict check to rerun.
5. Resolve every review conversation and squash-merge the pull request.
6. Confirm the post-merge `Release Manager gate` completes on `master`.
The aggregate check covers the PHP unit, integration, API, and legacy matrix,
plus Edge Agent, Edge Broker, and Edge Gateway Backend. Qodana is advisory and
the Release Manager gate is intentionally post-merge.
## Desired ruleset
[`rulesets/protect-default-branch.json`](rulesets/protect-default-branch.json)
is the importable final desired-state repository-ruleset request body. For the
initial POST, copy the file and override `enforcement` to `disabled`. Inspect
the normalized ruleset and verify a green preparation PR and post-merge run,
then PUT the exact committed file to activate it.
The desired rule targets `~DEFAULT_BRANCH`, requires pull requests with zero
approvals, conversation resolution, strict `Required CI` from GitHub Actions
integration `15368`, squash-only linear history, and blocks deletion and force
pushes. Repository administrators receive pull-request-only bypass; they do not
receive a standing direct-push bypass.
When the ruleset is activated, align repository settings at the same time:
retain squash merging, disable merge commits and rebase merging, enable
auto-merge and branch-update suggestions, delete merged branches automatically,
keep the Actions token read-only, and prevent Actions from approving reviews.
## Activation record
Repository ruleset `19041620` was activated on 2026-07-16 after preparation
PR #311 passed `Required CI` and the merged `master` commit passed both
`Required CI` and the `Release Manager gate`. This documentation update is
the after-activation canary for the normal protected pull-request path.
## Break glass
When an incident cannot wait for the normal gate:
1. Open a pull request and describe the incident, risk, and reason for bypass.
2. Have a repository administrator use the pull-request-only bypass.
3. Monitor `Required CI` and the post-merge Release Manager workflow.
4. Open a follow-up pull request for any deferred validation or remediation.
Never bypass by updating `refs/heads/master` directly. Ruleset changes and
emergency bypasses must remain visible in GitHub's audit trail.
+48
View File
@@ -0,0 +1,48 @@
USE_ENV=true
DEBUG=true
ENCRYPTION_KEY=ci-test-encryption-key
CORS=*
CONFIG_TIMEZONE=Europe/Copenhagen
CONFIG_DB_TARGET=debug
CONFIG_DB_HOST=mysql-debug
CONFIG_DB_USER=root
CONFIG_DB_PASSWORD=debug_root_password
CONFIG_DB_DATABASE=nnks_db_debug
CONFIG_DB_PORT=3306
CONFIG_DB_SSL_MODE=DISABLED
CONFIG_DB_DEBUG_HOST=mysql-debug
CONFIG_DB_DEBUG_USER=root
CONFIG_DB_DEBUG_PASSWORD=debug_root_password
CONFIG_DB_DEBUG_DATABASE=nnks_db_debug
CONFIG_DB_DEBUG_PORT=3306
CONFIG_DB_DEBUG_SSL_MODE=DISABLED
REDIS_CONFIG_HOST=redis
REDIS_CONFIG_USER=default
REDIS_CONFIG_DATABASE=0
REDIS_CONFIG_PASSWORD=
REDIS_CONFIG_PORT=6379
REDIS_CONFIG_DEBUG_HOST=redis
REDIS_CONFIG_DEBUG_USER=default
REDIS_CONFIG_DEBUG_DATABASE=0
REDIS_CONFIG_DEBUG_PASSWORD=
REDIS_CONFIG_DEBUG_PORT=6379
ECONOMIC_API_APP_ACCESS_GRANT=ci-test
ECONOMIC_API_APP_ACCESS_GRANT2=ci-test-secondary
ECONOMIC_API_APP_SECRET_TOKEN=ci-test-secret
WORDPRESS_STATIC_TOKEN=ci-test
EMAIL_WASH_CERTIFICATE_TOKEN=ci-test
WORDPRESS_API_URL=http://localhost
MINIO_ENDPOINT=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
SLACK_DEFAULT_WEBHOOK=
EDGE_BROKER_URL=http://edge-broker:4300
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
EDGE_AUTH_MODE=manager
EDGE_BROKER_SHARED_SECRET=truckwash-edge-ci
EDGE_GATEWAY_VIEW_CACHE_TTL=0
TRUCKWASH_TEST_BLOCK_REAL_SHELLY=1
+48
View File
@@ -0,0 +1,48 @@
USE_ENV=true
DEBUG=true
ENCRYPTION_KEY=ci-test-encryption-key
CORS=*
CONFIG_TIMEZONE=Europe/Copenhagen
CONFIG_DB_TARGET=debug
CONFIG_DB_HOST=mysql-debug
CONFIG_DB_USER=root
CONFIG_DB_PASSWORD=debug_root_password
CONFIG_DB_DATABASE=nnks_db_debug
CONFIG_DB_PORT=3306
CONFIG_DB_SSL_MODE=DISABLED
CONFIG_DB_DEBUG_HOST=mysql-debug
CONFIG_DB_DEBUG_USER=root
CONFIG_DB_DEBUG_PASSWORD=debug_root_password
CONFIG_DB_DEBUG_DATABASE=nnks_db_debug
CONFIG_DB_DEBUG_PORT=3306
CONFIG_DB_DEBUG_SSL_MODE=DISABLED
REDIS_CONFIG_HOST=redis
REDIS_CONFIG_USER=default
REDIS_CONFIG_DATABASE=0
REDIS_CONFIG_PASSWORD=
REDIS_CONFIG_PORT=6379
REDIS_CONFIG_DEBUG_HOST=redis
REDIS_CONFIG_DEBUG_USER=default
REDIS_CONFIG_DEBUG_DATABASE=0
REDIS_CONFIG_DEBUG_PASSWORD=
REDIS_CONFIG_DEBUG_PORT=6379
ECONOMIC_API_APP_ACCESS_GRANT=ci-test
ECONOMIC_API_APP_ACCESS_GRANT2=ci-test-secondary
ECONOMIC_API_APP_SECRET_TOKEN=ci-test-secret
WORDPRESS_STATIC_TOKEN=ci-test
EMAIL_WASH_CERTIFICATE_TOKEN=ci-test
WORDPRESS_API_URL=http://localhost
MINIO_ENDPOINT=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
SLACK_DEFAULT_WEBHOOK=
EDGE_BROKER_URL=http://edge-broker:4300
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
EDGE_AUTH_MODE=manager
EDGE_BROKER_SHARED_SECRET=truckwash-edge-ci
EDGE_GATEWAY_VIEW_CACHE_TTL=0
TRUCKWASH_TEST_BLOCK_REAL_SHELLY=1
+90
View File
@@ -0,0 +1,90 @@
services:
traefik:
container_name: "${COMPOSE_PROJECT_NAME:-api}-traefik"
redis:
container_name: "${COMPOSE_PROJECT_NAME:-api}-redis"
mysql-debug:
container_name: "${COMPOSE_PROJECT_NAME:-api}-mysql-debug"
ports: !reset []
edge-broker:
container_name: "${COMPOSE_PROJECT_NAME:-api}-edge-broker"
ports:
- "127.0.0.1:${EDGE_BROKER_CI_PORT:-14300}:4300"
labels:
- "traefik.http.routers.edge-broker-local-ci.rule=PathPrefix(`/api/edge-broker`)"
- "traefik.http.routers.edge-broker-local-ci.entrypoints=web"
- "traefik.http.routers.edge-broker-local-ci.middlewares=secure-headers@file,edge-broker-strip-local"
- "traefik.http.routers.edge-broker-local-ci.priority=190"
- "traefik.http.routers.edge-broker-local-ci.service=edge-broker"
caddy:
container_name: "${COMPOSE_PROJECT_NAME:-api}-caddy"
depends_on: !reset []
labels:
- "traefik.http.routers.local-api-ci.rule=PathPrefix(`/api`)"
- "traefik.http.routers.local-api-ci.entrypoints=web"
- "traefik.http.routers.local-api-ci.middlewares=strip-api-prefix@file,secure-headers@file"
- "traefik.http.routers.local-api-ci.priority=90"
- "traefik.http.routers.local-api-ci.service=caddy"
volumes:
- ci_php_app:/var/www/html
php1:
container_name: "${COMPOSE_PROJECT_NAME:-api}-php1"
depends_on: !reset []
environment:
AUTO_COMPOSER_INSTALL: "false"
USE_ENV: "true"
CONFIG_DB_TARGET: "debug"
CONFIG_DB_HOST: "mysql-debug"
CONFIG_DB_USER: "root"
CONFIG_DB_PASSWORD: "debug_root_password"
CONFIG_DB_DATABASE: "nnks_db_debug"
CONFIG_DB_PORT: "3306"
CONFIG_DB_DEBUG_HOST: "mysql-debug"
CONFIG_DB_DEBUG_USER: "root"
CONFIG_DB_DEBUG_PASSWORD: "debug_root_password"
CONFIG_DB_DEBUG_DATABASE: "nnks_db_debug"
CONFIG_DB_DEBUG_PORT: "3306"
REDIS_CONFIG_HOST: "redis"
REDIS_CONFIG_PORT: "6379"
REDIS_CONFIG_DATABASE: "0"
REDIS_CONFIG_DEBUG_HOST: "redis"
REDIS_CONFIG_DEBUG_PORT: "6379"
REDIS_CONFIG_DEBUG_DATABASE: "0"
TRUCKWASH_TEST_BLOCK_REAL_SHELLY: "1"
EDGE_GATEWAY_VIEW_CACHE_TTL: "0"
volumes:
- ci_php_app:/var/www/html
php2:
container_name: "${COMPOSE_PROJECT_NAME:-api}-php2"
volumes:
- ci_php_app:/var/www/html
php3:
container_name: "${COMPOSE_PROJECT_NAME:-api}-php3"
volumes:
- ci_php_app:/var/www/html
php4:
container_name: "${COMPOSE_PROJECT_NAME:-api}-php4"
volumes:
- ci_php_app:/var/www/html
php5:
container_name: "${COMPOSE_PROJECT_NAME:-api}-php5"
volumes:
- ci_php_app:/var/www/html
volumes:
ci_php_app:
networks:
default:
ipam:
config:
- subnet: "${CI_DOCKER_SUBNET:-10.240.0.0/24}"
@@ -0,0 +1,57 @@
{
"name": "Protect default branch",
"target": "branch",
"enforcement": "active",
"bypass_actors": [
{
"actor_id": 5,
"actor_type": "RepositoryRole",
"bypass_mode": "pull_request"
}
],
"conditions": {
"ref_name": {
"exclude": [],
"include": [
"~DEFAULT_BRANCH"
]
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "required_linear_history"
},
{
"type": "pull_request",
"parameters": {
"allowed_merge_methods": [
"squash"
],
"dismiss_stale_reviews_on_push": false,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_approving_review_count": 0,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"do_not_enforce_on_create": false,
"required_status_checks": [
{
"context": "Required CI",
"integration_id": 15368
}
],
"strict_required_status_checks_policy": true
}
}
]
}
+59 -13
View File
@@ -1,28 +1,74 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
branches:
- master
- beta
- canary
- internal
types:
- opened
- reopened
- synchronize
- ready_for_review
push:
branches: # Specify your branches here
- main # The 'main' branch
- 'releases/*' # The release branches
branches:
- master
- beta
- canary
- internal
concurrency:
group: qodana-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
qodana:
runs-on: ubuntu-latest
name: Qodana
if: >-
github.event_name != 'pull_request' ||
(
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login != 'dependabot[bot]'
)
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: write
pull-requests: write
contents: read
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v3
- name: Require Qodana Cloud token
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
shell: bash
run: |
set -euo pipefail
if [[ -z "${QODANA_TOKEN}" ]]; then
echo "::error::QODANA_TOKEN is not configured for this repository."
exit 1
fi
- name: Check out the analyzed commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2025.3
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Run Qodana
uses: JetBrains/qodana-action@4861e015da555e86a72b862892aba6c2b93e6891 # v2026.1.3
with:
pr-mode: false
pr-mode: ${{ github.event_name == 'pull_request' }}
use-caches: true
cache-default-branch-only: true
use-annotations: true
post-pr-comment: true
github-token: ${{ github.token }}
push-fixes: none
upload-result: false
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
QODANA_ENDPOINT: 'https://qodana.cloud'
+314 -200
View File
@@ -3,69 +3,107 @@ name: Tests
on:
pull_request:
push:
branches:
- master
- beta
- canary
- internal
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
unit:
name: Unit (required)
# Match the labels exposed by the Coolify-managed GitHub runner.
runs-on: [self-hosted, Linux, X64, default]
php:
name: PHP ${{ matrix.suite }} (required)
# Docker jobs use disposable workspaces so root-owned container artifacts cannot poison later checkouts.
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
suite: [unit, integration, api, legacy]
env:
DOCKER_HOST: unix:///var/run/docker.sock
COMPOSE_PROJECT_NAME: php-${{ github.run_id }}-${{ github.job }}-${{ matrix.suite }}-${{ github.run_attempt }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Ensure Docker access
run: |
set -euo pipefail
docker ps >/dev/null 2>&1 || {
echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2
exit 1
}
- name: Setup Node.js
uses: actions/setup-node@v4
if: ${{ matrix.suite == 'unit' }}
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- name: Check AI workflow sync
if: ${{ matrix.suite == 'unit' }}
run: node scripts/sync-ai-workflow.mjs --check
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mysqli, curl, openssl, json, redis, xdebug
coverage: xdebug
ini-values: variables_order=EGPCS,xdebug.mode=coverage
- name: Run PHP ${{ matrix.suite }} suite
run: bash scripts/php-ci-test.sh ${{ matrix.suite }}
- name: Resolve dependencies
working-directory: services/nginx/app
run: composer update --no-interaction --prefer-dist
- name: Run unit tests
working-directory: services/nginx/app
run: composer test:unit
- name: Generate coverage report
working-directory: services/nginx/app
run: composer test:coverage
- name: Upload coverage artifact
if: ${{ github.event_name == 'pull_request' }}
- name: Upload PHP suite logs
if: ${{ failure() }}
continue-on-error: true
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: unit-coverage-clover
path: services/nginx/app/build/logs/clover.xml
name: php-${{ matrix.suite }}-logs
path: .tmp/ci-logs/${{ matrix.suite }}
if-no-files-found: warn
retention-days: 1
retention-days: 3
edge-agent:
name: Edge Agent (required)
runs-on: [self-hosted, Linux, X64, default]
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-24.04' || 'backend' }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: npm
cache-dependency-path: services/edge-agent/package-lock.json
- name: Install native build tools
run: |
set -euo pipefail
if command -v make >/dev/null 2>&1 && command -v g++ >/dev/null 2>&1; then
exit 0
fi
if ! command -v apt-get >/dev/null 2>&1; then
echo "make and g++ are required to install node-pty, but apt-get is not available on this runner." >&2
exit 1
fi
apt_cmd=(apt-get)
if [ "$(id -u)" -ne 0 ]; then
if ! command -v sudo >/dev/null 2>&1; then
echo "make and g++ are missing, and sudo is not available to install them." >&2
exit 1
fi
apt_cmd=(sudo apt-get)
fi
"${apt_cmd[@]}" update
"${apt_cmd[@]}" install -y --no-install-recommends build-essential python3
- name: Install dependencies
working-directory: services/edge-agent
@@ -77,28 +115,29 @@ jobs:
edge-broker:
name: Edge Broker (required)
runs-on: [self-hosted, Linux, X64, default]
runs-on: ubuntu-24.04
env:
DOCKER_HOST: unix:///var/run/docker.sock
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Materialize compose env files
env:
COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }}
COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }}
- name: Ensure Docker access
run: |
set -euo pipefail
if [ -z "${COMPOSE_ENV}" ]; then
echo "Required GitHub secret COMPOSE_ENV is not configured." >&2
docker ps >/dev/null 2>&1 || {
echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2
exit 1
fi
if [ -z "${COMPOSE_ENV_STAGING}" ]; then
echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2
exit 1
fi
printf '%s\n' "$COMPOSE_ENV" > .env
printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging
}
- name: Materialize CI compose env files
run: |
set -euo pipefail
cp .github/ci.env .env
cp .github/ci.env.staging .env.staging
- name: Validate compose contracts
run: |
@@ -106,11 +145,9 @@ jobs:
docker compose -f docker-compose.example.yml config > /dev/null
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: npm
cache-dependency-path: services/edge-broker/package-lock.json
- name: Install dependencies
working-directory: services/edge-broker
@@ -122,183 +159,260 @@ jobs:
edge-gateway-backend:
name: Edge Gateway Backend (required)
runs-on: [self-hosted, Linux, X64, default]
runs-on: ubuntu-24.04
env:
DOCKER_HOST: unix:///var/run/docker.sock
COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml
COMPOSE_PROJECT_NAME: edge-gateway-backend-${{ github.run_id }}-${{ github.run_attempt }}
COMPOSE_PROFILES: dev
TRAEFIK_WEB_PORT: "18080"
TRAEFIK_WEBSECURE_PORT: "18443"
TRAEFIK_WEBSECURE_STAGING_PORT: "18433"
TRAEFIK_METRICS_PORT: "19100"
EDGE_BROKER_CI_PORT: "14300"
EDGE_GATEWAY_E2E_BASE_URL: "http://localhost:18080/api"
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Materialize compose env files
env:
COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }}
COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }}
- name: Ensure Docker access
run: |
set -euo pipefail
if [ -z "${COMPOSE_ENV}" ]; then
echo "Required GitHub secret COMPOSE_ENV is not configured." >&2
docker ps >/dev/null 2>&1 || {
echo "Docker is unavailable to the runner identity. Fix the isolated runner configuration; the workflow will not weaken /var/run/docker.sock permissions." >&2
exit 1
fi
if [ -z "${COMPOSE_ENV_STAGING}" ]; then
echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2
}
- name: Allocate CI ports
run: |
set -euo pipefail
find_free_port() {
start="$1"
end="$2"
port="$start"
while [ "$port" -le "$end" ]; do
if ! ss -H -ltn "sport = :$port" 2>/dev/null | grep -q .; then
echo "$port"
return 0
fi
port=$((port + 1))
done
echo "No free port in range ${start}-${end}." >&2
exit 1
fi
printf '%s\n' "$COMPOSE_ENV" > .env
printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging
}
base=$((20000 + (GITHUB_RUN_ID % 20000)))
web_port="$(find_free_port "$base" "$((base + 2000))")"
websecure_port="$(find_free_port "$((web_port + 1))" "$((web_port + 2000))")"
staging_port="$(find_free_port "$((websecure_port + 1))" "$((websecure_port + 2000))")"
metrics_port="$(find_free_port "$((staging_port + 1))" "$((staging_port + 2000))")"
broker_port="$(find_free_port "$((metrics_port + 1))" "$((metrics_port + 2000))")"
checksum="$(printf '%s' "$COMPOSE_PROJECT_NAME" | cksum | awk '{print $1}')"
subnet_second=$((64 + ((checksum / 256) % 64)))
subnet_third=$((checksum % 256))
ci_docker_subnet="10.${subnet_second}.${subnet_third}.0/24"
{
echo "TRAEFIK_WEB_PORT=${web_port}"
echo "TRAEFIK_WEBSECURE_PORT=${websecure_port}"
echo "TRAEFIK_WEBSECURE_STAGING_PORT=${staging_port}"
echo "TRAEFIK_METRICS_PORT=${metrics_port}"
echo "EDGE_BROKER_CI_PORT=${broker_port}"
echo "CI_DOCKER_SUBNET=${ci_docker_subnet}"
echo "EDGE_GATEWAY_E2E_BASE_URL=http://localhost:${web_port}/api"
echo "EDGE_GATEWAY_E2E_COMPOSE_PROJECT=${COMPOSE_PROJECT_NAME}"
} >> "$GITHUB_ENV"
- name: Materialize CI compose env files
run: |
set -euo pipefail
cp .github/ci.env .env
cp .github/ci.env.staging .env.staging
printf '\nEDGE_PUBLIC_BROKER_URL=http://edge-broker:4300/edge-broker\n' >> .env
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
- name: Boot local stack
run: docker compose up -d traefik redis mysql-debug edge-broker php1 caddy
run: sh scripts/ci-docker-compose-up.sh traefik redis mysql-debug edge-broker php1 php2 php3 php4 php5 caddy
- name: Sync PHP app checkout
run: >
tar
--exclude='./vendor'
--exclude='./.phpunit.cache'
--exclude='./build/logs'
-C services/nginx/app -cf - .
| docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar --no-same-owner -C /var/www/html -xf -
- name: Resolve dependencies
run: |
set -euo pipefail
composer_install() {
install_mode="$1"
max_attempts="$2"
attempt=1
while :; do
if docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer install --no-interaction ${install_mode} --no-progress"; then
return 0
fi
if [ "$attempt" -ge "$max_attempts" ]; then
return 1
fi
sleep_seconds=$((attempt * 5))
echo "composer install ${install_mode} failed; retrying in ${sleep_seconds}s (attempt $((attempt + 1))/${max_attempts})" >&2
sleep "$sleep_seconds"
attempt=$((attempt + 1))
done
}
composer_install --prefer-dist 3 || {
echo "Composer dist install failed; retrying with --prefer-source." >&2
composer_install --prefer-source 2
}
- name: Verify edge gateway test files
run: >
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
"cd /var/www/html &&
php -r '\$composer = json_decode(file_get_contents(\"composer.json\"), true); echo \"Composer scripts: \", implode(\",\", array_keys(\$composer[\"scripts\"] ?? [])), PHP_EOL;' &&
find tests/Api -maxdepth 1 -type f -name 'EdgeGateway*ApiTest.php' -print &&
test -f tests/Api/EdgeGatewayAgentApiTest.php &&
test -f tests/Api/EdgeGatewayBrokerApiTest.php &&
test -f tests/Api/EdgeGatewayOperatorApiTest.php"
- name: Run edge gateway API tests
run: docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:api:edge"
run: >
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
"cd /var/www/html &&
RUN_API_TESTS=1
API_TEST_BOOTSTRAP_SCHEMA=1
API_TEST_ALLOW_LIVE_DB=1
CONFIG_DB_TARGET=debug
CONFIG_DB_HOST=mysql-debug
CONFIG_DB_USER=\${CONFIG_DB_USER:-root}
CONFIG_DB_PASSWORD=\${CONFIG_DB_PASSWORD:-debug_root_password}
CONFIG_DB_DATABASE=\${CONFIG_DB_DATABASE:-nnks_db_debug}
CONFIG_DB_PORT=3306
CONFIG_DB_DEBUG_HOST=mysql-debug
CONFIG_DB_DEBUG_USER=\${CONFIG_DB_DEBUG_USER:-root}
CONFIG_DB_DEBUG_PASSWORD=\${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
CONFIG_DB_DEBUG_DATABASE=\${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
CONFIG_DB_DEBUG_PORT=3306
API_TEST_REQUEST_TIMEOUT=180
EDGE_GATEWAY_VIEW_CACHE_TTL=0
EDGE_BROKER_URL=
vendor/bin/pest
tests/Api/EdgeGatewayAgentApiTest.php
tests/Api/EdgeGatewayBrokerApiTest.php
tests/Api/EdgeGatewayOperatorApiTest.php
--colors=always"
- name: Run edge gateway integration tests
run: docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:integration:edge"
run: >
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
"cd /var/www/html &&
RUN_INTEGRATION_TESTS=1
CONFIG_DB_TARGET=debug
CONFIG_DB_HOST=mysql-debug
CONFIG_DB_USER=\${CONFIG_DB_USER:-root}
CONFIG_DB_PASSWORD=\${CONFIG_DB_PASSWORD:-debug_root_password}
CONFIG_DB_DATABASE=\${CONFIG_DB_DATABASE:-nnks_db_debug}
CONFIG_DB_PORT=3306
CONFIG_DB_DEBUG_HOST=mysql-debug
CONFIG_DB_DEBUG_USER=\${CONFIG_DB_DEBUG_USER:-root}
CONFIG_DB_DEBUG_PASSWORD=\${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
CONFIG_DB_DEBUG_DATABASE=\${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
CONFIG_DB_DEBUG_PORT=3306
EDGE_BROKER_URL=
vendor/bin/pest tests/Integration/EdgeGateway --colors=always"
- name: Run edge gateway E2E smoke
env:
EDGE_GATEWAY_E2E_COPY_CONFIG: "true"
EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP: "true"
run: node scripts/edge-gateway-e2e.mjs
- name: Tear down local stack
if: always()
run: docker compose down -v
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v
integration:
name: Integration (advisory)
runs-on: [self-hosted, Linux, X64, default]
continue-on-error: true
services:
redis:
image: redis:7
ports:
- 6379:6379
mysql:
image: mysql:8
env:
MYSQL_DATABASE: app_test
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
required-ci:
name: Required CI
runs-on: ubuntu-latest
needs: [php, edge-agent, edge-broker, edge-gateway-backend]
if: ${{ always() }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mysqli, curl, openssl, json, redis
ini-values: variables_order=EGPCS
- name: Resolve dependencies
working-directory: services/nginx/app
run: composer update --no-interaction --prefer-dist
- name: Run integration tests
working-directory: services/nginx/app
- name: Verify required jobs succeeded
env:
RUN_INTEGRATION_TESTS: '1'
USE_ENV: 'true'
DEBUG: '0'
ENCRYPTION_KEY: test-key
CORS: '*'
CONFIG_TIMEZONE: Europe/Copenhagen
ECONOMIC_API_APP_ACCESS_GRANT: test
ECONOMIC_API_APP_ACCESS_GRANT2: test
ECONOMIC_API_APP_SECRET_TOKEN: test
WORDPRESS_STATIC_TOKEN: ''
EMAIL_WASH_CERTIFICATE_TOKEN: ''
WORDPRESS_API_URL: http://localhost
MINIO_ENDPOINT: ''
MINIO_ACCESS_KEY: ''
MINIO_SECRET_KEY: ''
SLACK_DEFAULT_WEBHOOK: ''
REDIS_CONFIG_HOST: 127.0.0.1
REDIS_CONFIG_DATABASE: '0'
REDIS_CONFIG_PASSWORD: ''
REDIS_CONFIG_PORT: '6379'
CONFIG_DB_HOST: 127.0.0.1
CONFIG_DB_USER: root
CONFIG_DB_PASSWORD: root
CONFIG_DB_DATABASE: app_test
CONFIG_DB_PORT: '3306'
run: composer test:integration
PHP_RESULT: ${{ needs.php.result }}
EDGE_AGENT_RESULT: ${{ needs.edge-agent.result }}
EDGE_BROKER_RESULT: ${{ needs.edge-broker.result }}
EDGE_GATEWAY_BACKEND_RESULT: ${{ needs.edge-gateway-backend.result }}
run: |
set -euo pipefail
failed=0
for dependency in \
"php=${PHP_RESULT}" \
"edge-agent=${EDGE_AGENT_RESULT}" \
"edge-broker=${EDGE_BROKER_RESULT}" \
"edge-gateway-backend=${EDGE_GATEWAY_BACKEND_RESULT}"
do
name="${dependency%%=*}"
result="${dependency#*=}"
if [ "$result" != "success" ]; then
echo "Required dependency ${name} completed with result: ${result:-missing}" >&2
failed=1
fi
done
test "$failed" -eq 0
api:
name: API (advisory)
runs-on: [self-hosted, Linux, X64, default]
continue-on-error: true
services:
redis:
image: redis:7
ports:
- 6379:6379
mysql:
image: mysql:8
env:
MYSQL_DATABASE: app_test
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
release-manager-gate:
name: Release Manager gate
runs-on: [self-hosted, Linux, X64, pleno, backend]
needs: [required-ci]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && needs.required-ci.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Record Release Manager API gate
run: |
set -euo pipefail
test -n "$RELEASE_MANAGER_GATE_TOKEN" || (echo "RELEASE_MANAGER_GATE_TOKEN is required" >&2; exit 1)
response_file="$(mktemp)"
http_code="$(curl --show-error --silent \
--connect-timeout 10 \
--retry 5 \
--retry-all-errors \
--retry-delay 15 \
--retry-max-time 300 \
-o "$response_file" \
-w '%{http_code}' \
-X POST "$RELEASE_MANAGER_GATE_URL" \
-H "Authorization: Bearer $RELEASE_MANAGER_GATE_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"channel_slug\":\"stable\",\"app\":\"api\",\"repository\":\"$RELEASE_REPOSITORY\",\"branch\":\"$RELEASE_BRANCH\",\"expected_commit\":\"$RELEASE_EXPECTED_COMMIT\",\"workflow_url\":\"$RELEASE_WORKFLOW_URL\",\"auto_sync\":true,\"wait_timeout_seconds\":300,\"poll_interval_seconds\":10,\"required_checks\":[\"api_gateway\"]}")"
response_body="$(cat "$response_file")"
rm -f "$response_file"
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mysqli, curl, openssl, json, redis
ini-values: variables_order=EGPCS
if [[ "$http_code" =~ ^2[0-9][0-9]$ ]]; then
printf '%s\n' "$response_body"
exit 0
fi
- name: Resolve dependencies
working-directory: services/nginx/app
run: composer update --no-interaction --prefer-dist
- name: Run API tests
working-directory: services/nginx/app
printf '%s\n' "$response_body"
echo "Release Manager gate failed with HTTP $http_code." >&2
exit 1
env:
RUN_API_TESTS: '1'
API_TEST_BOOTSTRAP_SCHEMA: '1'
USE_ENV: 'true'
DEBUG: '0'
ENCRYPTION_KEY: test-key
CORS: '*'
CONFIG_TIMEZONE: Europe/Copenhagen
ECONOMIC_API_APP_ACCESS_GRANT: test
ECONOMIC_API_APP_ACCESS_GRANT2: test
ECONOMIC_API_APP_SECRET_TOKEN: test
WORDPRESS_STATIC_TOKEN: ''
EMAIL_WASH_CERTIFICATE_TOKEN: ''
WORDPRESS_API_URL: http://localhost
MINIO_ENDPOINT: ''
MINIO_ACCESS_KEY: ''
MINIO_SECRET_KEY: ''
SLACK_DEFAULT_WEBHOOK: ''
REDIS_CONFIG_HOST: 127.0.0.1
REDIS_CONFIG_DATABASE: '0'
REDIS_CONFIG_PASSWORD: ''
REDIS_CONFIG_PORT: '6379'
CONFIG_DB_HOST: 127.0.0.1
CONFIG_DB_USER: root
CONFIG_DB_PASSWORD: root
CONFIG_DB_DATABASE: app_test
CONFIG_DB_PORT: '3306'
run: composer test:api
RELEASE_MANAGER_GATE_URL: ${{ secrets.RELEASE_MANAGER_GATE_URL || 'https://api.truckwash.io/release/gate/test-runs' }}
RELEASE_MANAGER_GATE_TOKEN: ${{ secrets.RELEASE_MANAGER_GATE_TOKEN }}
RELEASE_REPOSITORY: ${{ github.repository }}
RELEASE_BRANCH: ${{ github.ref_name }}
RELEASE_EXPECTED_COMMIT: ${{ github.sha }}
RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
+6
View File
@@ -2,6 +2,7 @@
/docker-compose.yml
/services/nginx/app/vendor/
/services/nginx/app/modules/washcertificates/vendor/
/services/nginx/app/.phpunit.cache/
/services/nginx/letsencrypt/
*.pem
*.log.gz
@@ -10,5 +11,10 @@
/.idea/
.env
/services/caddy/logs*
.env.old
/.tmp/
/.env.staging
/services/nginx/app/storage/replication-bootstrap.json
/.env_old_2
/.openclaw/
/services/nginx/app/build/phpstan/
+4 -2
View File
@@ -40,13 +40,15 @@ COPY . /var/www/html
# Copy Nginx configuration file
COPY nginx.conf /etc/nginx/nginx.conf
COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf
# Install Composer
COPY --from=composer:2.6 /usr/bin/composer /usr/bin/composer
# Runtime bootstrap: lightweight entrypoint to ensure Composer deps exist when app is bind-mounted
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/docker-entrypoint.sh
# Install PHP dependencies through Composer (only where composer.json exists)
# Main app dependencies
@@ -72,4 +74,4 @@ EXPOSE 80 443
ENTRYPOINT ["docker-entrypoint.sh"]
# Start services when no command is provided (docker-compose overrides this with ["php-fpm"])
CMD ["php-fpm"]
CMD ["php-fpm"]
+80
View File
@@ -0,0 +1,80 @@
FROM php:8.2.15-fpm
WORKDIR /var/www/html
COPY --from=composer:2.6 /usr/bin/composer /usr/bin/composer
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
$PHPIZE_DEPS \
ca-certificates \
curl \
default-mysql-client \
git \
imagemagick \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmagickcore-dev \
libmagickwand-dev \
libonig-dev \
libpng-dev \
libssl-dev \
libxml2-dev \
libzip-dev \
mariadb-client \
nginx \
openssl \
pkg-config \
redis-tools \
unzip \
zip; \
update-ca-certificates; \
docker-php-ext-configure gd --with-freetype --with-jpeg; \
docker-php-ext-install -j"$(nproc)" \
bcmath \
exif \
gd \
mbstring \
mysqli \
pcntl \
pdo_mysql \
sockets \
zip; \
pecl install imagick-3.7.0 redis; \
docker-php-ext-enable imagick redis; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $PHPIZE_DEPS; \
rm -rf /var/lib/apt/lists/*
COPY services/nginx/app/ /var/www/html/
COPY scripts/bird-control-plane-activate.php /var/www/html/scripts/bird-control-plane-activate.php
COPY scripts/bird-control-plane-auto-activate.php /var/www/html/scripts/bird-control-plane-auto-activate.php
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf
COPY services/coolify/api/nginx.conf /etc/nginx/nginx.conf
COPY services/coolify/api/start.sh /usr/local/bin/coolify-api-start
RUN set -eux; \
rm -f /var/www/html/storage/replication-bootstrap.json /var/www/html/storage/replication-bootstrap-*.json; \
sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh /usr/local/bin/coolify-api-start; \
chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/coolify-api-start; \
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction -d /var/www/html; \
if [ -f /var/www/html/modules/washcertificates/composer.json ]; then \
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction -d /var/www/html/modules/washcertificates; \
fi; \
COMPOSER_ALLOW_SUPERUSER=1 composer dump-autoload --no-dev --optimize --no-interaction -d /var/www/html; \
php -d display_errors=1 -r 'require "/var/www/html/vendor/autoload.php"; exit(interface_exists("Psr\\Http\\Message\\UriInterface") && interface_exists("Psr\\Http\\Message\\StreamInterface") ? 0 : 1);'; \
php -r 'exit(function_exists("proc_open") && extension_loaded("openssl") ? 0 : 1);'; \
test "$(openssl pkey -pubin -in /var/www/html/modules/bird/resources/control-plane-bootstrap-public.pem -outform DER | sha256sum | cut -d " " -f 1)" = "6dc63c6ffe33b8de0b1396d7f529f56aea0a685ef98168016161cf721ddc8c21"; \
chown -R www-data:www-data /var/www/html; \
chmod -R 755 /var/www/html
ENV APP_DIR=/var/www/html \
MODULE_DIR=/var/www/html/modules/washcertificates \
AUTO_COMPOSER_INSTALL=false \
COMPOSER_ALLOW_SUPERUSER=1
EXPOSE 80
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["coolify-api-start"]
+14 -4
View File
@@ -2,6 +2,11 @@
Backend API for Copenhagen Truck Wash services.
Changes are published from a scoped feature branch through a pull request to
`master`; direct default-branch pushes are not part of the release workflow.
See [default branch protection](.github/BRANCH_PROTECTION.md) for the CI gate
and emergency procedure.
## Architecture & Stack
- **Edge Proxy:** [Traefik 2.11](https://doc.traefik.io/traefik/) (Handles TLS termination, routing, and rate limiting).
- **Web Server:** [Caddy 2.7](https://caddyserver.com/) (Serves the PHP application via FastCGI).
@@ -70,6 +75,11 @@ When updating `.env` values used by PHP containers (for example e-conomic tokens
docker compose up -d --force-recreate php1 php2 php3 php4 php5 php-cron
```
#### Edge Broker Public URL
Set `EDGE_PUBLIC_BROKER_URL` to the public route that serves the edge broker, including the path prefix handled by the proxy. Local Traefik uses `http://localhost/api/edge-broker`; production routes use the public broker prefix, for example `https://api.truckwash.dk/edge-broker`.
The browser terminal connects to the exact advertised `EDGE_PUBLIC_BROKER_URL` plus `/ws/browser-shell`. That URL must be routable through the proxy to the edge-broker service. Do not rely on derived `/api/edge-broker` fallback paths outside the local Traefik setup.
## Testing
The project now uses [Pest](https://pestphp.com/) as the primary test runner in `services/nginx/app`.
@@ -91,8 +101,8 @@ For local Docker development, run the PHP suites inside `php1`:
docker exec php1 sh -lc "cd /var/www/html && composer test:unit"
docker exec php1 sh -lc "cd /var/www/html && composer test:integration"
docker exec php1 sh -lc "cd /var/www/html && composer test:api"
docker exec php1 sh -lc "cd /var/www/html && composer test:api:edge"
docker exec php1 sh -lc "cd /var/www/html && composer test:integration:edge"
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:api:edge"
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:integration:edge"
```
Integration tests are opt-in and should be run with required services available:
@@ -112,8 +122,8 @@ The dedicated backend regression lane for the PHP edge gateway stack is split in
Run the targeted PHP suites inside `php1`:
```powershell
docker exec php1 sh -lc "cd /var/www/html && composer test:api:edge"
docker exec php1 sh -lc "cd /var/www/html && composer test:integration:edge"
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:api:edge"
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:integration:edge"
```
Run the full local smoke from `backend-php` on the host:
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -10,7 +10,7 @@ $CONFIG_DB = [
$DEBUG = true; // Set to true to enable debugging (Error messages will be shown, and this should never be used in production)
$USE_PROD_ECONOMIC_IN_DEBUG = true; // Set to true to use the production economic API in debug mode
$ENCRYPTION_KEY = ''; // 44 Characters long encryption key
$CORS = '*'; // Set to the domain that should be allowed to access the API e.g. https://example.com
$CORS = '*'; // Set to comma-separated allowed origins e.g. https://example.com,https://api-v2.truckwash.io
$ECONOMIC_API = [
'app_access_grant' => '', // Economic API access grant token (1)
'app_access_grant2' => '', // Economic API access grant token (2)
@@ -31,7 +31,7 @@ $MINIO = [
'access_key' => '', // Minio access
'secret_key' => '' // Minio secret key
];
$SLACK_DEFAULT_WEBHOOK = ''; // Default Slack webhook URL e.g. https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
$SLACK_DEFAULT_WEBHOOK = ''; // Set through SLACK_DEFAULT_WEBHOOK; never commit a production webhook URL.
$REDIS_CONFIG = [
'host' => '', // Redis host (IP address)
'user' => '', // Redis user
+6 -4
View File
@@ -52,8 +52,9 @@ services:
dockerfile: services/edge-broker/Dockerfile
container_name: edge-broker
environment:
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-strict}
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
labels:
- "traefik.enable=true"
- "traefik.http.routers.edge-broker-api.rule=Host(`api.example.com`) && PathPrefix(`/edge-broker`)"
@@ -70,6 +71,7 @@ services:
- "traefik.http.middlewares.edge-broker-strip-local.stripPrefix.prefixes=/api/edge-broker"
- "traefik.http.services.edge-broker.loadbalancer.server.port=4300"
caddy:
image: caddy:2.7.6-alpine
container_name: caddy
@@ -112,7 +114,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "true"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -127,13 +129,13 @@ services:
- redis
- mysql
- edge-broker
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
command: ["php", "index.php", "run", "cron-worker"]
env_file:
- .env.example
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
+17 -2
View File
@@ -77,6 +77,7 @@ services:
dockerfile: services/edge-broker/Dockerfile
container_name: edge-broker
environment:
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-manager}
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
labels:
@@ -95,6 +96,13 @@ services:
- "traefik.http.routers.edge-broker-api-io.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-api-io.priority=200"
- "traefik.http.routers.edge-broker-api-io.service=edge-broker"
- "traefik.http.routers.edge-broker-api-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-api-v2.entrypoints=websecure"
- "traefik.http.routers.edge-broker-api-v2.tls=true"
- "traefik.http.routers.edge-broker-api-v2.tls.certresolver=le_io"
- "traefik.http.routers.edge-broker-api-v2.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-api-v2.priority=200"
- "traefik.http.routers.edge-broker-api-v2.service=edge-broker"
- "traefik.http.routers.edge-broker-api-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-api-staging.entrypoints=websecure-staging"
- "traefik.http.routers.edge-broker-api-staging.tls=true"
@@ -152,7 +160,14 @@ services:
- "traefik.http.routers.api-io.tls.certresolver=le_io"
- "traefik.http.routers.api-io.service=caddy"
- "traefik.http.routers.api-io.middlewares=secure-headers@file,api-ratelimit@file"
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`)"
- "traefik.http.routers.api-v2.rule=Host(`api-v2.truckwash.io`)"
- "traefik.http.routers.api-v2.entrypoints=websecure"
- "traefik.http.routers.api-v2.tls=true"
- "traefik.http.routers.api-v2.tls.domains[0].main=api-v2.truckwash.io"
- "traefik.http.routers.api-v2.tls.certresolver=le_io"
- "traefik.http.routers.api-v2.service=caddy"
- "traefik.http.routers.api-v2.middlewares=secure-headers@file,api-ratelimit@file"
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`) || Host(`api-v2.truckwash.io`)"
- "traefik.http.routers.api-http.entrypoints=web"
- "traefik.http.routers.api-http.middlewares=redirect-to-https@file"
- "traefik.http.routers.api-http.service=caddy"
@@ -352,7 +367,7 @@ services:
depends_on:
- redis
- edge-broker
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
command: ["php", "index.php", "run", "cron-worker"]
env_file:
- .env
environment:
+1 -1
View File
@@ -27,5 +27,5 @@ services:
## docker compose up -d traefik caddy php1 php2 php3 php4 php5 db redis
##
## Notes:
## - Traefik uses Lets Encrypt production. Ensure DNS A/AAAA records for api.truckwash.dk, api.truckwash.io and traefik.truckwash.dk point to this host and ports 80/443 are reachable.
## - Traefik uses Lets Encrypt production. Ensure DNS A/AAAA records for api.truckwash.dk, api.truckwash.io, api-v2.truckwash.io and traefik.truckwash.dk point to the expected ingress and ports 80/443 are reachable.
## - The dashboard is protected by basic auth and an IP allowlist (defined in dynamic.yml). Replace the bcrypt hash before enabling in production.
+75 -19
View File
@@ -3,12 +3,14 @@ services:
traefik:
image: traefik:2.11
container_name: traefik
group_add:
- "${DOCKER_SOCKET_GID:-65534}"
ports:
- "80:80"
- "443:443"
- "4433:4433"
- "${TRAEFIK_WEB_PORT:-80}:80"
- "${TRAEFIK_WEBSECURE_PORT:-443}:443"
- "${TRAEFIK_WEBSECURE_STAGING_PORT:-4433}:4433"
# Prometheus metrics endpoint (local dev)
- "9100:9100"
- "${TRAEFIK_METRICS_PORT:-9100}:9100"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./services/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
@@ -34,6 +36,41 @@ services:
- "traefik.http.routers.traefik-local.entrypoints=web"
- "traefik.http.routers.traefik-local.service=api@internal"
- "traefik.http.routers.traefik-local.middlewares=dashboard-allow-local@file,dashboard-auth@file"
# Broker API (handled in edge-broker service)
- "traefik.http.routers.edge-broker.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker.entrypoints=websecure"
- "traefik.http.routers.edge-broker.tls=true"
- "traefik.http.routers.edge-broker.tls.certresolver=le"
- "traefik.http.routers.edge-broker.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker.priority=200"
- "traefik.http.routers.edge-broker.service=edge-broker"
- "traefik.http.routers.edge-broker-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-io.entrypoints=websecure"
- "traefik.http.routers.edge-broker-io.tls=true"
- "traefik.http.routers.edge-broker-io.tls.certresolver=le_io"
- "traefik.http.routers.edge-broker-io.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-io.priority=200"
- "traefik.http.routers.edge-broker-io.service=edge-broker"
- "traefik.http.routers.edge-broker-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-v2.entrypoints=websecure"
- "traefik.http.routers.edge-broker-v2.tls=true"
- "traefik.http.routers.edge-broker-v2.tls.certresolver=le_io"
- "traefik.http.routers.edge-broker-v2.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-v2.priority=200"
- "traefik.http.routers.edge-broker-v2.service=edge-broker"
- "traefik.http.routers.edge-broker-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-staging.entrypoints=websecure-staging"
- "traefik.http.routers.edge-broker-staging.tls=true"
- "traefik.http.routers.edge-broker-staging.tls.certresolver=le_io"
- "traefik.http.routers.edge-broker-staging.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-staging.priority=200"
- "traefik.http.routers.edge-broker-staging.service=edge-broker"
- "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
- "traefik.http.routers.edge-broker-local.entrypoints=web"
- "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local"
- "traefik.http.routers.edge-broker-local.priority=200"
- "traefik.http.routers.edge-broker-local.service=edge-broker"
- "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
redis:
image: redis:7
@@ -66,8 +103,10 @@ services:
mysql-debug:
image: mysql:8.4
container_name: mysql-debug
profiles: [dev]
command: ["mysqld", "--innodb-use-native-aio=0"]
environment:
MYSQL_ROOT_PASSWORD: ${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
MYSQL_ROOT_PASSWORD: ${CONFIG_DB_DEBUG_PASSWORD:?CONFIG_DB_DEBUG_PASSWORD is required for mysql-debug}
MYSQL_DATABASE: ${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
ports:
- "3307:3306"
@@ -86,8 +125,9 @@ services:
dockerfile: services/edge-broker/Dockerfile
container_name: edge-broker
environment:
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-strict}
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
labels:
- "traefik.enable=true"
- "traefik.http.routers.edge-broker-api.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)"
@@ -104,6 +144,13 @@ services:
- "traefik.http.routers.edge-broker-api-io.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-api-io.priority=200"
- "traefik.http.routers.edge-broker-api-io.service=edge-broker"
- "traefik.http.routers.edge-broker-api-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-api-v2.entrypoints=websecure"
- "traefik.http.routers.edge-broker-api-v2.tls=true"
- "traefik.http.routers.edge-broker-api-v2.tls.certresolver=le_io"
- "traefik.http.routers.edge-broker-api-v2.middlewares=secure-headers@file,edge-broker-strip"
- "traefik.http.routers.edge-broker-api-v2.priority=200"
- "traefik.http.routers.edge-broker-api-v2.service=edge-broker"
- "traefik.http.routers.edge-broker-api-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
- "traefik.http.routers.edge-broker-api-staging.entrypoints=websecure-staging"
- "traefik.http.routers.edge-broker-api-staging.tls=true"
@@ -143,7 +190,7 @@ services:
- php5
volumes:
- ./services/nginx/app:/var/www/html
- ./services/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- ./services/caddy:/etc/caddy:ro
- ./services/caddy/logs:/var/log/caddy
labels:
- "traefik.enable=true"
@@ -163,8 +210,16 @@ services:
- "traefik.http.routers.api-io.tls.certresolver=le_io"
- "traefik.http.routers.api-io.service=caddy"
- "traefik.http.routers.api-io.middlewares=secure-headers@file,api-ratelimit@file"
# Public API (.io load-balanced gateway)
- "traefik.http.routers.api-v2.rule=Host(`api-v2.truckwash.io`)"
- "traefik.http.routers.api-v2.entrypoints=websecure"
- "traefik.http.routers.api-v2.tls=true"
- "traefik.http.routers.api-v2.tls.domains[0].main=api-v2.truckwash.io"
- "traefik.http.routers.api-v2.tls.certresolver=le_io"
- "traefik.http.routers.api-v2.service=caddy"
- "traefik.http.routers.api-v2.middlewares=secure-headers@file,api-ratelimit@file"
# HTTP to HTTPS redirect for both API domains
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`)"
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`) || Host(`api-v2.truckwash.io`)"
- "traefik.http.routers.api-http.entrypoints=web"
- "traefik.http.routers.api-http.middlewares=redirect-to-https@file"
- "traefik.http.routers.api-http.service=caddy"
@@ -200,9 +255,10 @@ services:
container_name: caddy-staging
depends_on:
- php-staging
command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile-staging", "--adapter", "caddyfile"]
volumes:
- ./services/nginx/app:/var/www/html
- ./services/caddy/Caddyfile-staging:/etc/caddy/Caddyfile:ro
- ./services/nginx/staging:/var/www/html
- ./services/caddy:/etc/caddy:ro
- ./services/caddy/logs-staging:/var/log/caddy
labels:
- "traefik.enable=true"
@@ -255,7 +311,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "true"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -275,7 +331,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -295,7 +351,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -315,7 +371,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -335,7 +391,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
@@ -355,9 +411,9 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/nginx/staging:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
- ./services/php/logs-staging:/var/log/php
@@ -369,13 +425,13 @@ services:
depends_on:
- redis
- edge-broker
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
command: ["php", "index.php", "run", "cron-worker"]
env_file:
- .env
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
+106 -3
View File
@@ -3357,6 +3357,9 @@
},
"email_notifications_enabled": {
"type": "boolean"
},
"superuser_new_customer_email_notifications_enabled": {
"type": "boolean"
}
}
}
@@ -9853,6 +9856,7 @@
"Orders"
],
"summary": "Create Stripe payment intent",
"description": "Creates a Stripe Terminal card payment intent with fixed 25% moms.",
"operationId": "createStripePaymentIntent",
"requestBody": {
"required": true,
@@ -9870,9 +9874,6 @@
},
"reader": {
"type": "string"
},
"tax_percentage": {
"type": "integer"
}
}
}
@@ -12955,6 +12956,54 @@
}
}
},
"/slack/config": {
"get": {
"tags": [
"Config"
],
"summary": "Get Slack config",
"operationId": "getSlackConfig",
"responses": {
"200": {
"description": "Slack configuration retrieved successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SlackConfigListResponse"
}
}
}
}
}
},
"post": {
"tags": [
"Config"
],
"summary": "Update Slack config",
"operationId": "updateSlackConfig",
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {}
}
}
},
"responses": {
"200": {
"description": "Slack configuration updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModuleConfigUpdateResponse"
}
}
}
}
}
}
},
"/backups/config": {
"get": {
"tags": [
@@ -15496,6 +15545,39 @@
"value"
]
},
"SlackConfigEntry": {
"type": "object",
"properties": {
"module": {
"type": "string",
"enum": [
"Slack"
]
},
"variable": {
"type": "string",
"enum": [
"customer_registration_webhook_url"
]
},
"type": {
"type": "string",
"enum": [
"string"
]
},
"value": {
"type": "string",
"example": "https://hooks.slack.com/services/..."
}
},
"required": [
"module",
"variable",
"type",
"value"
]
},
"BackupsConfigEntry": {
"type": "object",
"properties": {
@@ -16240,6 +16322,27 @@
}
]
},
"SlackConfigListResponse": {
"allOf": [
{
"$ref": "#/components/schemas/ModuleConfigEnvelopeBase"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SlackConfigEntry"
}
}
},
"required": [
"data"
]
}
]
},
"BackupsConfigListResponse": {
"allOf": [
{
@@ -12,7 +12,7 @@
</chapter>
<chapter title="Operation" id="operation">
<p>Operation ID: <code>createStripePaymentIntent</code></p>
<p>Create Stripe payment intent</p>
<p>Creates a Stripe Terminal card payment intent with fixed 25% moms.</p>
</chapter>
<chapter title="Authentication" id="authentication">
<p>Security requirements:</p>
@@ -32,9 +32,6 @@
},
&quot;reader&quot;: {
&quot;type&quot;: &quot;string&quot;
},
&quot;tax_percentage&quot;: {
&quot;type&quot;: &quot;integer&quot;
}
},
&quot;required&quot;: [
+3 -3
View File
@@ -38,14 +38,14 @@ http {
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
add_header Access-Control-Allow-Credentials true;
# If OPTIONS method is needed for preflight
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
return 204; # No Content
}
@@ -65,4 +65,4 @@ http {
location ~* \.(cgi|shtml|phtml)$ {
}
}
}
}
+3 -3
View File
@@ -52,14 +52,14 @@ http {
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
add_header Access-Control-Allow-Credentials true;
# If OPTIONS method is needed for preflight
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
return 204; # No Content
}
@@ -80,4 +80,4 @@ http {
# Additional SSL options or configurations can be placed here, if necessary.
}
}
}
}
+6139 -125
View File
File diff suppressed because it is too large Load Diff
+132545
View File
File diff suppressed because one or more lines are too long
+49 -40
View File
@@ -1,46 +1,55 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
linter: jetbrains/qodana-php:2026.1
profile:
name: qodana.starter
name: qodana.recommended
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
php:
version: "8.2"
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
bootstrap: |+
set -eu
composer --working-dir=services/nginx/app install --no-interaction --prefer-dist --no-progress --ignore-platform-reqs
composer --working-dir=services/nginx/app/modules/washcertificates install --no-interaction --prefer-dist --no-progress --ignore-platform-reqs
npm --prefix services/edge-agent ci --ignore-scripts
npm --prefix services/edge-broker ci --ignore-scripts
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-php:2025.3
exclude:
# This application is intentionally Composer-classmapped and keeps legacy snake_case
# classes plus multiple local test doubles in single files; PSR path rules do not apply.
- name: PhpIllegalPsrClassPathInspection
paths:
- services/nginx/app
# Unit-test doubles intentionally bypass integration-heavy parent constructors.
- name: PhpMissingParentConstructorInspection
paths:
- services/nginx/app/tests
# These focused tests configure doubles through public fields before invoking behavior.
- name: PhpObjectFieldsAreOnlyWrittenInspection
paths:
- services/nginx/app/tests/Unit/Bird/BirdGateCallFlowTest.php
- services/nginx/app/tests/Unit/Invoicing/EconomicCustomersDiscountFallbackTest.php
- services/nginx/app/tests/Unit/Selfserve/SelfserveCustomerLaneAccessTest.php
# API coverage markers are intentional statement-style calls in the Pest DSL.
# Their return value is irrelevant; the call records route/scenario coverage.
- name: PhpExpressionResultUnusedInspection
paths:
- services/nginx/app/tests/Api
- name: All
paths:
- services/nginx/app/vendor
- services/nginx/app/modules/washcertificates/vendor
- services/nginx/app/build
- services/nginx/app/.phpunit.cache
- services/nginx/app/tests/Legacy
- services/edge-agent/node_modules
- services/edge-broker/node_modules
- services/edge-agent/dist
- documentation/generated
- documentation/topics/generated
- documentation/_build
- documentation/_site_rebuild_20260317
- docs_bird_voice_calls.html
- .tmp
- .openclaw
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env sh
set -eu
suite="${1:-}"
case "$suite" in
unit|integration|api|legacy|all)
;;
*)
echo "Usage: $0 <unit|integration|api|legacy|all>" >&2
exit 2
;;
esac
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)"
cd "$repo_root"
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
project_suffix="$(date +%s)-$$"
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
log_dir=".tmp/ci-logs/$suite"
mkdir -p "$log_dir"
env_backup_dir=".tmp/php-ci-env-backup-$project_suffix"
mkdir -p "$env_backup_dir"
had_env=0
had_env_staging=0
if [ -f .env ]; then
cp .env "$env_backup_dir/env"
had_env=1
fi
if [ -f .env.staging ]; then
cp .env.staging "$env_backup_dir/env.staging"
had_env_staging=1
fi
cp .github/ci.env .env
cp .github/ci.env.staging .env.staging
collect_logs() {
status="$1"
if [ "$status" -eq 0 ]; then
return
fi
mkdir -p "$log_dir"
docker compose $compose_files ps > "$log_dir/docker-compose-ps.txt" 2>&1 || true
docker compose $compose_files logs --no-color > "$log_dir/docker-compose.log" 2>&1 || true
docker compose $compose_files cp php1:/var/www/html/build/logs "$log_dir/app-build-logs" >/dev/null 2>&1 || true
docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true
}
cleanup() {
status="$?"
collect_logs "$status"
docker compose $compose_files down -v >/dev/null 2>&1 || true
if [ "$had_env" -eq 1 ]; then
cp "$env_backup_dir/env" .env
else
rm -f .env
fi
if [ "$had_env_staging" -eq 1 ]; then
cp "$env_backup_dir/env.staging" .env.staging
else
rm -f .env.staging
fi
rm -rf "$env_backup_dir"
exit "$status"
}
trap cleanup EXIT INT TERM
docker compose $compose_files up -d redis mysql-debug php1
docker compose $compose_files exec -T php1 sh -lc '
set -eu
for i in $(seq 1 90); do
if MYSQL_PWD="${CONFIG_DB_PASSWORD:-debug_root_password}" mysqladmin \
-h "${CONFIG_DB_HOST:-mysql-debug}" \
-P "${CONFIG_DB_PORT:-3306}" \
-u "${CONFIG_DB_USER:-root}" \
ping --silent >/dev/null 2>&1; then
exit 0
fi
sleep 1
done
echo "Timed out waiting for mysql-debug" >&2
exit 1
'
tar \
--exclude='./vendor' \
--exclude='./.phpunit.cache' \
--exclude='./build/logs' \
-C services/nginx/app -cf - . \
| docker compose $compose_files exec -T php1 tar -C /var/www/html -xf -
docker compose $compose_files exec -T php1 sh -lc \
'cd /var/www/html && composer install --no-interaction --prefer-dist --no-progress'
docker compose $compose_files exec -T php1 sh -lc \
"cd /var/www/html && composer test:ci:$suite"
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "This command is CLI-only.\n");
exit(2);
}
const WD = __DIR__ . '/../services/nginx/app';
require_once WD . '/vendor/autoload.php';
require_once WD . '/config.php';
require_once WD . '/classes/db.php';
require_once WD . '/classes/account_deletion_schema_bootstrap.php';
$response = null;
$db = new \classes\db($CONFIG_DB);
$db->connect();
$command = $argv[1] ?? 'check';
if ($command === 'apply') {
if (($argv[2] ?? '') !== '--yes') {
fwrite(STDERR, "Refusing schema mutation without: apply --yes\n");
exit(2);
}
\classes\account_deletion_schema_bootstrap::apply();
}
if (!in_array($command, ['check', 'apply'], true)) {
fwrite(STDERR, "Usage: scripts/account-deletion-schema.php check|apply --yes\n");
exit(2);
}
$status = \classes\account_deletion_schema_bootstrap::check();
fwrite(STDOUT, json_encode($status, JSON_UNESCAPED_SLASHES) . PHP_EOL);
exit($status['ready'] ? 0 : 1);
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "This command is CLI-only.\n");
exit(2);
}
$command = $argv[1] ?? 'check';
if (!in_array($command, ['check', 'apply', 'webhooks-check', 'webhooks-apply'], true)) {
fwrite(
STDERR,
"Usage: scripts/bird-control-plane-activate.php check|apply|webhooks-check|webhooks-apply [--yes]\n"
);
exit(2);
}
if (in_array($command, ['apply', 'webhooks-apply'], true) && ($argv[2] ?? '') !== '--yes') {
fwrite(STDERR, "Refusing Bird activation without: apply --yes\n");
exit(2);
}
$appDirectory = __DIR__ . '/../services/nginx/app';
if (!is_file($appDirectory . '/config.php')) {
$appDirectory = dirname(__DIR__);
}
define('WD', $appDirectory);
require_once WD . '/vendor/autoload.php';
require_once WD . '/config.php';
require_once WD . '/classes/db.php';
require_once WD . '/modules/bird/classes/bird_control_plane_activator.php';
require_once WD . '/modules/bird/classes/bird_webhook_subscription_reconciler.php';
try {
$pdo = \classes\db::getPDO();
if (str_starts_with($command, 'webhooks-')) {
$reconciler = new \bird\classes\bird_webhook_subscription_reconciler($pdo);
$organizationId = trim((string)(getenv('BIRD_ORGANIZATION_ID') ?: ''));
$status = $command === 'webhooks-apply'
? $reconciler->apply($organizationId)
: $reconciler->check($organizationId);
} else {
$activator = new \bird\classes\bird_control_plane_activator($pdo);
$status = $command === 'apply' ? $activator->apply([
'controlPlaneToken' => trim((string)(getenv('BIRD_CONTROL_PLANE_TOKEN') ?: '')),
'webhookSigningKey' => trim((string)(getenv('BIRD_WEBHOOK_SIGNING_KEY') ?: '')),
'participantId' => trim((string)(getenv('BIRD_PARTICIPANT_ID') ?: '')),
]) : $activator->check();
}
fwrite(STDOUT, json_encode($status, JSON_UNESCAPED_SLASHES) . PHP_EOL);
exit(($status['ready'] ?? false) === true ? 0 : 1);
} catch (Throwable $throwable) {
error_log('[bird-control-plane-activate] Failed: ' . get_class($throwable));
fwrite(STDOUT, json_encode([
'ready' => false,
'errorCode' => 'bird_activation_failed',
], JSON_UNESCAPED_SLASHES) . PHP_EOL);
exit(1);
}
@@ -0,0 +1,28 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli') {
exit(2);
}
$appDirectory = __DIR__ . '/../services/nginx/app';
if (!is_file($appDirectory . '/config.php')) {
$appDirectory = dirname(__DIR__);
}
define('WD', $appDirectory);
require_once WD . '/vendor/autoload.php';
require_once WD . '/config.php';
require_once WD . '/classes/db.php';
require_once WD . '/modules/bird/classes/bird_control_plane_auto_activation.php';
try {
$status = (new \bird\classes\bird_control_plane_auto_activation(
\classes\db::getPDO()
))->run();
fwrite(STDOUT, json_encode($status, JSON_UNESCAPED_SLASHES) . PHP_EOL);
exit(($status['ready'] ?? false) === true ? 0 : 1);
} catch (Throwable $throwable) {
error_log('[bird-control-plane-auto-activate] Failed: ' . get_class($throwable));
fwrite(STDOUT, '{"ready":false,"errorCode":"bird_auto_activation_failed"}' . PHP_EOL);
exit(1);
}
@@ -0,0 +1,79 @@
#!/bin/sh
set -eu
bootstrap_url='https://api.truckwash.io:4433/bird/control-plane/v1/bootstrap'
status_url='https://api.truckwash.io:4433/bird/control-plane/v1/status'
expected_algorithm='RSA-OAEP-256'
expected_fingerprint='6dc63c6ffe33b8de0b1396d7f529f56aea0a685ef98168016161cf721ddc8c21'
private_key='/home/jeppe/.openclaw/credentials/bird.bootstrap-private.pem'
credential_dir='/home/jeppe/.openclaw/credentials'
destination="$credential_dir/bird.gateway-token"
umask 077
mkdir -p "$credential_dir"
envelope_file="$(mktemp "$credential_dir/.bird-bootstrap-envelope.XXXXXX")"
candidate_file="$(mktemp "$credential_dir/.bird-gateway-token.XXXXXX")"
payload_file="$(mktemp "$credential_dir/.bird-bootstrap-payload.XXXXXX")"
status_file="$(mktemp "$credential_dir/.bird-bootstrap-status.XXXXXX")"
cleanup() {
rm -f "$envelope_file" "$candidate_file" "$payload_file" "$status_file"
}
trap cleanup EXIT HUP INT TERM
test -r "$private_key"
test "$(stat -c '%a' "$private_key")" = '600'
curl --proto '=https' --tlsv1.2 --fail --silent --show-error \
--max-time 30 "$bootstrap_url" > "$envelope_file"
test "$(jq -r '.success // false' "$envelope_file")" = 'true'
test "$(jq -r '.data.algorithm // empty' "$envelope_file")" = "$expected_algorithm"
test "$(jq -r '.data.keyFingerprint // empty' "$envelope_file")" = "$expected_fingerprint"
jq -e '.data | keys == ["algorithm","ciphertext","keyFingerprint","tokenVersion","updatedAt"]' \
"$envelope_file" >/dev/null
jq -e '.data.tokenVersion | type == "number" and . >= 1 and floor == .' \
"$envelope_file" >/dev/null
jq -e '.data.updatedAt | type == "string" and test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$")' \
"$envelope_file" >/dev/null
jq -e '.data.ciphertext | type == "string" and length == 512 and test("^[A-Za-z0-9+/]{512}$")' \
"$envelope_file" >/dev/null
jq -r '.data.ciphertext' "$envelope_file" \
| base64 -d \
| openssl pkeyutl -decrypt -inkey "$private_key" \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256 \
-pkeyopt rsa_mgf1_md:sha256 > "$payload_file"
jq -e '. | keys == ["algorithm","keyFingerprint","token","tokenVersion","updatedAt"]' \
"$payload_file" >/dev/null
test "$(jq -r '.algorithm // empty' "$payload_file")" = "$expected_algorithm"
test "$(jq -r '.keyFingerprint // empty' "$payload_file")" = "$expected_fingerprint"
test "$(jq -r '.tokenVersion // empty' "$payload_file")" = \
"$(jq -r '.data.tokenVersion' "$envelope_file")"
test "$(jq -r '.updatedAt // empty' "$payload_file")" = \
"$(jq -r '.data.updatedAt' "$envelope_file")"
jq -j '.token' "$payload_file" > "$candidate_file"
test "$(wc -c < "$candidate_file")" = '64'
grep -Eq '^[A-Za-z0-9_-]{64}$' "$candidate_file"
chmod 600 "$candidate_file"
token="$(cat "$candidate_file")"
{
printf 'url = "%s"\n' "$status_url"
printf 'proto = "=https"\n'
printf 'tlsv1.2\n'
printf 'fail\nsilent\nshow-error\n'
printf 'max-time = 30\n'
printf 'header = "Authorization: Bearer %s"\n' "$token"
} | curl --config - > "$status_file"
unset token
jq -e '.success == true and .data.enabled == true and .data.webhookConfigured == true' \
"$status_file" >/dev/null
mv -f "$candidate_file" "$destination"
chmod 600 "$destination"
trap - EXIT HUP INT TERM
rm -f "$envelope_file" "$payload_file" "$status_file"
printf 'Bird gateway credential bootstrapped and authenticated.\n'
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env php
<?php
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "This command is CLI-only.\n");
exit(2);
}
const WD = __DIR__ . '/../services/nginx/app';
require_once WD . '/vendor/autoload.php';
require_once WD . '/config.php';
require_once WD . '/classes/db.php';
require_once WD . '/modules/bird/classes/bird_control_plane_schema_bootstrap.php';
$command = $argv[1] ?? 'check';
if (!in_array($command, ['check', 'apply'], true)) {
fwrite(STDERR, "Usage: scripts/bird-control-plane-schema.php check|apply --yes\n");
exit(2);
}
$pdo = \classes\db::getPDO();
if ($command === 'apply') {
if (($argv[2] ?? '') !== '--yes') {
fwrite(STDERR, "Refusing schema mutation without: apply --yes\n");
exit(2);
}
\bird\classes\bird_control_plane_schema_bootstrap::apply($pdo);
}
$status = \bird\classes\bird_control_plane_schema_bootstrap::check($pdo);
fwrite(STDOUT, json_encode($status, JSON_UNESCAPED_SLASHES) . PHP_EOL);
exit($status['ready'] ? 0 : 1);
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env sh
set -eu
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <service> [service ...]" >&2
exit 2
fi
compose_files="${CI_DOCKER_COMPOSE_FILES:--f docker-compose.yml -f .github/docker-compose.ci.yml}"
lock_file="${CI_DOCKER_LOCK_FILE:-/tmp/pleno-api-ci-docker-compose-up.lock}"
max_attempts="${CI_DOCKER_UP_RETRIES:-${PHP_CI_DOCKER_RETRIES:-3}}"
export COMPOSE_PROFILES="${COMPOSE_PROFILES:-dev}"
compose_up() {
attempt=1
while :; do
docker network prune -f >/dev/null 2>&1 || true
if docker compose $compose_files up -d "$@"; then
return 0
fi
status="$?"
docker compose $compose_files down -v --remove-orphans >/dev/null 2>&1 || true
if [ "$attempt" -ge "$max_attempts" ]; then
return "$status"
fi
sleep_seconds=$((attempt * 5))
echo "Docker compose up failed with status $status; retrying in ${sleep_seconds}s (attempt $((attempt + 1))/$max_attempts)." >&2
sleep "$sleep_seconds"
attempt=$((attempt + 1))
done
}
if command -v flock >/dev/null 2>&1; then
(
flock 9
compose_up "$@"
) 9>"$lock_file"
else
echo "flock is not available; running Docker compose startup without a host lock." >&2
compose_up "$@"
fi
+453
View File
@@ -0,0 +1,453 @@
import { spawn } from "node:child_process";
import crypto from "node:crypto";
import fs from "node:fs";
import http from "node:http";
import net from "node:net";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const DEFAULT_AGENT_PATH = path.join(
repoRoot,
"services/nginx/app/resources/edge-gateway-agent/agent.php"
);
const DEFAULT_PHP_IMAGE = "php:8.2-cli-bookworm";
const DEFAULT_TIMEOUT_MS = 12000;
function parseArgs(argv = process.argv.slice(2)) {
const options = {
agentPath: DEFAULT_AGENT_PATH,
phpImage: DEFAULT_PHP_IMAGE,
timeoutMs: DEFAULT_TIMEOUT_MS,
keepTemp: false,
help: false,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const next = argv[index + 1];
switch (arg) {
case "--agent-path":
options.agentPath = path.resolve(String(next || "").trim());
index += 1;
break;
case "--php-image":
options.phpImage = String(next || "").trim() || DEFAULT_PHP_IMAGE;
index += 1;
break;
case "--timeout-ms":
options.timeoutMs = Number.parseInt(String(next || ""), 10) || DEFAULT_TIMEOUT_MS;
index += 1;
break;
case "--keep-temp":
options.keepTemp = true;
break;
case "--help":
case "-h":
options.help = true;
break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
return options;
}
function printUsage() {
process.stdout.write(`Usage:
node scripts/edge-agent-command-drain-proof.mjs [options]
Verifies that a broker-connected PHP compose edge agent still drains API-queued
SET_RELAY_STATE jobs to the LAN worker /relay/switch endpoint.
Options:
--agent-path <path> PHP agent artifact to execute.
Default: ${DEFAULT_AGENT_PATH}
--php-image <image> Docker PHP image with curl, sqlite3, and pdo_sqlite.
Default: ${DEFAULT_PHP_IMAGE}
--timeout-ms <ms> Proof timeout. Default: ${DEFAULT_TIMEOUT_MS}
--keep-temp Keep the temporary config/runtime directory.
--help Show this help text.
`);
}
function readJson(request) {
return new Promise((resolve) => {
let raw = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
raw += chunk;
});
request.on("end", () => {
if (raw.trim() === "") {
resolve({});
return;
}
try {
resolve(JSON.parse(raw));
} catch {
resolve({ __invalid: raw });
}
});
});
}
function sendJson(response, status, payload) {
const body = JSON.stringify(payload);
response.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(body),
});
response.end(body);
}
function listen(server) {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => resolve(server.address().port));
});
}
function closeServer(server) {
return new Promise((resolve) => {
server.close(() => resolve());
});
}
function websocketAcceptKey(key) {
return crypto
.createHash("sha1")
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
.digest("base64");
}
function createBrokerServer(state) {
const sockets = new Set();
const server = net.createServer((socket) => {
sockets.add(socket);
socket.on("close", () => sockets.delete(socket));
let buffer = "";
socket.on("data", (chunk) => {
buffer += chunk.toString("binary");
if (state.brokerHandshakeSeen || !buffer.includes("\r\n\r\n")) {
return;
}
const requestText = Buffer.from(buffer, "binary").toString("utf8");
const key = requestText.match(/Sec-WebSocket-Key:\s*(.+)\r\n/i)?.[1]?.trim();
const requestLine = requestText.split("\r\n")[0] || "";
if (!requestLine.includes("/ws/agent?")) {
state.failure = new Error(`unexpected broker path: ${requestLine}`);
}
if (!key) {
state.failure = new Error("broker handshake missing Sec-WebSocket-Key");
return;
}
socket.write([
"HTTP/1.1 101 Switching Protocols",
"Upgrade: websocket",
"Connection: Upgrade",
`Sec-WebSocket-Accept: ${websocketAcceptKey(key)}`,
"",
"",
].join("\r\n"));
state.brokerHandshakeSeen = true;
buffer = "";
});
});
return { server, sockets };
}
function createWorkerServer(state) {
return http.createServer(async (request, response) => {
const url = new URL(request.url, "http://127.0.0.1");
const body = await readJson(request);
state.requests.push({ service: "worker", method: request.method, path: url.pathname, body });
if (request.method === "GET" && url.pathname === "/health") {
sendJson(response, 200, { status: "healthy", timestamp: new Date().toISOString() });
return;
}
if (request.method === "POST" && url.pathname === "/relay/switch") {
state.relaySwitchSeen = true;
if (body.local_ip !== "10.123.0.31" || body.channel !== 0 || body.on !== true) {
state.failure = new Error(`unexpected relay switch payload: ${JSON.stringify(body)}`);
}
sendJson(response, 200, {
online: true,
on: true,
output: true,
raw: { source: "fake-worker" },
});
return;
}
sendJson(response, 404, { message: "not found" });
});
}
function createApiServer(state, brokerPort, workerPort) {
return http.createServer(async (request, response) => {
const url = new URL(request.url, "http://127.0.0.1");
const body = await readJson(request);
state.requests.push({ service: "api", method: request.method, path: url.pathname, body });
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/heartbeat") {
sendJson(response, 200, { data: { ok: true, broker_url: `ws://127.0.0.1:${brokerPort}` } });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/selfserve/machine-signal-bindings") {
sendJson(response, 200, { data: { monitors: [] } });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/commands/poll") {
state.commandPollSeen = true;
if (body.wait_seconds !== 0) {
state.failure = new Error(
`broker-connected command poll should be non-blocking, got wait_seconds=${body.wait_seconds}`
);
}
if (!state.commandDelivered) {
state.commandDelivered = true;
sendJson(response, 200, {
data: {
id: 77,
command_type: "SET_RELAY_STATE",
payload: {
localIp: "10.123.0.31",
channel: 0,
on: true,
relayId: "relay-proof",
},
},
});
return;
}
sendJson(response, 200, { data: null });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/commands/77/result") {
state.resultSeen = true;
if (body.ok !== true || body.result?.on !== true || body.result?.raw?.source !== "fake-worker") {
state.failure = new Error(`unexpected command result: ${JSON.stringify(body)}`);
}
sendJson(response, 200, { data: { acknowledged: true } });
return;
}
sendJson(response, 404, { message: "not found", path: url.pathname, workerPort });
});
}
function writeConfig(tempDir, apiPort, brokerPort, workerPort) {
const containerProofDir = "/proof";
const runtimeDir = `${containerProofDir}/runtime`;
const config = {
apiUrl: `http://127.0.0.1:${apiPort}`,
brokerUrl: `ws://127.0.0.1:${brokerPort}`,
gatewayId: 42,
agentToken: "agent-token",
installDir: containerProofDir,
runtimeDir,
stateDatabasePath: `${runtimeDir}/gateway-state.sqlite`,
workerBaseUrl: `http://127.0.0.1:${workerPort}`,
heartbeatIntervalSeconds: 60,
operationPollTimeoutSeconds: 20,
};
const configPath = path.join(tempDir, "config.json");
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
return { configPath, containerConfigPath: `${containerProofDir}/config.json` };
}
function spawnAgent({ agentPath, phpImage, tempDir, containerConfigPath }) {
return spawn("docker", [
"run",
"--rm",
"--network",
"host",
"-v",
`${agentPath}:/agent.php:ro`,
"-v",
`${tempDir}:/proof`,
phpImage,
"php",
"/agent.php",
"--config",
containerConfigPath,
], { stdio: ["ignore", "pipe", "pipe"] });
}
async function stopChild(child) {
if (child.exitCode !== null || child.signalCode !== null) {
return;
}
child.kill("SIGTERM");
const hardKill = setTimeout(() => {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
}
}, 1500);
await Promise.race([
new Promise((resolve) => child.once("exit", resolve)),
new Promise((resolve) => setTimeout(resolve, 2200)),
]);
clearTimeout(hardKill);
}
function evidenceFromState(state, childExited) {
return {
brokerHandshakeSeen: state.brokerHandshakeSeen,
commandPollSeen: state.commandPollSeen,
relaySwitchSeen: state.relaySwitchSeen,
resultSeen: state.resultSeen,
agentStayedRunningUntilProofComplete: !childExited,
};
}
export async function runProof(options) {
if (process.platform !== "linux") {
throw new Error("This proof uses Docker --network host and currently expects Linux.");
}
if (!fs.existsSync(options.agentPath)) {
throw new Error(`Agent artifact not found: ${options.agentPath}`);
}
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "edge-agent-command-drain-proof-"));
fs.mkdirSync(path.join(tempDir, "runtime"), { recursive: true });
const state = {
brokerHandshakeSeen: false,
commandPollSeen: false,
relaySwitchSeen: false,
resultSeen: false,
commandDelivered: false,
failure: null,
requests: [],
};
const broker = createBrokerServer(state);
const workerServer = createWorkerServer(state);
let apiServer = null;
let child = null;
let stdout = "";
let stderr = "";
let childExited = false;
try {
const brokerPort = await listen(broker.server);
const workerPort = await listen(workerServer);
apiServer = createApiServer(state, brokerPort, workerPort);
const apiPort = await listen(apiServer);
const { containerConfigPath } = writeConfig(tempDir, apiPort, brokerPort, workerPort);
child = spawnAgent({ ...options, tempDir, containerConfigPath });
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.once("exit", () => {
childExited = true;
});
const deadline = Date.now() + options.timeoutMs;
while (Date.now() < deadline && !state.failure && !childExited) {
if (state.brokerHandshakeSeen && state.commandPollSeen && state.relaySwitchSeen && state.resultSeen) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
const evidence = evidenceFromState(state, childExited);
if (
state.failure ||
!state.brokerHandshakeSeen ||
!state.commandPollSeen ||
!state.relaySwitchSeen ||
!state.resultSeen
) {
const error = state.failure || new Error("missing proof evidence");
error.evidence = evidence;
error.requests = state.requests;
error.stdout = stdout.slice(-3000);
error.stderr = stderr.slice(-3000);
throw error;
}
return {
evidence,
agentPath: options.agentPath,
phpImage: options.phpImage,
tempDir,
requestCount: state.requests.length,
};
} finally {
if (child) {
await stopChild(child);
}
for (const socket of broker.sockets) {
socket.destroy();
}
await Promise.allSettled([
closeServer(broker.server),
closeServer(workerServer),
apiServer ? closeServer(apiServer) : Promise.resolve(),
]);
if (!options.keepTemp) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
}
async function main() {
const options = parseArgs();
if (options.help) {
printUsage();
return;
}
const result = await runProof(options);
process.stdout.write("PASS broker-connected API command poll triggered local relay switch and posted result\n");
process.stdout.write(`${JSON.stringify(result.evidence)}\n`);
process.stdout.write(`Agent: ${result.agentPath}\n`);
process.stdout.write(`PHP image: ${result.phpImage}\n`);
if (options.keepTemp) {
process.stdout.write(`Temp dir: ${result.tempDir}\n`);
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
process.stderr.write(`FAIL ${error.message}\n`);
if (error.evidence) {
process.stderr.write(`Evidence: ${JSON.stringify(error.evidence)}\n`);
}
if (error.requests) {
process.stderr.write(`Requests: ${JSON.stringify(error.requests, null, 2)}\n`);
}
if (error.stdout) {
process.stderr.write(`stdout: ${error.stdout}\n`);
}
if (error.stderr) {
process.stderr.write(`stderr: ${error.stderr}\n`);
}
process.exit(1);
});
}
+619
View File
@@ -0,0 +1,619 @@
import { spawn } from "node:child_process";
import crypto from "node:crypto";
import fs from "node:fs";
import http from "node:http";
import net from "node:net";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const DEFAULT_AGENT_PATH = path.join(
repoRoot,
"services/nginx/app/resources/edge-gateway-agent/agent.php"
);
const DEFAULT_WORKER_PATH = path.join(
repoRoot,
"services/nginx/app/resources/edge-gateway-agent/lan-worker.php"
);
const DEFAULT_PHP_IMAGE = "php:8.2-cli-bookworm";
const DEFAULT_TIMEOUT_MS = 15000;
const AGENT_TOKEN = "agent-token";
function parseArgs(argv = process.argv.slice(2)) {
const options = {
agentPath: DEFAULT_AGENT_PATH,
workerPath: DEFAULT_WORKER_PATH,
phpImage: DEFAULT_PHP_IMAGE,
timeoutMs: DEFAULT_TIMEOUT_MS,
keepTemp: false,
help: false,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
const next = argv[index + 1];
switch (arg) {
case "--agent-path":
options.agentPath = path.resolve(String(next || "").trim());
index += 1;
break;
case "--worker-path":
options.workerPath = path.resolve(String(next || "").trim());
index += 1;
break;
case "--php-image":
options.phpImage = String(next || "").trim() || DEFAULT_PHP_IMAGE;
index += 1;
break;
case "--timeout-ms":
options.timeoutMs = Number.parseInt(String(next || ""), 10) || DEFAULT_TIMEOUT_MS;
index += 1;
break;
case "--keep-temp":
options.keepTemp = true;
break;
case "--help":
case "-h":
options.help = true;
break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
return options;
}
function printUsage() {
process.stdout.write(`Usage:
node scripts/edge-agent-to-shelly-proof.mjs [options]
Runs the PHP edge agent and real LAN worker against fake broker, API, and
Shelly RPC endpoints. Verifies that a broker-connected SET_RELAY_STATE command
drains from the API, reaches the worker, triggers a Shelly-style Switch.Set
call, reads Switch.GetStatus, and posts the command result.
Options:
--agent-path <path> PHP agent artifact to execute.
Default: ${DEFAULT_AGENT_PATH}
--worker-path <path> PHP LAN worker artifact to execute.
Default: ${DEFAULT_WORKER_PATH}
--php-image <image> Docker PHP image with curl, sqlite3, and pdo_sqlite.
Default: ${DEFAULT_PHP_IMAGE}
--timeout-ms <ms> Proof timeout. Default: ${DEFAULT_TIMEOUT_MS}
--keep-temp Keep the temporary config/runtime directory.
--help Show this help text.
`);
}
function readJson(request) {
return new Promise((resolve) => {
let raw = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
raw += chunk;
});
request.on("end", () => {
if (raw.trim() === "") {
resolve({});
return;
}
try {
resolve(JSON.parse(raw));
} catch {
resolve({ __invalid: raw });
}
});
});
}
function sendJson(response, status, payload) {
const body = JSON.stringify(payload);
response.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(body),
});
response.end(body);
}
function requestJson({ method = "GET", port, path: requestPath, body = null, headers = {} }) {
return new Promise((resolve, reject) => {
const payload = body === null ? null : JSON.stringify(body);
const request = http.request({
hostname: "127.0.0.1",
port,
path: requestPath,
method,
headers: {
accept: "application/json",
...(payload === null ? {} : {
"content-type": "application/json",
"content-length": Buffer.byteLength(payload),
}),
...headers,
},
timeout: 1000,
}, (response) => {
let raw = "";
response.setEncoding("utf8");
response.on("data", (chunk) => {
raw += chunk;
});
response.on("end", () => {
let decoded;
try {
decoded = raw.trim() === "" ? {} : JSON.parse(raw);
} catch {
decoded = { __invalid: raw };
}
resolve({ status: response.statusCode || 0, body: decoded });
});
});
request.on("error", reject);
request.on("timeout", () => {
request.destroy(new Error("request timed out"));
});
if (payload !== null) {
request.write(payload);
}
request.end();
});
}
function listen(server) {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => resolve(server.address().port));
});
}
function closeServer(server) {
return new Promise((resolve) => {
server.close(() => resolve());
});
}
async function reservePort() {
const server = net.createServer();
const port = await new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => resolve(server.address().port));
});
await closeServer(server);
return port;
}
function websocketAcceptKey(key) {
return crypto
.createHash("sha1")
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
.digest("base64");
}
function createBrokerServer(state) {
const sockets = new Set();
const server = net.createServer((socket) => {
sockets.add(socket);
socket.on("close", () => sockets.delete(socket));
let buffer = "";
socket.on("data", (chunk) => {
buffer += chunk.toString("binary");
if (state.brokerHandshakeSeen || !buffer.includes("\r\n\r\n")) {
return;
}
const requestText = Buffer.from(buffer, "binary").toString("utf8");
const key = requestText.match(/Sec-WebSocket-Key:\s*(.+)\r\n/i)?.[1]?.trim();
const requestLine = requestText.split("\r\n")[0] || "";
if (!requestLine.includes("/ws/agent?")) {
state.failure = new Error(`unexpected broker path: ${requestLine}`);
}
if (!key) {
state.failure = new Error("broker handshake missing Sec-WebSocket-Key");
return;
}
socket.write([
"HTTP/1.1 101 Switching Protocols",
"Upgrade: websocket",
"Connection: Upgrade",
`Sec-WebSocket-Accept: ${websocketAcceptKey(key)}`,
"",
"",
].join("\r\n"));
state.brokerHandshakeSeen = true;
buffer = "";
});
});
return { server, sockets };
}
function createShellyServer(state) {
return http.createServer((request, response) => {
const url = new URL(request.url, "http://127.0.0.1");
state.requests.push({
service: "shelly",
method: request.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams.entries()),
});
if (request.method === "GET" && url.pathname === "/rpc/Switch.Set") {
state.shellySwitchSetSeen = true;
if (url.searchParams.get("id") !== "0" || url.searchParams.get("on") !== "true") {
state.failure = new Error(`unexpected Shelly Switch.Set query: ${url.search}`);
}
sendJson(response, 200, { was_on: false, output: true });
return;
}
if (request.method === "GET" && url.pathname === "/rpc/Switch.GetStatus") {
state.shellyStatusSeen = true;
if (url.searchParams.get("id") !== "0") {
state.failure = new Error(`unexpected Shelly Switch.GetStatus query: ${url.search}`);
}
sendJson(response, 200, { id: 0, output: true, source: "fake-shelly-rpc" });
return;
}
if (url.pathname.startsWith("/relay/")) {
state.failure = new Error(`legacy Shelly endpoint should not be used for generation 2 proof: ${url.pathname}`);
}
sendJson(response, 404, { message: "not found" });
});
}
function createApiServer(state, brokerPort, shellyAddress) {
return http.createServer(async (request, response) => {
const url = new URL(request.url, "http://127.0.0.1");
const body = await readJson(request);
state.requests.push({ service: "api", method: request.method, path: url.pathname, body });
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/heartbeat") {
sendJson(response, 200, { data: { ok: true, broker_url: `ws://127.0.0.1:${brokerPort}` } });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/selfserve/machine-signal-bindings") {
sendJson(response, 200, { data: { monitors: [] } });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/commands/poll") {
state.commandPollSeen = true;
if (body.wait_seconds !== 0) {
state.failure = new Error(
`broker-connected command poll should be non-blocking, got wait_seconds=${body.wait_seconds}`
);
}
if (!state.commandDelivered) {
state.commandDelivered = true;
sendJson(response, 200, {
data: {
id: 77,
command_type: "SET_RELAY_STATE",
payload: {
localIp: shellyAddress,
local_ip: shellyAddress,
channel: 0,
on: true,
relayId: "relay-proof",
relay_id: "relay-proof",
deviceGeneration: 2,
device_generation: 2,
},
},
});
return;
}
sendJson(response, 200, { data: null });
return;
}
if (request.method === "POST" && url.pathname === "/edge-agent/gateways/42/commands/77/result") {
state.resultSeen = true;
if (
body.ok !== true ||
body.result?.on !== true ||
body.result?.output !== true ||
body.result?.raw?.source !== "fake-shelly-rpc"
) {
state.failure = new Error(`unexpected command result: ${JSON.stringify(body)}`);
}
sendJson(response, 200, { data: { acknowledged: true } });
return;
}
sendJson(response, 404, { message: "not found", path: url.pathname });
});
}
function writeConfig(tempDir, apiPort, brokerPort, workerPort) {
const containerProofDir = "/proof";
const runtimeDir = `${containerProofDir}/runtime`;
const config = {
apiUrl: `http://127.0.0.1:${apiPort}`,
brokerUrl: `ws://127.0.0.1:${brokerPort}`,
gatewayId: 42,
agentToken: AGENT_TOKEN,
installDir: containerProofDir,
runtimeDir,
stateDatabasePath: `${runtimeDir}/gateway-state.sqlite`,
workerBaseUrl: `http://127.0.0.1:${workerPort}`,
heartbeatIntervalSeconds: 60,
operationPollTimeoutSeconds: 20,
};
const configPath = path.join(tempDir, "config.json");
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
return { containerConfigPath: `${containerProofDir}/config.json` };
}
function spawnWorker({ workerPath, phpImage, workerPort }) {
return spawn("docker", [
"run",
"--rm",
"--network",
"host",
"-e",
`TRUCKWASH_WORKER_TOKEN=${AGENT_TOKEN}`,
"-v",
`${workerPath}:/lan-worker.php:ro`,
phpImage,
"php",
"-S",
`127.0.0.1:${workerPort}`,
"/lan-worker.php",
], { stdio: ["ignore", "pipe", "pipe"] });
}
function spawnAgent({ agentPath, phpImage, tempDir, containerConfigPath }) {
return spawn("docker", [
"run",
"--rm",
"--network",
"host",
"-v",
`${agentPath}:/agent.php:ro`,
"-v",
`${tempDir}:/proof`,
phpImage,
"php",
"/agent.php",
"--config",
containerConfigPath,
], { stdio: ["ignore", "pipe", "pipe"] });
}
async function waitForWorker(workerPort, child, timeoutMs) {
const deadline = Date.now() + timeoutMs;
let lastError = null;
while (Date.now() < deadline) {
if (child.exitCode !== null || child.signalCode !== null) {
throw new Error(`LAN worker exited before becoming healthy: ${child.exitCode ?? child.signalCode}`);
}
try {
const response = await requestJson({ port: workerPort, path: "/health" });
if (response.status === 200 && response.body?.service === "lan-worker") {
return;
}
} catch (error) {
lastError = error;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw lastError || new Error("LAN worker did not become healthy");
}
async function stopChild(child) {
if (child.exitCode !== null || child.signalCode !== null) {
return;
}
child.kill("SIGTERM");
const hardKill = setTimeout(() => {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
}
}, 1500);
await Promise.race([
new Promise((resolve) => child.once("exit", resolve)),
new Promise((resolve) => setTimeout(resolve, 2200)),
]);
clearTimeout(hardKill);
}
function evidenceFromState(state, agentExited, workerExited) {
return {
brokerHandshakeSeen: state.brokerHandshakeSeen,
commandPollSeen: state.commandPollSeen,
shellySwitchSetSeen: state.shellySwitchSetSeen,
shellyStatusSeen: state.shellyStatusSeen,
resultSeen: state.resultSeen,
agentStayedRunningUntilProofComplete: !agentExited,
workerStayedRunningUntilProofComplete: !workerExited,
};
}
export async function runProof(options) {
if (process.platform !== "linux") {
throw new Error("This proof uses Docker --network host and currently expects Linux.");
}
if (!fs.existsSync(options.agentPath)) {
throw new Error(`Agent artifact not found: ${options.agentPath}`);
}
if (!fs.existsSync(options.workerPath)) {
throw new Error(`LAN worker artifact not found: ${options.workerPath}`);
}
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "edge-agent-to-shelly-proof-"));
fs.mkdirSync(path.join(tempDir, "runtime"), { recursive: true });
const state = {
brokerHandshakeSeen: false,
commandPollSeen: false,
shellySwitchSetSeen: false,
shellyStatusSeen: false,
resultSeen: false,
commandDelivered: false,
failure: null,
requests: [],
};
const broker = createBrokerServer(state);
const shellyServer = createShellyServer(state);
let apiServer = null;
let agent = null;
let worker = null;
let agentStdout = "";
let agentStderr = "";
let workerStdout = "";
let workerStderr = "";
let agentExited = false;
let workerExited = false;
try {
const brokerPort = await listen(broker.server);
const shellyPort = await listen(shellyServer);
const workerPort = await reservePort();
const shellyAddress = `127.0.0.1:${shellyPort}`;
apiServer = createApiServer(state, brokerPort, shellyAddress);
const apiPort = await listen(apiServer);
const { containerConfigPath } = writeConfig(tempDir, apiPort, brokerPort, workerPort);
worker = spawnWorker({ ...options, workerPort });
worker.stdout.on("data", (chunk) => {
workerStdout += chunk.toString();
});
worker.stderr.on("data", (chunk) => {
workerStderr += chunk.toString();
});
worker.once("exit", () => {
workerExited = true;
});
await waitForWorker(workerPort, worker, 5000);
agent = spawnAgent({ ...options, tempDir, containerConfigPath });
agent.stdout.on("data", (chunk) => {
agentStdout += chunk.toString();
});
agent.stderr.on("data", (chunk) => {
agentStderr += chunk.toString();
});
agent.once("exit", () => {
agentExited = true;
});
const deadline = Date.now() + options.timeoutMs;
while (Date.now() < deadline && !state.failure && !agentExited && !workerExited) {
if (
state.brokerHandshakeSeen &&
state.commandPollSeen &&
state.shellySwitchSetSeen &&
state.shellyStatusSeen &&
state.resultSeen
) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
const evidence = evidenceFromState(state, agentExited, workerExited);
if (
state.failure ||
!state.brokerHandshakeSeen ||
!state.commandPollSeen ||
!state.shellySwitchSetSeen ||
!state.shellyStatusSeen ||
!state.resultSeen
) {
const error = state.failure || new Error("missing proof evidence");
error.evidence = evidence;
error.requests = state.requests;
error.agentStdout = agentStdout.slice(-3000);
error.agentStderr = agentStderr.slice(-3000);
error.workerStdout = workerStdout.slice(-3000);
error.workerStderr = workerStderr.slice(-3000);
throw error;
}
return {
evidence,
agentPath: options.agentPath,
workerPath: options.workerPath,
phpImage: options.phpImage,
tempDir,
requestCount: state.requests.length,
};
} finally {
if (agent) {
await stopChild(agent);
}
if (worker) {
await stopChild(worker);
}
for (const socket of broker.sockets) {
socket.destroy();
}
await Promise.allSettled([
closeServer(broker.server),
closeServer(shellyServer),
apiServer ? closeServer(apiServer) : Promise.resolve(),
]);
if (!options.keepTemp) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
}
async function main() {
const options = parseArgs();
if (options.help) {
printUsage();
return;
}
const result = await runProof(options);
process.stdout.write("PASS broker-connected API command triggered real LAN worker Shelly RPC signal and posted result\n");
process.stdout.write(`${JSON.stringify(result.evidence)}\n`);
process.stdout.write(`Agent: ${result.agentPath}\n`);
process.stdout.write(`LAN worker: ${result.workerPath}\n`);
process.stdout.write(`PHP image: ${result.phpImage}\n`);
if (options.keepTemp) {
process.stdout.write(`Temp dir: ${result.tempDir}\n`);
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
process.stderr.write(`FAIL ${error.message}\n`);
if (error.evidence) {
process.stderr.write(`Evidence: ${JSON.stringify(error.evidence)}\n`);
}
if (error.requests) {
process.stderr.write(`Requests: ${JSON.stringify(error.requests, null, 2)}\n`);
}
if (error.agentStdout) {
process.stderr.write(`agent stdout: ${error.agentStdout}\n`);
}
if (error.agentStderr) {
process.stderr.write(`agent stderr: ${error.agentStderr}\n`);
}
if (error.workerStdout) {
process.stderr.write(`worker stdout: ${error.workerStdout}\n`);
}
if (error.workerStderr) {
process.stderr.write(`worker stderr: ${error.workerStderr}\n`);
}
process.exit(1);
});
}
+377 -74
View File
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
import { DEFAULT_CONFIG_FILE_NAME, DEFAULT_HOST_API_URL } from "./test-gateway.mjs";
const execFile = promisify(execFileCallback);
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "caddy"];
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "php2", "php3", "php4", "php5", "caddy"];
function composeArgs(projectName, args) {
return ["compose", "-p", projectName, ...args];
@@ -75,6 +75,233 @@ function normalizeBaseUrl(url) {
return String(url || "").replace(/\/+$/, "");
}
function baseUrlWithHost(baseUrl, host, port = null) {
const url = new URL(normalizeBaseUrl(baseUrl));
url.hostname = host;
if (port !== null) {
url.port = port;
}
return normalizeBaseUrl(url.toString());
}
function directCaddyBaseUrl(baseUrl) {
const url = new URL(normalizeBaseUrl(baseUrl));
url.hostname = "caddy";
url.port = "";
if (url.pathname === "/api" || url.pathname === "/api/") {
url.pathname = "/";
}
return normalizeBaseUrl(url.toString());
}
function isLocalHost(hostname) {
const normalized = String(hostname || "").toLowerCase().replace(/^\x5b|\x5d$/g, "");
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
}
function resolveBrokerWebSocketUrl(rawUrl, apiBaseUrl) {
const websocketUrl = new URL(String(rawUrl));
const apiUrl = new URL(normalizeBaseUrl(apiBaseUrl));
const ciBrokerPort = String(process.env.EDGE_BROKER_CI_PORT || "").trim();
if (isLocalHost(apiUrl.hostname) && websocketUrl.hostname === "edge-broker" && ciBrokerPort !== "") {
websocketUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
websocketUrl.hostname = apiUrl.hostname;
websocketUrl.port = ciBrokerPort;
websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
return websocketUrl.toString();
}
if (apiUrl.hostname === "caddy" && websocketUrl.hostname === "caddy") {
websocketUrl.hostname = "edge-broker";
websocketUrl.port = "4300";
websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
}
if (isLocalHost(apiUrl.hostname) && ["caddy", "edge-broker"].includes(websocketUrl.hostname)) {
const brokerPath = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
websocketUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
websocketUrl.hostname = apiUrl.hostname;
websocketUrl.port = apiUrl.port;
websocketUrl.pathname = `/api/edge-broker${brokerPath}`;
}
if (isLocalHost(websocketUrl.hostname)) {
websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
}
return websocketUrl.toString();
}
async function readDefaultGatewayHost() {
if (process.platform === "win32") {
return null;
}
try {
const routeTable = await fs.readFile("/proc/net/route", "utf8");
const route = routeTable
.split(/\r?\n/)
.map((line) => line.trim().split(/\s+/))
.find((fields) => fields[1] === "00000000" && /^[0-9A-Fa-f]{8}$/.test(fields[2] || ""));
if (!route) {
return null;
}
const gateway = route[2];
const octets = [
gateway.slice(6, 8),
gateway.slice(4, 6),
gateway.slice(2, 4),
gateway.slice(0, 2),
].map((octet) => Number.parseInt(octet, 16));
if (octets.some((octet) => !Number.isInteger(octet)) || octets.every((octet) => octet === 0)) {
return null;
}
return octets.join(".");
} catch {
return null;
}
}
function composeNetworkName(composeProject) {
return `${composeProject}_default`;
}
async function readCurrentContainerRef() {
if (process.platform === "win32") {
return null;
}
const candidates = [];
const envHostname = String(process.env.HOSTNAME || "").trim();
if (envHostname !== "") {
candidates.push(envHostname);
}
try {
const hostname = (await fs.readFile("/etc/hostname", "utf8")).trim();
if (hostname !== "") {
candidates.push(hostname);
}
} catch {
// Not running in a container with /etc/hostname available.
}
try {
const cgroup = await fs.readFile("/proc/self/cgroup", "utf8");
const matches = cgroup.match(/[0-9a-f]{64}/gi) || [];
candidates.push(...matches);
} catch {
// cgroup metadata is optional in local development.
}
return candidates.find((candidate) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,127}$/.test(candidate)) || null;
}
async function connectCurrentContainerToComposeNetwork(rootDir, composeProject) {
const containerRef = await readCurrentContainerRef();
if (!containerRef) {
return false;
}
const inspection = await runCommand("docker", ["inspect", containerRef], {
cwd: rootDir,
allowFailure: true,
});
if (inspection.code !== 0) {
return false;
}
const networkName = composeNetworkName(composeProject);
const connection = await runCommand("docker", ["network", "connect", networkName, containerRef], {
cwd: rootDir,
allowFailure: true,
});
const stderr = String(connection.stderr || "");
if (connection.code === 0) {
process.stdout.write(`Attached runner container ${containerRef} to ${networkName}.\n`);
return true;
}
return /already exists|already connected/i.test(stderr);
}
async function disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject) {
const containerRef = await readCurrentContainerRef();
if (!containerRef) {
return;
}
await runCommand("docker", ["network", "disconnect", composeNetworkName(composeProject), containerRef], {
cwd: rootDir,
allowFailure: true,
});
}
async function readComposeServiceHost(rootDir, composeProject, serviceName) {
const ps = await runCommand("docker", composeArgs(composeProject, ["ps", "-q", serviceName]), {
cwd: rootDir,
allowFailure: true,
});
const containerId = String(ps.stdout || "").trim().split(/\s+/).find(Boolean);
const containerRefs = [
...(containerId ? [containerId] : []),
serviceName,
];
for (const containerRef of [...new Set(containerRefs)]) {
const inspection = await runCommand("docker", [
"inspect",
"-f",
"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}",
containerRef,
], {
cwd: rootDir,
allowFailure: true,
});
const host = String(inspection.stdout || "").trim().split(/\s+/).find((value) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value));
if (host) {
return host;
}
}
return null;
}
async function candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork = false) {
const normalized = normalizeBaseUrl(baseUrl);
const candidates = [normalized];
const url = new URL(normalized);
if (["localhost", "127.0.0.1", "::1"].includes(url.hostname)) {
if (rootDir && composeProject) {
if (useComposeNetwork) {
candidates.push(directCaddyBaseUrl(normalized));
candidates.push(baseUrlWithHost(normalized, "traefik", ""));
}
const traefikHost = await readComposeServiceHost(rootDir, composeProject, "traefik");
if (traefikHost) {
candidates.push(baseUrlWithHost(normalized, traefikHost, ""));
}
}
const gatewayHost = await readDefaultGatewayHost();
if (gatewayHost) {
candidates.push(baseUrlWithHost(normalized, gatewayHost));
}
candidates.push(baseUrlWithHost(normalized, "host.docker.internal"));
}
return [...new Set(candidates)];
}
async function waitForCondition(predicate, { timeoutMs = 30_000, intervalMs = 500, message = "Timed out" } = {}) {
const deadline = Date.now() + timeoutMs;
@@ -96,26 +323,30 @@ async function ensureComposeServices(rootDir, composeProject) {
});
}
async function waitForApiReady(baseUrl, attempts = 60) {
const root = normalizeBaseUrl(baseUrl);
async function waitForApiReady(baseUrl, rootDir, composeProject, useComposeNetwork = false, attempts = 60) {
const candidates = await candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork);
let lastError = "API never responded";
for (let attempt = 0; attempt < attempts; attempt += 1) {
try {
const response = await fetch(`${root}/ping`);
if (response.ok) {
return;
}
for (const root of candidates) {
try {
const response = await fetch(`${root}/ping`, {
signal: AbortSignal.timeout(1000),
});
if (response.ok) {
return root;
}
lastError = `Unexpected ping status ${response.status}`;
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
lastError = `${root}/ping returned HTTP ${response.status}`;
} catch (error) {
lastError = `${root}/ping failed: ${error instanceof Error ? error.message : String(error)}`;
}
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
throw new Error(`API did not become ready at ${root}/ping: ${lastError}`);
throw new Error(`API did not become ready at ${candidates.map((candidate) => `${candidate}/ping`).join(", ")}: ${lastError}`);
}
function parseLastJsonLine(output) {
@@ -301,6 +532,14 @@ function closeSocket(socket) {
socket.close();
}
function shouldCopyGatewayConfig() {
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_COPY_CONFIG || "").trim());
}
function shouldSkipComposeUp() {
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP || "").trim());
}
function collectMessages(rows) {
return Array.isArray(rows)
? rows
@@ -338,6 +577,38 @@ function summarizeStreamMessages(messages, limit = 12) {
.filter(Boolean);
}
async function readGatewayDiagnostics({ baseUrl, authToken, gatewayId, containerName }) {
const diagnostics = {};
if (gatewayId !== null && gatewayId > 0 && authToken) {
try {
const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
token: authToken,
});
diagnostics.gateway = {
status: detail?.data?.status ?? null,
channelStatus: detail?.data?.channel_status ?? null,
brokerPresence: detail?.data?.metadata?.broker_presence ?? null,
brokerConnected: detail?.data?.metadata?.broker_connected ?? null,
brokerLastError: detail?.data?.metadata?.broker_last_error ?? null,
};
} catch (error) {
diagnostics.gatewayError = error instanceof Error ? error.message : String(error);
}
}
try {
const logs = await runCommand("docker", ["logs", "--tail", "120", containerName], {
allowFailure: true,
});
diagnostics.containerLogs = String(`${logs.stdout || ""}${logs.stderr || ""}`).trim().split(/\r?\n/).slice(-120);
} catch (error) {
diagnostics.containerLogError = error instanceof Error ? error.message : String(error);
}
return diagnostics;
}
async function main() {
const scriptPath = fileURLToPath(import.meta.url);
const rootDir = await resolveRootDir(scriptPath);
@@ -345,7 +616,7 @@ async function main() {
const containerName = `truckwash-edge-e2e-${runId}`;
const configDir = path.join(rootDir, ".tmp", "edge-gateway-e2e", runId);
const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
const baseUrl = process.env.EDGE_GATEWAY_E2E_BASE_URL || DEFAULT_HOST_API_URL;
let baseUrl = process.env.EDGE_GATEWAY_E2E_BASE_URL || DEFAULT_HOST_API_URL;
const composeProject =
process.env.EDGE_GATEWAY_E2E_COMPOSE_PROJECT
|| path.basename(rootDir);
@@ -354,10 +625,15 @@ async function main() {
let gatewayId = null;
let streamSocket = null;
let shellSocket = null;
let runnerNetworkAttached = false;
try {
await ensureComposeServices(rootDir, composeProject);
await waitForApiReady(baseUrl);
if (!shouldSkipComposeUp()) {
await ensureComposeServices(rootDir, composeProject);
}
runnerNetworkAttached = await connectCurrentContainerToComposeNetwork(rootDir, composeProject);
baseUrl = await waitForApiReady(baseUrl, rootDir, composeProject, runnerNetworkAttached);
process.stdout.write(`Using API base URL ${baseUrl}\n`);
fixture = await runPhpFixture(rootDir, composeProject, "create");
const authToken = String(fixture.auth_token || "");
@@ -381,6 +657,8 @@ async function main() {
"start",
"--install-token",
installToken,
"--host-api-url",
baseUrl,
"--container-name",
containerName,
"--config-dir",
@@ -388,6 +666,7 @@ async function main() {
"--heartbeat-seconds",
"3",
"--skip-compose-up",
...(shouldCopyGatewayConfig() ? ["--copy-config"] : []),
], {
cwd: rootDir,
stdio: "inherit",
@@ -444,22 +723,35 @@ async function main() {
}
);
await waitForCondition(
async () => {
const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
token: authToken,
});
try {
await waitForCondition(
async () => {
const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
token: authToken,
});
return Boolean(
detail?.data?.channel_status?.broker?.connected
|| detail?.data?.metadata?.broker_connected
);
},
{
timeoutMs: 90_000,
message: "Gateway never established a live broker connection after install.",
}
);
return Boolean(
detail?.data?.channel_status?.broker?.connected
|| detail?.data?.metadata?.broker_connected
);
},
{
timeoutMs: 90_000,
message: "Gateway never established a live broker connection after install.",
}
);
} catch (error) {
const diagnostics = await readGatewayDiagnostics({
baseUrl,
authToken,
gatewayId,
containerName,
});
throw new Error([
error instanceof Error ? error.message : String(error),
`Broker diagnostics: ${JSON.stringify(diagnostics, null, 2)}`,
].join("\n"));
}
const WebSocketImpl = await loadWebSocketImplementation();
const streamSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/stream-session`, {
@@ -468,7 +760,10 @@ async function main() {
scopes: ["overview", "tasks", "logs", "statistics"],
},
});
const streamWsUrl = buildSocketUrl(String(streamSession?.data?.ws_url || ""), String(streamSession?.data?.token || ""));
const streamWsUrl = buildSocketUrl(
resolveBrokerWebSocketUrl(String(streamSession?.data?.ws_url || ""), baseUrl),
String(streamSession?.data?.token || "")
);
streamSocket = new WebSocketImpl(streamWsUrl);
const streamMessages = collectSocketMessages(streamSocket);
@@ -479,11 +774,14 @@ async function main() {
{ timeoutMs: 15_000, message: "Gateway stream never became ready." }
);
const readyMessage = streamMessages.find((message) => message?.type === "gateway.stream.ready");
assert.equal(
Boolean(readyMessage?.connected),
true,
"Gateway stream became ready before the broker reported the gateway as connected."
await waitForSocketMessage(
streamMessages,
(message) => (
message?.type === "gateway.stream.ready" && message?.connected === true
) || (
message?.type === "presence.changed" && message?.status === "connected"
),
{ timeoutMs: 45_000, message: "Gateway stream never observed a connected broker presence." }
);
const operationResponse = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/operations`, {
@@ -511,13 +809,27 @@ async function main() {
assert.ok(operationId > 0, "Operation creation did not return an operation id.");
try {
await waitForSocketMessage(
streamMessages,
(message) => message?.type === "task.updated" && Number(message?.operationId || 0) === operationId,
{ timeoutMs: 180_000, message: "Live gateway stream never emitted task.updated for the queued operation." }
await waitForCondition(
async () => {
const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
token: authToken,
});
const operation = Array.isArray(operations?.data)
? operations.data.find((item) => Number(item?.id || 0) === operationId)
: null;
return operation?.status === "COMPLETED"
|| streamMessages.some((message) => (
message?.type === "task.updated"
&& Number(message?.operationId || 0) === operationId
&& message?.operation?.status === "COMPLETED"
));
},
{ timeoutMs: 180_000, message: "Gateway operation never completed through the live agent." }
);
} catch (error) {
let operationSnapshot = null;
let operationSnapshot;
try {
const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
token: authToken,
@@ -537,21 +849,6 @@ async function main() {
throw new Error(diagnostic);
}
await waitForCondition(
async () => {
const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
token: authToken,
});
const operation = Array.isArray(operations?.data)
? operations.data.find((item) => Number(item?.id || 0) === operationId)
: null;
return operation?.status === "COMPLETED";
},
{ timeoutMs: 180_000, message: "Gateway operation never completed through the live agent." }
);
await waitForSocketMessage(
streamMessages,
(message) => message?.type === "gateway.telemetry" || message?.type === "stats.updated",
@@ -596,7 +893,10 @@ async function main() {
rows: 40,
},
});
const shellWsUrl = buildSocketUrl(String(shellSession?.data?.ws_url || ""), String(shellSession?.data?.token || ""));
const shellWsUrl = buildSocketUrl(
resolveBrokerWebSocketUrl(String(shellSession?.data?.ws_url || ""), baseUrl),
String(shellSession?.data?.token || "")
);
shellSocket = new WebSocketImpl(shellWsUrl);
const shellMessages = collectSocketMessages(shellSocket);
@@ -624,22 +924,20 @@ async function main() {
{ timeoutMs: 20_000, message: "Browser shell never closed cleanly." }
);
const logsAfterShell = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
token: authToken,
});
const shellTranscripts = Array.isArray(logsAfterShell?.data?.shell_sessions)
? logsAfterShell.data.shell_sessions.map((session) => String(session?.transcript || ""))
: [];
await waitForCondition(
async () => {
const logsAfterShell = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
token: authToken,
});
const shellTranscripts = Array.isArray(logsAfterShell?.data?.shell_sessions)
? logsAfterShell.data.shell_sessions.map((session) => String(session?.transcript || ""))
: [];
const timelineMessages = collectMessages(logsAfterShell?.data?.timeline || []);
assert.ok(
shellTranscripts.some((transcript) => transcript.includes("edge-e2e-shell")),
"Gateway logs page did not persist the shell transcript."
);
const timelineMessages = collectMessages(logsAfterShell?.data?.timeline || []);
assert.ok(
timelineMessages.includes("GATEWAY_SHELL_SESSION_CLOSED"),
"Gateway logs page did not include the shell close audit event."
return shellTranscripts.some((transcript) => transcript.includes("edge-e2e-shell"))
&& timelineMessages.includes("GATEWAY_SHELL_SESSION_CLOSED");
},
{ timeoutMs: 30_000, message: "Gateway logs page did not persist the shell transcript and close audit event." }
);
process.stdout.write("Edge gateway E2E smoke completed successfully.\n");
@@ -657,9 +955,10 @@ async function main() {
allowFailure: true,
}).catch(() => {});
if (gatewayId !== null && fixture?.auth_token) {
const fixtureAuthToken = fixture?.auth_token;
if (gatewayId !== null && fixtureAuthToken) {
await apiRequest(baseUrl, "DELETE", `/edge-gateways/${gatewayId}`, {
token: String(fixture.auth_token),
token: String(fixtureAuthToken),
}).catch(() => {});
}
@@ -668,6 +967,10 @@ async function main() {
}
await fs.rm(configDir, { recursive: true, force: true }).catch(() => {});
if (runnerNetworkAttached) {
await disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject).catch(() => {});
}
}
}
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env sh
set -eu
suite="${1:-}"
case "$suite" in
unit|integration|api|legacy|all)
;;
*)
echo "Usage: $0 <unit|integration|api|legacy|all>" >&2
exit 2
;;
esac
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)"
cd "$repo_root"
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
project_suffix="$(date +%s)-$$"
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
export COMPOSE_PROFILES="${COMPOSE_PROFILES:-dev}"
log_dir=".tmp/ci-logs/$suite"
mkdir -p "$log_dir"
env_backup_dir=".tmp/php-ci-env-backup-$project_suffix"
mkdir -p "$env_backup_dir"
had_env=0
had_env_staging=0
if [ -f .env ]; then
cp .env "$env_backup_dir/env"
had_env=1
fi
if [ -f .env.staging ]; then
cp .env.staging "$env_backup_dir/env.staging"
had_env_staging=1
fi
cp .github/ci.env .env
cp .github/ci.env.staging .env.staging
collect_logs() {
status="$1"
if [ "$status" -eq 0 ]; then
return
fi
mkdir -p "$log_dir"
docker compose $compose_files ps > "$log_dir/docker-compose-ps.txt" 2>&1 || true
docker compose $compose_files logs --no-color > "$log_dir/docker-compose.log" 2>&1 || true
docker compose $compose_files cp php1:/var/www/html/build/logs "$log_dir/app-build-logs" >/dev/null 2>&1 || true
docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true
}
retry_command() {
max_attempts="$1"
shift
attempt=1
while :; do
"$@" && return 0
status="$?"
if [ "$attempt" -ge "$max_attempts" ]; then
return "$status"
fi
sleep_seconds=$((attempt * 5))
echo "Command failed with status $status; retrying in ${sleep_seconds}s (attempt $((attempt + 1))/$max_attempts): $*" >&2
sleep "$sleep_seconds"
attempt=$((attempt + 1))
done
}
composer_install() {
dist_attempts="${PHP_CI_COMPOSER_RETRIES:-3}"
source_attempts="${PHP_CI_COMPOSER_SOURCE_RETRIES:-2}"
if retry_command "$dist_attempts" \
docker compose $compose_files exec -T php1 sh -lc \
'cd /var/www/html && composer install --no-interaction --prefer-dist --no-progress'; then
return 0
fi
echo "Composer dist install failed after ${dist_attempts} attempts; retrying with --prefer-source." >&2
retry_command "$source_attempts" \
docker compose $compose_files exec -T php1 sh -lc \
'cd /var/www/html && composer install --no-interaction --prefer-source --no-progress'
}
configure_ci_docker_subnet() {
if [ -n "${CI_DOCKER_SUBNET:-}" ]; then
return
fi
checksum="$(printf '%s' "$COMPOSE_PROJECT_NAME" | cksum | awk '{print $1}')"
subnet_second=$((64 + ((checksum / 256) % 64)))
subnet_third=$((checksum % 256))
export CI_DOCKER_SUBNET="10.${subnet_second}.${subnet_third}.0/24"
}
cleanup() {
status="$?"
collect_logs "$status"
docker compose $compose_files down -v >/dev/null 2>&1 || true
if [ "$had_env" -eq 1 ]; then
cp "$env_backup_dir/env" .env
else
rm -f .env
fi
if [ "$had_env_staging" -eq 1 ]; then
cp "$env_backup_dir/env.staging" .env.staging
else
rm -f .env.staging
fi
rm -rf "$env_backup_dir"
exit "$status"
}
trap cleanup EXIT INT TERM
configure_ci_docker_subnet
sh scripts/ci-docker-compose-up.sh redis mysql-debug php1
docker compose $compose_files exec -T php1 sh -lc '
set -eu
for i in $(seq 1 90); do
if MYSQL_PWD="${CONFIG_DB_PASSWORD:-debug_root_password}" mysqladmin \
-h "${CONFIG_DB_HOST:-mysql-debug}" \
-P "${CONFIG_DB_PORT:-3306}" \
-u "${CONFIG_DB_USER:-root}" \
ping --silent >/dev/null 2>&1; then
exit 0
fi
sleep 1
done
echo "Timed out waiting for mysql-debug" >&2
exit 1
'
tar \
--exclude='./vendor' \
--exclude='./.phpunit.cache' \
--exclude='./build/logs' \
-C services/nginx/app -cf - . \
| docker compose $compose_files exec -T php1 tar --no-same-owner -C /var/www/html -xf -
docker compose $compose_files exec -T php1 sh -lc 'rm -rf /var/www/repo-root && mkdir -p /var/www/repo-root'
tar \
-cf - \
Dockerfile \
Dockerfile.coolify-api \
docker-compose.yml \
docker-compose.example.yml \
docker-compose.prod.standalone.yml \
scripts/bird-control-plane-auto-activate.php \
scripts/bird-control-plane-bootstrap-local.sh \
scripts/xlvask-automation-migrate.php \
services/coolify/api/start.sh \
services/php/Dockerfile \
services/php/php-fpm-pool.conf \
| docker compose $compose_files exec -T php1 tar --no-same-owner -C /var/www/repo-root -xf -
composer_install
docker compose $compose_files exec -T php1 sh -lc \
"cd /var/www/html && PLENO_REPO_ROOT_FOR_TESTS=/var/www/repo-root composer test:ci:$suite"
+73 -3
View File
@@ -1,14 +1,32 @@
import process from "node:process";
import path from "node:path";
import { createHash } from "node:crypto";
import { pathToFileURL } from "node:url";
export const DEFAULT_STAGING_BASE_URL = "https://api.truckwash.io:4433";
export const EXPECTED_INSTALL_VERSION = "compose-php-agent-v3";
export const REQUIRED_MANIFEST_ARTIFACTS = [
"agent.php",
"lan-worker.php",
"auto-updater.php",
"docker-compose.gateway.yml",
"Dockerfile.edge-agent",
"Dockerfile.lan-worker",
"Dockerfile.auto-updater",
"gateway-launcher.sh",
"truckwash-edge-gateway-stack.service",
"truckwash-edge-agent.service",
];
export const INSTALLER_SCRIPT_REQUIRED_SNIPPETS = [
"/edge-agent/install-token/status",
"/edge-agent/artifacts/manifest.json",
"report_install_status",
'begin_install_phase "VERIFY_TOKEN"',
'begin_install_phase "VERIFY_ARTIFACTS"',
'begin_install_phase "WAIT_FOR_CLAIM"',
'report_install_status "FAILED"',
"verify_manifest_artifact",
EXPECTED_INSTALL_VERSION,
];
export function normalizeBaseUrl(url) {
@@ -55,13 +73,20 @@ export function buildChecks(baseUrl, installToken) {
name: "Ping",
url: `${normalizedBaseUrl}/ping`,
},
{
name: "Artifact manifest",
url: `${normalizedBaseUrl}/edge-agent/artifacts/manifest.json`,
artifactName: "manifest.json",
},
{
name: "Agent PHP artifact",
url: `${normalizedBaseUrl}/edge-agent/artifacts/agent.php`,
artifactName: "agent.php",
},
{
name: "Service unit artifact",
url: `${normalizedBaseUrl}/edge-agent/artifacts/truckwash-edge-agent.service`,
artifactName: "truckwash-edge-agent.service",
},
{
name: "Installer script",
@@ -70,6 +95,42 @@ export function buildChecks(baseUrl, installToken) {
];
}
export function validateArtifactManifestBody(body) {
const manifest = JSON.parse(String(body || ""));
if (manifest.version !== EXPECTED_INSTALL_VERSION) {
throw new Error(`Artifact manifest version mismatch: expected ${EXPECTED_INSTALL_VERSION}, got ${manifest.version}`);
}
if (!Array.isArray(manifest.artifacts)) {
throw new Error("Artifact manifest is missing artifacts.");
}
const byName = new Map(manifest.artifacts.map((artifact) => [artifact?.name, artifact]));
const missingArtifacts = REQUIRED_MANIFEST_ARTIFACTS.filter((artifactName) => !byName.has(artifactName));
if (missingArtifacts.length) {
throw new Error(`Artifact manifest is missing required artifacts: ${missingArtifacts.join(", ")}`);
}
return manifest;
}
export function validateArtifactBodyAgainstManifest(manifest, artifactName, body) {
const artifact = manifest?.artifacts?.find((entry) => entry?.name === artifactName);
if (!artifact) {
throw new Error(`Artifact ${artifactName} is missing from manifest.`);
}
const buffer = Buffer.isBuffer(body) ? body : Buffer.from(String(body || ""));
const sha256 = createHash("sha256").update(buffer).digest("hex");
if (sha256 !== artifact.sha256) {
throw new Error(`Artifact ${artifactName} hash mismatch: ${sha256} !== ${artifact.sha256}`);
}
if (buffer.length !== artifact.bytes) {
throw new Error(`Artifact ${artifactName} size mismatch: ${buffer.length} !== ${artifact.bytes}`);
}
return artifact;
}
export function validateInstallerScriptBody(body) {
const source = String(body || "");
const missingSnippets = INSTALLER_SCRIPT_REQUIRED_SNIPPETS.filter((snippet) => !source.includes(snippet));
@@ -107,16 +168,17 @@ export async function runSmoke({ baseUrl, installToken }) {
const checks = buildChecks(baseUrl, installToken);
const results = [];
let artifactManifest = null;
for (const check of checks) {
process.stdout.write(`[staging-smoke] GET ${check.url}\n`);
const response = await fetch(check.url);
const body = await response.text();
const body = Buffer.from(await response.arrayBuffer());
const result = {
...check,
status: response.status,
ok: response.ok,
bodyPreview: previewBody(body),
bodyPreview: previewBody(body.toString("utf8")),
};
results.push(result);
@@ -127,8 +189,16 @@ export async function runSmoke({ baseUrl, installToken }) {
);
}
if (check.name === "Artifact manifest") {
artifactManifest = validateArtifactManifestBody(body.toString("utf8"));
result.version = artifactManifest.version;
result.artifactCount = artifactManifest.artifacts.length;
}
if (artifactManifest && check.artifactName && check.artifactName !== "manifest.json") {
result.verifiedArtifact = validateArtifactBodyAgainstManifest(artifactManifest, check.artifactName, body);
}
if (check.name === "Installer script") {
result.verifiedSnippets = validateInstallerScriptBody(body);
result.verifiedSnippets = validateInstallerScriptBody(body.toString("utf8"));
}
}
@@ -3,10 +3,13 @@ import assert from "node:assert/strict";
import {
DEFAULT_STAGING_BASE_URL,
EXPECTED_INSTALL_VERSION,
INSTALLER_SCRIPT_REQUIRED_SNIPPETS,
buildChecks,
normalizeBaseUrl,
parseArgs,
validateArtifactBodyAgainstManifest,
validateArtifactManifestBody,
validateInstallerScriptBody,
} from "./staging-edge-gateway-smoke.mjs";
@@ -32,18 +35,58 @@ test("buildChecks targets the public staging endpoints", () => {
assert.deepEqual(checks.map((check) => check.url), [
"https://api.truckwash.io:4433/ping",
"https://api.truckwash.io:4433/edge-agent/artifacts/manifest.json",
"https://api.truckwash.io:4433/edge-agent/artifacts/agent.php",
"https://api.truckwash.io:4433/edge-agent/artifacts/truckwash-edge-agent.service",
"https://api.truckwash.io:4433/edge-agent/install.sh?token=abc%20123",
]);
});
test("validateArtifactManifestBody requires v3 install artifacts", () => {
const artifacts = [
"agent.php",
"lan-worker.php",
"auto-updater.php",
"docker-compose.gateway.yml",
"Dockerfile.edge-agent",
"Dockerfile.lan-worker",
"Dockerfile.auto-updater",
"gateway-launcher.sh",
"truckwash-edge-gateway-stack.service",
"truckwash-edge-agent.service",
].map((name) => ({ name, sha256: "abc", bytes: 1 }));
const manifest = validateArtifactManifestBody(JSON.stringify({
version: EXPECTED_INSTALL_VERSION,
artifacts,
}));
assert.equal(manifest.version, EXPECTED_INSTALL_VERSION);
});
test("validateArtifactBodyAgainstManifest verifies size and hash", () => {
const body = Buffer.from("hello");
const manifest = {
artifacts: [{
name: "agent.php",
sha256: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
bytes: body.length,
}],
};
assert.equal(validateArtifactBodyAgainstManifest(manifest, "agent.php", body).name, "agent.php");
});
test("validateInstallerScriptBody requires install-session reporting wiring", () => {
const script = `
INSTALL_STATUS_URL="https://api.truckwash.io:4433/edge-agent/install-token/status"
fetch_http "Download artifact manifest" "https://api.truckwash.io:4433/edge-agent/artifacts/manifest.json"
report_install_status "FAILED"
begin_install_phase "VERIFY_TOKEN" "Verifying install token"
begin_install_phase "VERIFY_ARTIFACTS" "Verifying edge gateway artifacts"
begin_install_phase "WAIT_FOR_CLAIM" "Waiting for gateway heartbeat and claim"
verify_manifest_artifact
${EXPECTED_INSTALL_VERSION}
`;
assert.deepEqual(validateInstallerScriptBody(script), INSTALLER_SCRIPT_REQUIRED_SNIPPETS);
+66 -26
View File
@@ -12,7 +12,7 @@ export const DEFAULT_IMAGE_TAG = "truckwash-edge-agent:test-gateway";
export const DEFAULT_CONFIG_FILE_NAME = "test-gateway.json";
export const DEFAULT_HOST_API_URL = "http://localhost/api";
export const DEFAULT_CONTAINER_API_URL = "http://caddy";
export const DEFAULT_CONTAINER_BROKER_URL = "http://edge-broker:4300";
export const DEFAULT_CONTAINER_BROKER_URL = "ws://edge-broker:4300";
export const DEFAULT_INSTALL_DIR = "/opt/truckwash-edge-agent";
export const DEFAULT_RUNTIME_DIR = `${DEFAULT_INSTALL_DIR}/runtime`;
export const DEFAULT_STATE_DATABASE_PATH = `${DEFAULT_RUNTIME_DIR}/gateway-state.sqlite`;
@@ -25,6 +25,16 @@ function composeArgs(projectName, args) {
return ["compose", "-p", projectName, ...args];
}
function usesWindowsPathSyntax(filePath) {
return /^[A-Za-z]:($|[\\/])/.test(filePath) || filePath.startsWith("\\\\") || filePath.includes("\\");
}
function pathForInputs(...filePaths) {
const hasWindowsPath = filePaths.some((filePath) => usesWindowsPathSyntax(String(filePath || "")));
return hasWindowsPath ? path.win32 : path;
}
async function resolveRootDir(scriptPath) {
const cwd = process.cwd();
@@ -56,6 +66,7 @@ Options:
--tail <lines> Log lines for the logs action. Default: 200
--skip-compose-up Do not start the local Docker Compose stack before start.
--skip-build Do not rebuild the test gateway image before start.
--copy-config Copy generated config into the container instead of bind mounting it.
`);
}
@@ -65,7 +76,7 @@ export function resolveComposeProjectName(rootDir, env = process.env) {
return explicit;
}
return path.basename(rootDir);
return pathForInputs(rootDir).basename(rootDir);
}
export function resolveComposeNetworkName(rootDir, env = process.env) {
@@ -73,11 +84,13 @@ export function resolveComposeNetworkName(rootDir, env = process.env) {
}
export function resolveConfigDirectory(rootDir, explicitDir = null) {
const pathModule = pathForInputs(rootDir, explicitDir);
if (explicitDir) {
return path.resolve(rootDir, explicitDir);
return pathModule.resolve(rootDir, explicitDir);
}
return path.join(rootDir, ".tmp", "test-gateway");
return pathModule.join(rootDir, ".tmp", "test-gateway");
}
export function shouldClaimGateway(existingConfig = {}, installToken = "") {
@@ -150,6 +163,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
tail: "200",
skipComposeUp: false,
skipBuild: false,
copyConfig: false,
};
for (let index = 0; index < rest.length; index += 1) {
@@ -204,6 +218,9 @@ export function parseArgs(argv = process.argv.slice(2)) {
case "--skip-build":
options.skipBuild = true;
break;
case "--copy-config":
options.copyConfig = true;
break;
case "--help":
case "-h":
options.help = true;
@@ -361,35 +378,57 @@ async function startContainer({
containerName,
hostname,
configDir,
copyConfig,
}) {
const networkName = resolveComposeNetworkName(rootDir);
const mountedConfigDir = toDockerMountPath(configDir);
const containerConfigPath = copyConfig
? `/tmp/${DEFAULT_CONFIG_FILE_NAME}`
: `/config/${DEFAULT_CONFIG_FILE_NAME}`;
const createArgs = [
copyConfig ? "create" : "run",
...(copyConfig ? [] : ["-d"]),
"--name",
containerName,
"--hostname",
hostname,
"--restart",
"unless-stopped",
"--network",
networkName,
...(copyConfig ? [] : ["-v", `${mountedConfigDir}:/config`]),
imageTag,
"--config",
containerConfigPath,
];
await removeContainer(containerName);
await runCommand(
"docker",
[
"run",
"-d",
"--name",
containerName,
"--hostname",
hostname,
"--restart",
"unless-stopped",
"--network",
networkName,
"-v",
`${mountedConfigDir}:/config`,
imageTag,
"--config",
`/config/${DEFAULT_CONFIG_FILE_NAME}`,
],
{
await runCommand("docker", createArgs, {
cwd: rootDir,
stdio: "inherit",
});
if (copyConfig) {
const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
await fs.chmod(configFilePath, 0o666).catch(() => {});
try {
await runCommand("docker", [
"cp",
configFilePath,
`${containerName}:${containerConfigPath}`,
], {
cwd: rootDir,
stdio: "inherit",
});
} finally {
await fs.chmod(configFilePath, 0o600).catch(() => {});
}
await runCommand("docker", ["start", containerName], {
cwd: rootDir,
stdio: "inherit",
}
);
});
}
}
async function printStatus({ imageTag, configDir, containerName }) {
@@ -469,6 +508,7 @@ async function main() {
containerName: options.containerName,
hostname: options.hostname,
configDir,
copyConfig: options.copyConfig,
});
process.stdout.write(`Test gateway container started.
+6
View File
@@ -52,6 +52,12 @@ test("parseArgs accepts help without an explicit action", () => {
assert.equal(options.help, true);
});
test("parseArgs accepts copy config mode", () => {
const options = parseArgs(["start", "--copy-config"]);
assert.equal(options.copyConfig, true);
});
test("buildGatewayConfig applies defaults for a dockerized gateway", () => {
const config = buildGatewayConfig({});
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env php
<?php
/**
* XL Vask automation schema migration script.
*
* Mirrors the scripts/account-deletion-schema.php and
* scripts/bird-control-plane-schema.php patterns so ops can run an explicit,
* non-cron, non-HTTP migration from the API container.
*
* Usage (from the api repo root, against the configured DB):
* php scripts/xlvask-automation-migrate.php check
* php scripts/xlvask-automation-migrate.php apply --yes
*
* "check" never mutates state and always exits 0 when ready / 1 when not.
* "apply" requires an explicit --yes flag before calling the gated
* migration_20260804_xlvask_ai_auto_policy_v2::apply() entry point, which
* itself is operator-only by design (see AUTOMATION_RUNBOOK §2).
*/
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "This command is CLI-only.\n");
exit(2);
}
const WD = __DIR__ . '/../services/nginx/app';
require_once WD . '/vendor/autoload.php';
require_once WD . '/config.php';
require_once WD . '/classes/db.php';
require_once WD . '/classes/xlvask_usage_logs_schema_bootstrap.php';
require_once WD . '/modules/xlvask/migrations/20260804_xlvask_ai_auto_policy_v2.php';
$command = $argv[1] ?? 'check';
if (!in_array($command, ['check', 'apply'], true)) {
fwrite(STDERR, "Usage: scripts/xlvask-automation-migrate.php check|apply --yes\n");
exit(2);
}
$db = new \classes\db($CONFIG_DB);
$db->connect();
if ($command === 'apply') {
if (($argv[2] ?? '') !== '--yes') {
fwrite(STDERR, "Refusing schema mutation without: apply --yes\n");
exit(2);
}
$status = \classes\xlvask_usage_logs_schema_bootstrap::applyExplicitMigration();
} else {
$status = \classes\xlvask_usage_logs_schema_bootstrap::migrationStatus();
}
fwrite(STDOUT, json_encode($status, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL);
exit((bool)($status['ready'] ?? false) ? 0 : 1);
+5
View File
@@ -10,6 +10,11 @@
# CORS is handled at the edge by Traefik's headers middleware.
# Do not set or strip Access-Control-* headers here to avoid conflicts.
# Do not expose local replication bootstrap material from the public web root.
# Bootstrap snapshots contain sensitive failover credentials.
@replicationBootstrap path /storage/replication-bootstrap.json /storage/replication-bootstrap-*
respond @replicationBootstrap 404
# PHP handling via FastCGI to php-fpm pool
php_fastcgi php1:9000 php2:9000 php3:9000 php4:9000 php5:9000
+5
View File
@@ -10,6 +10,11 @@
# CORS is handled at the edge by Traefik's headers middleware.
# Do not set or strip Access-Control-* headers here to avoid conflicts.
# Do not expose local replication bootstrap material from the public web root.
# Bootstrap snapshots contain sensitive failover credentials.
@replicationBootstrap path /storage/replication-bootstrap.json /storage/replication-bootstrap-*
respond @replicationBootstrap 404
# PHP handling via FastCGI to php-fpm pool
php_fastcgi php-staging:9000
-578
View File
@@ -1,578 +0,0 @@
{"level":"info","ts":1773826166.9708004,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.336730715,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826169.3245327,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.125025382,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826169.3245995,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.260210293,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826169.5794039,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.500289085,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826170.0778017,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.39669069,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826171.5490258,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.069669237,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826171.9627604,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.497760546,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826173.9760478,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.070845887,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826174.2890058,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.384107891,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826174.4404097,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.39908183,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826174.8434205,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Cache-Control":["no-cache"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.323563505,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826176.69389,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.385544905,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826176.7040854,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.395716047,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826179.9976525,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.252061272,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826179.9976532,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.252032823,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826180.2976575,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.416949968,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826180.702024,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.355183735,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826182.3615072,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.213493623,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826182.3619726,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.214062164,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826184.9015193,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.309120507,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826185.0053287,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Pragma":["no-cache"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.412929425,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826185.1834722,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.458085374,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826185.596906,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.404400262,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826187.2444446,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.245449894,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826187.2444932,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.245510879,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826189.7901962,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.351403066,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826189.845259,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.406399188,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826190.0125787,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.436858971,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826190.1053317,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.061400822,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826191.9140744,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.067352992,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826191.9140744,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Gpc":["1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.067144351,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"]}}
{"level":"info","ts":1773826194.5529275,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.27129644,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826194.5538201,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.272200234,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826194.8282824,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.410512911,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826195.345705,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.450611072,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826196.9063478,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.223679165,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826196.907733,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.225121309,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826199.390079,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.266549333,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826199.54275,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.419228455,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826199.7448642,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.486476363,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826200.1450813,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.406705347,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826200.902575,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Access-Control-Request-Method":["GET"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["*/*"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.004933848,"size":0,"status":200,"resp_headers":{"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"]}}
{"level":"info","ts":1773826200.9184515,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Access-Control-Request-Method":["GET"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.003871461,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826201.313931,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Authorization":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.408608832,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826201.314516,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.417756253,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826201.6138952,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Authorization":[],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.297119022,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826201.614594,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.298338892,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826201.6740956,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.056775832,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826201.8352685,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.309177004,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826201.835635,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.309922332,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826202.1111634,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.434629705,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826204.2307596,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.270490136,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826204.231079,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.270777164,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826204.9306307,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.834871676,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826205.0144818,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.437812131,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826206.6582522,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.296923602,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826206.7820892,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.421257269,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826209.07706,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.28168601,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826209.0780072,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.282730259,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826209.2607417,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.329227333,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826210.719875,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.336777872,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826212.4557168,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.288418347,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826212.6393747,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.457420958,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826214.6817403,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.060898725,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826214.9970322,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.376193278,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826215.119907,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.37697543,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826215.5844464,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.358253193,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826217.364804,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.336250288,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826217.3651576,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.353011792,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826219.6290405,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.168509855,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826219.6290424,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Cache-Control":["no-cache"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.168515835,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826220.0472326,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.451005017,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826220.5235884,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.459100139,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826221.9169457,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.056181782,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826221.917784,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.057027708,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826222.9502344,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.668844483,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826223.49054,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.537614159,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826223.9723587,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.479864308,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826224.343807,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.368627458,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826224.856207,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.51019735,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826225.3152466,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.212788185,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826225.3152463,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.213877297,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826225.34763,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.489283626,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826225.3848963,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.280640345,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826225.3853478,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.284002289,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"]}}
{"level":"info","ts":1773826225.8986015,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.548822248,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826226.4152071,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.514747638,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826227.3371081,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.300847773,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826227.7347972,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.698537342,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826229.322464,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.180099137,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826229.3229284,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.182328794,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826229.5229506,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.547793197,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826229.7905357,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.515788574,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826230.0126746,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.487792958,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826230.015993,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.066576546,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826230.3144436,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.486501483,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826230.409719,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.09307799,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826231.4518874,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Access-Control-Request-Headers":["authorization"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["*/*"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"Access-Control-Request-Method":["GET"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.007941754,"size":0,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Server":["Caddy"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826231.9584615,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.504336719,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826232.00635,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.11080176,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826232.393898,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.498349724,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826233.7580044,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.31107267,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826234.0281086,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.268209641,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826234.856958,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.064741677,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826235.0811856,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.410918923,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826235.0965688,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.305344393,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826235.096562,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.305286142,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826235.758473,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.96652785,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826236.2328858,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":1.149166396,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826236.734551,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Access-Control-Request-Headers":["authorization"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Method":["GET"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.004409892,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826236.8764596,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Fetch-Mode":["cors"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.139966551,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826236.8765473,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.146188956,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826237.1359358,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.405598041,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826238.3543074,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.069293506,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826238.6079137,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.078009457,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826239.8719676,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.23115293,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826239.936697,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.297177805,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826239.93727,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.297747795,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826240.177016,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.301852145,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826240.2603126,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.619578163,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826240.2948732,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.115473072,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826242.5489316,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["*/*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"]}},"bytes_read":0,"user_id":"","duration":0.004359668,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826242.840928,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.294881878,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826242.8434336,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.29885309,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826243.2189693,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Authorization":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.667883652,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826244.6206405,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.518181713,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826244.7226462,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.099885191,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826245.7282386,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.286842731,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826245.7282357,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.286843033,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826245.729071,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.40778575,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826245.8759341,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.433985598,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"]}}
{"level":"info","ts":1773826246.0560663,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.325072718,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826246.150864,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.709473378,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826247.3969293,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Access-Control-Request-Method":["GET"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.004395296,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826247.4287875,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Cache-Control":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.047798163,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826247.4294498,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.048426199,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826247.4584892,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Authorization":[],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.059579264,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826249.377847,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.434692156,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826249.9012983,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.520719503,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826250.3366525,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.039950465,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826250.3779438,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.21601427,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826250.3779619,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.081639673,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826250.423558,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.043639415,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826250.5279162,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.231591889,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826250.5284529,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.231734683,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826252.2262323,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["*/*"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.002952425,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826252.4899101,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.266604287,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826252.4902215,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.266939348,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826252.6353197,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.406947854,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826254.0988395,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.309949283,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826254.4142804,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.313229674,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826255.1939533,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.052917017,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826255.1943216,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.053291772,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826255.4786673,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.474724907,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826255.5260544,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.384703087,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826255.526563,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.385158447,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826256.113922,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.632900798,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826257.084515,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["*/*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Origin":["http://localhost:5173"],"Access-Control-Request-Headers":["authorization"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.003835498,"size":0,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826257.4487476,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.362283977,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826257.4492424,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"Sec-Fetch-Dest":["empty"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.368815775,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826257.6348832,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.55442496,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826259.0092547,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.376877321,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826259.6307788,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.619805015,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826260.145301,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.161590542,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826260.2827036,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.30025666,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826260.3579996,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.374290372,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826260.358565,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.375673265,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826260.3591213,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.495672175,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826260.9677532,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.005428654,"size":0,"status":200,"resp_headers":{"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"]}}
{"level":"info","ts":1773826261.0812206,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.719474137,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826261.131758,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.152860403,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826261.9229548,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Access-Control-Request-Headers":["authorization"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Method":["GET"],"Referer":["http://localhost:5173/"],"Accept":["*/*"],"Origin":["http://localhost:5173"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.004796388,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826262.1808798,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.262721205,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826262.3055892,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.387411559,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826262.3256145,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.399990235,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826264.0834198,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.605018867,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826264.4656212,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.379761165,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826265.159418,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Mode":["cors"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.337052132,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826265.1599183,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.338656721,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826265.159959,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.338665488,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826265.1599865,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.457723334,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826265.1608107,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.338186345,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826265.7457488,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.58234562,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826266.760665,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.003348445,"size":0,"status":200,"resp_headers":{"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"]}}
{"level":"info","ts":1773826267.099361,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.341886103,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826267.3528302,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.589548578,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826267.3544612,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.597138218,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826268.7962244,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.471960855,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826269.3874242,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.587826508,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826269.8864343,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.340632699,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826270.0011191,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.335564386,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826270.001632,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.336146783,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826270.1797457,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.289916136,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826270.244424,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.578840314,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826270.4123607,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.746906789,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826272.5798728,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Access-Control-Request-Method":["GET"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["*/*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.004585942,"size":0,"status":200,"resp_headers":{"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"]}}
{"level":"info","ts":1773826272.9760356,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.393565183,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826272.9766512,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.401031403,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"]}}
{"level":"info","ts":1773826273.757031,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":1.18182985,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826274.6127648,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.479104184,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826275.1434913,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.528749182,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826275.6235788,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.137716661,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826275.6238408,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.259205249,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826275.7685578,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.142324079,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826275.8653605,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.379437951,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826275.8658516,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.379986088,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826275.8674288,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.381435854,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826277.4191809,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["*/*"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.003160298,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Server":["Caddy"],"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826277.513314,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.083039333,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826277.513765,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.083276757,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826277.839947,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Authorization":[],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.418394618,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826279.311053,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.32588745,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826279.553671,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.240337937,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826280.4236414,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.219593491,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826280.4239388,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.097175698,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826280.609539,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.282907568,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826280.6095476,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.282945769,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826280.6837862,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.357176393,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826280.9124198,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.486043498,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826282.2816343,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Referer":["http://localhost:5173/"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.004272434,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826282.5014513,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.224096667,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826282.5932522,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"Authorization":[],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.309016132,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826282.762372,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.485008109,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826284.2473116,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.419944897,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826284.7155588,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.465644762,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826285.3731148,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.33201332,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826285.382416,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.204788999,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826285.3824492,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.204646815,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826285.527407,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.349765478,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826285.5277753,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.350248889,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826286.06445,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.688880394,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826287.113801,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.004059447,"size":0,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"]}}
{"level":"info","ts":1773826287.419412,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.309658213,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826287.4198174,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.31017131,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826287.5938196,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Authorization":[],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.6"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.477313442,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826289.0298648,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.360409759,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826289.3319452,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.299687827,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826290.3376222,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.452177689,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826290.6402397,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.300352632,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826291.9410431,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.003132931,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826292.3842604,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Authorization":[],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.440860827,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826293.9343493,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.439952281,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826294.3440604,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.407360227,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826295.1711533,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.449134138,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826295.8527117,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.679430866,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826298.575222,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.239223342,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826299.1671898,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.589304101,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826299.9768214,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.42280195,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826300.3889349,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.409451117,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826304.4682908,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.297302011,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826304.6760879,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.205166353,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826305.7035754,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.309903469,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826306.06365,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.35758992,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826309.426586,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.411602016,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826309.7365882,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.307559619,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826310.5728822,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.340467364,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826311.1087942,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.53365395,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826314.3029535,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.451387991,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826314.7390003,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.433637015,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826315.145722,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.071645191,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826315.2184045,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.07060575,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826318.9327765,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.223040782,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826319.372069,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.435527131,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826320.2377045,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.310641846,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826320.6059396,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.366049107,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826321.019666,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["*/*"],"Access-Control-Request-Method":["GET"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.005125441,"size":0,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826321.0330973,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.004094559,"size":0,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826321.065755,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.050754619,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826321.0663266,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43688","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.049707903,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826321.0714931,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.058299498,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826321.3352807,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.31271021,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826321.3353183,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.320461487,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826321.33543,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.322217999,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826321.336045,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.319932569,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826321.454813,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.115766323,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826324.1107216,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.563325744,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826324.51782,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.404870334,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826325.0767791,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.313798868,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826325.516587,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.43789269,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826328.5783246,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.192510186,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826328.9600253,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.33591841,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826329.9461956,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.343614408,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826330.0325968,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.08370118,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826333.4395497,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.203711159,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826334.8488133,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.407913962,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826336.189148,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.775229936,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826336.5129259,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.321547405,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826339.8193758,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.778237382,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826340.484483,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.661839092,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826341.052277,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.565531578,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826341.1713974,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.116995635,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826344.3424993,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.459823803,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826344.9462178,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.600610796,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826345.400256,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.298155702,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826346.0480387,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.645135856,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826349.156766,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.41996003,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826349.5705867,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.410768907,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826350.1640942,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.209420494,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826350.5521028,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.385858993,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"]}}
{"level":"info","ts":1773826354.1082847,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.532853099,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826354.7754962,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.664431534,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826355.290558,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.498205064,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826355.7959008,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.50333574,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826359.4631894,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":1.046471635,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826359.8179033,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.352511432,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826360.3723376,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.552128883,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826361.6249712,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.250746166,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826364.3044171,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":1.036105659,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826365.7734706,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.500940415,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826366.3151329,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.539422058,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826366.9041758,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.585534079,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826369.6179724,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.552731569,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826370.0349576,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.414776487,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826370.3518865,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.066909465,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826370.719497,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.365441292,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826374.2375813,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.322032518,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826374.37445,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.134229129,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"]}}
{"level":"info","ts":1773826375.2585166,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.120342752,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826375.7874947,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.526105892,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826379.1529784,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.391462836,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826379.737555,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.582494402,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826380.8159359,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.838749844,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826380.9110472,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.0926876,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826381.0814805,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Access-Control-Request-Method":["GET"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.003464438,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826381.2150488,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43688","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.135002073,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826381.353293,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.275410157,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826381.4263964,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.347429674,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826381.4263968,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Pragma":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.347228296,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826381.4269779,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.347126352,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826381.4264684,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.346251891,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826381.4288092,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["*/*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.33539058,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["*"],"Server":["Caddy"],"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826381.725631,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.640679594,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826381.8789194,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Authorization":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.150518915,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826384.2855997,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.68677901,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826384.419676,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.131463831,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826385.296497,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.482235259,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826385.9763112,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.677502726,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826389.0283816,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.585028881,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826389.7241693,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.693332769,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826389.9961612,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.270062312,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826390.0560722,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Origin":["http://localhost:5173"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.058272897,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826394.1860425,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.905101807,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826394.8389401,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.650678782,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826396.8760483,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":1.062733619,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826397.4128597,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.533872213,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826399.3711278,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Gpc":["1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.275338328,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826399.8407238,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.467445268,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826400.8211105,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Sec-Gpc":["1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.503336673,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826401.3927042,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.568617153,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826404.9276648,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.981689797,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826405.3865352,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.45668309,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826405.7212017,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.332704571,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"]}}
{"level":"info","ts":1773826406.24432,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.520752258,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826409.4144459,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.619358427,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826409.8032606,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.386445718,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826410.3962119,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.382138844,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826410.8523376,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.453331614,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826413.9524436,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.317999654,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826414.2163806,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.261299034,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826415.6521916,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.804872582,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826416.2200892,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.56558394,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826418.6992216,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.230586771,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826419.2315469,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.528964389,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826420.1994016,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.516745862,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826420.5655065,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Sec-Fetch-Dest":["empty"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.364192672,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826423.6324012,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.336594454,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826424.1642637,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.529683486,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826424.9427843,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.417239584,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826425.25549,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.310120034,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826429.4322617,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.294377725,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826429.6303096,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.195912736,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826430.5558884,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.20020931,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826430.6338358,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.075953449,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826434.7283158,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.749575954,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826435.4002984,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.669408179,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826436.0852852,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.681158799,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826436.6932082,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.605147457,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826439.2860832,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.454779172,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826439.8214653,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.533406742,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826440.4660094,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.417585991,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826440.8675385,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.398997562,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826441.1522396,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.005090754,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"]}}
{"level":"info","ts":1773826441.1626284,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43688","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["*/*"],"Access-Control-Request-Headers":["authorization"],"Access-Control-Request-Method":["GET"],"Referer":["http://localhost:5173/"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.002807514,"size":0,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826441.3849103,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.236757779,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826441.3854418,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.236391524,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826441.473212,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.326754444,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826441.4736257,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.325698639,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826441.473778,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.32471909,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826441.4738522,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.325672989,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826441.6903734,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Authorization":[],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.53561241,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826442.0301497,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.337053066,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826443.734806,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.057697718,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826444.2450626,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.328282862,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826445.1334405,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.237094723,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826445.4486778,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.312441438,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826448.7812324,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.276600283,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826449.2926083,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.509287977,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826450.3206308,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.58550417,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826450.4873588,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.163633449,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826453.6153731,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.261944392,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826453.958753,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.341302764,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826454.935746,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.358773571,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826455.1492126,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.21140475,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826459.3996916,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.239497817,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826460.007519,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.605473485,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826460.4367466,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.054840982,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826460.6668808,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.227959031,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826464.721618,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.516813632,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"error","ts":1773826464.7636774,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/departments","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.558936596,"size":111,"status":401,"resp_headers":{"Server":["Caddy"],"Status":["401 Unauthorized"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826464.8303838,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.606549241,"size":735,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy"]}}
{"level":"info","ts":1773826469.3836143,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.334187709,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826474.1502144,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.245024222,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826479.0968618,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.355127487,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826484.1756196,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.582159192,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826490.199057,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.804270926,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826493.4475157,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/departments","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.367623667,"size":111,"status":401,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["401 Unauthorized"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826493.652717,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Server":["0b64317da32a"]}},"bytes_read":0,"user_id":"","duration":0.572779984,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826495.118352,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":2.018611954,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1773826499.0833688,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":1.143638682,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826501.2080162,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Headers":["authorization"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Access-Control-Request-Method":["GET"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.005821988,"size":0,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"]}}
{"level":"info","ts":1773826501.2162066,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43688","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"OPTIONS","host":"localhost","uri":"/ping","headers":{"Accept":["*/*"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Access-Control-Request-Method":["GET"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"Access-Control-Request-Headers":["authorization"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.002818669,"size":0,"status":200,"resp_headers":{"Content-Type":["application/json"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Access-Control-Allow-Headers":["*"],"Server":["Caddy"]}}
{"level":"info","ts":1773826501.3781483,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"55612","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.174063301,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826501.3781526,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.176308399,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826501.378165,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38484","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.174260755,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826501.5183465,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"41354","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.313467045,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826501.5187743,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"38470","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.314577812,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826501.6968036,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Authorization":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.486608324,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826502.2942631,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"43684","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Origin":["http://localhost:5173"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.090459252,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826502.6856973,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Authorization":[],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"]}},"bytes_read":0,"user_id":"","duration":0.986786982,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826503.1070702,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["0b64317da32a"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.329633423,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826507.9452004,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.330087892,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826512.99931,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Server":["0b64317da32a"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.545302906,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826518.1475375,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["0b64317da32a"],"X-Forwarded-Host":["localhost"],"Cache-Control":["no-cache"]}},"bytes_read":0,"user_id":"","duration":0.843202642,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"error","ts":1773826523.1225665,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"53894","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["0b64317da32a"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.007480931,"size":0,"status":502,"resp_headers":{"Server":["Caddy"]}}
{"level":"info","ts":1773826533.9554763,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Cache-Control":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":1.158905036,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826536.9338899,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":2.965340628,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1773826538.547151,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.910843089,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826543.0969942,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.629550116,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826547.7162032,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.395867947,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826554.0806224,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.934719548,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826558.3746915,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.379371845,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826561.631695,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"Cache-Control":["no-cache"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.377331413,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826561.631695,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.371442491,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826561.7586732,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.500663238,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826561.9058926,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.644824932,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826561.9058967,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.64280669,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826561.9065697,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.648565892,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826562.008113,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.744631138,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826562.745923,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Authorization":[],"Referer":["http://localhost:5173/"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.836964667,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826563.3830287,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.539581044,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826568.1970885,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.52872221,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826572.8485315,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.329632635,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826577.5866983,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.246261041,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826583.649508,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.472560865,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826588.5287206,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.50213333,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826593.668152,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.794128836,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826598.0569248,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"]}},"bytes_read":0,"user_id":"","duration":0.3455446,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"]}}
{"level":"info","ts":1773826602.9588246,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.390555761,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826607.8736682,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.477587811,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826613.8135455,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.595724596,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826618.519822,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.469573999,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826621.5868504,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Cache-Control":["no-cache"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.269667338,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826621.5905738,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.273376391,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826621.7035396,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.386524533,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826621.703969,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.388962343,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826621.7040298,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.386885628,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826621.704212,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.388677708,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826621.7050414,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.387877353,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826622.5236545,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.816353161,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826623.0119357,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.108896811,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826628.1503737,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.398736135,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826633.0426946,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.458874081,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826637.766165,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.344250942,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826643.0986114,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.839096138,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826647.9172235,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Fetch-Site":["same-site"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.293915662,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826648.011681,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/departments","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.388416664,"size":111,"status":401,"resp_headers":{"Server":["Caddy"],"Status":["401 Unauthorized"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826649.156614,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":1.486069357,"size":735,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826652.7637017,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.287533254,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1773826657.6192331,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""]}},"bytes_read":0,"user_id":"","duration":0.304165869,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826662.484209,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.332249171,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826667.3339016,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.335003003,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826672.4496367,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"]}},"bytes_read":0,"user_id":"","duration":0.615472516,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"info","ts":1773826677.9462786,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.284755105,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826681.6040988,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.215791054,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"]}}
{"level":"info","ts":1773826681.604095,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.219227531,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826681.6041782,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"]}},"bytes_read":0,"user_id":"","duration":0.220043092,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826681.7778602,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.391469649,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826681.7804117,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.391671889,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826681.7854679,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.398119439,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1773826682.2771666,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Authorization":[],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.496209609,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1773826682.2781265,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.893277484,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826683.178707,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.663738661,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"info","ts":1773826688.3381941,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37104","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.989881721,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826688.52026,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/departments","headers":{"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.517618677,"size":111,"status":401,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"Status":["401 Unauthorized"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"info","ts":1773826689.0678031,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["cf3606ea293b"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":1.064311318,"size":101,"status":200,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1773826689.8285036,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":1.804908345,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1773826693.5957835,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.750935948,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826697.3757653,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/departments","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"Origin":["http://localhost:5173"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.293729729,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826697.3757768,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Gpc":["1"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.293211565,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826697.7717874,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/guest/departments?include_lanes=true","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-site"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.666268221,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826701.5105367,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Authorization":[],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.53491044,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"error","ts":1773826701.7883804,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/auth/reCAPTCHA/public","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.816917711,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826701.853436,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"50300","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.339505529,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826701.8538113,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/auth/session","headers":{"Origin":["http://localhost:5173"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.6"],"Referer":["http://localhost:5173/"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.877614488,"size":72,"status":404,"resp_headers":{"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826701.984547,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"35502","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/guest/departments?include_lanes=true","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Cache-Control":["no-cache"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"]}},"bytes_read":0,"user_id":"","duration":0.172874076,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"Server":["Caddy"]}}
{"level":"error","ts":1773826702.0168717,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37054","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/auth/session","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Authorization":[],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.159969922,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826702.242746,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/departments","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"],"X-Forwarded-Port":["80"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Referer":["http://localhost:5173/"],"X-Forwarded-Proto":["http"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"Authorization":[],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":1.295959827,"size":72,"status":404,"resp_headers":{"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"error","ts":1773826702.3069026,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37072","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/departments","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Cache-Control":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.577721038,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826702.350668,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/auth/reCAPTCHA/public","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"]}},"bytes_read":0,"user_id":"","duration":0.555174873,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"error","ts":1773826702.8036997,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/departments","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Origin":["http://localhost:5173"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.557892356,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826702.8073766,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":1.077441848,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826703.4966028,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"]}},"bytes_read":0,"user_id":"","duration":0.684978633,"size":72,"status":404,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"error","ts":1773826708.0270803,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Fetch-Mode":["cors"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Authorization":[],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Origin":["http://localhost:5173"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.486951214,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826708.3696883,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Sec-Fetch-Mode":["cors"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.60697834,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826708.5068195,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Authorization":[],"Origin":["http://localhost:5173"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.476434872,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Server":["Caddy"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826708.6845531,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.17228174,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826713.0960047,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Server":["cf3606ea293b"],"Origin":["http://localhost:5173"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.704865696,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"]}}
{"level":"error","ts":1773826713.2229595,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""]}},"bytes_read":0,"user_id":"","duration":0.607684269,"size":72,"status":404,"resp_headers":{"Status":["404 Not Found"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826713.5119667,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-Port":["80"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Accept-Language":["en-GB,en;q=0.9"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"]}},"bytes_read":0,"user_id":"","duration":0.163409461,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826713.7476597,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Gpc":["1"]}},"bytes_read":0,"user_id":"","duration":0.648740312,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826717.6552675,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["cf3606ea293b"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":0.195128913,"size":72,"status":404,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"error","ts":1773826717.6557715,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Authorization":[],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.429146662,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826718.6928694,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Pragma":["no-cache"],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Cache-Control":["no-cache"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.496269746,"size":72,"status":404,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"error","ts":1773826718.6936173,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Fetch-Site":["same-site"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":1.035254318,"size":72,"status":404,"resp_headers":{"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"error","ts":1773826722.7025964,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Fetch-Site":["same-site"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.6"],"X-Forwarded-Host":["localhost"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Authorization":[],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.621774556,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"]}}
{"level":"error","ts":1773826723.3734825,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.338516843,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826723.3745852,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Authorization":[],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.667566008,"size":72,"status":404,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"error","ts":1773826723.4913466,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept":["application/json, text/plain, */*"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Gpc":["1"],"X-Forwarded-Proto":["http"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":1.187267605,"size":72,"status":404,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"]}}
{"level":"error","ts":1773826727.4152093,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Gpc":["1"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Authorization":[],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"]}},"bytes_read":0,"user_id":"","duration":0.497147953,"size":72,"status":404,"resp_headers":{"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"error","ts":1773826727.8890858,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37084","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.747295957,"size":72,"status":404,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826728.1730373,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Server":["cf3606ea293b"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Authorization":[],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Port":["80"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":0.754468923,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826728.6366873,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"X-Forwarded-For":["172.18.0.1"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Accept-Language":["en-GB,en;q=0.9"],"Cache-Control":["no-cache"],"Origin":["http://localhost:5173"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Pragma":["no-cache"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.746952708,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"error","ts":1773826732.221285,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Server":["cf3606ea293b"],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Authorization":[],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-site"],"X-Real-Ip":["172.18.0.1"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Language":["en-GB,en;q=0.6"],"Origin":["http://localhost:5173"],"Referer":["http://localhost:5173/"]}},"bytes_read":0,"user_id":"","duration":0.456508601,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"]}}
{"level":"error","ts":1773826732.2212849,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept-Language":["en-GB,en;q=0.9"],"Sec-Gpc":["1"],"X-Forwarded-For":["172.18.0.1"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Origin":["http://localhost:5173"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Referer":["http://localhost:5173/"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Mode":["cors"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"]}},"bytes_read":0,"user_id":"","duration":0.234413772,"size":72,"status":404,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"]}}
{"level":"error","ts":1773826732.912765,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37058","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Referer":["http://localhost:5173/"],"Sec-Fetch-Mode":["cors"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["cf3606ea293b"],"X-Real-Ip":["172.18.0.1"],"Authorization":[],"Origin":["http://localhost:5173"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-site"],"Sec-Gpc":["1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"Accept":["application/json, text/plain, */*"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":0.687789355,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"error","ts":1773826733.4827838,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"37092","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/:4433/ping","headers":{"Accept":["application/json, text/plain, */*"],"Origin":["http://localhost:5173"],"Pragma":["no-cache"],"Sec-Ch-Ua":["\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Brave\";v=\"146\""],"Sec-Ch-Ua-Platform":["\"Windows\""],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Site":["same-site"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Cache-Control":["no-cache"],"Referer":["http://localhost:5173/"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["cf3606ea293b"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Gpc":["1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Language":["en-GB,en;q=0.9"]}},"bytes_read":0,"user_id":"","duration":0.752769145,"size":72,"status":404,"resp_headers":{"Access-Control-Allow-Origin":["*"],"Access-Control-Allow-Headers":["Content-Type, Authorization, X-Customer-Number"],"Server":["Caddy"],"Access-Control-Allow-Methods":["GET, POST, PUT, DELETE, OPTIONS"],"Content-Type":["application/json; charset=utf-8"],"Status":["404 Not Found"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1774359410.7662385,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"]}},"bytes_read":0,"user_id":"","duration":1.419261331,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359411.8936794,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-Server":["4513febd0e98"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":1.095823154,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359413.4743714,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"]}},"bytes_read":0,"user_id":"","duration":1.569025749,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1774359414.9427295,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["4513febd0e98"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.453042866,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359416.5113647,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":1.555332673,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1774359417.9860232,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.45695831,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359418.5943637,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.595441308,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1774359419.6813276,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"42848","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":1.075013274,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359428.338712,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.910140374,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359429.22114,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.862201927,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359429.1707215,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":1.389081049,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359429.3823278,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.194319251,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359430.8409362,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":1.437162491,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1774359431.0736253,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"]}},"bytes_read":0,"user_id":"","duration":0.21548384,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359432.5463028,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.457021421,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359433.1541202,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.593640533,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359433.7735548,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.604391296,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359434.523004,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.735703774,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359435.3819067,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"]}},"bytes_read":0,"user_id":"","duration":0.841783131,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1774359436.0587533,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.65987697,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1774359436.7292738,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.654426603,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1774359437.4872267,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"X-Forwarded-Host":["localhost"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.737746322,"size":101,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"]}}
{"level":"info","ts":1774359438.1417146,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":0.638910553,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1774359438.9001129,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"]}},"bytes_read":0,"user_id":"","duration":0.744432539,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"]}}
{"level":"info","ts":1774359439.7426257,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-Server":["4513febd0e98"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":0.820541924,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359440.5554748,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"Accept-Encoding":["gzip"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"]}},"bytes_read":0,"user_id":"","duration":0.798630689,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359441.0909092,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"]}},"bytes_read":0,"user_id":"","duration":0.522440142,"size":101,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359441.87296,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/ping","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":0.768554371,"size":101,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359451.9496348,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":3.16841118,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359454.7461386,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":2.771633599,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359456.8014095,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":2.043545388,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359458.8112676,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.997627811,"size":735,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1774359460.0082002,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Host":["localhost"],"X-Forwarded-Server":["4513febd0e98"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"]}},"bytes_read":0,"user_id":"","duration":2.719624341,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Type":["application/json; charset=utf-8"],"X-Powered-By":["PHP/8.2.15"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359462.1559546,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Server":["4513febd0e98"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"]}},"bytes_read":0,"user_id":"","duration":2.129318162,"size":735,"status":200,"resp_headers":{"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy"]}}
{"level":"info","ts":1774359464.0260193,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["80"],"X-Real-Ip":["172.18.0.1"]}},"bytes_read":0,"user_id":"","duration":1.858119212,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359466.158465,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Real-Ip":["172.18.0.1"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"],"X-Forwarded-Prefix":["/api"],"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"]}},"bytes_read":0,"user_id":"","duration":2.120016812,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359468.6958492,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Proto":["http"],"X-Forwarded-Server":["4513febd0e98"],"Accept-Encoding":["gzip"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Prefix":["/api"],"X-Real-Ip":["172.18.0.1"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Port":["80"]}},"bytes_read":0,"user_id":"","duration":2.523297017,"size":735,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"],"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"]}}
{"level":"info","ts":1774359470.677206,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.6","remote_port":"53830","client_ip":"172.18.0.6","proto":"HTTP/1.1","method":"GET","host":"localhost","uri":"/guest/departments?include_lanes=true","headers":{"X-Forwarded-Port":["80"],"X-Forwarded-Proto":["http"],"X-Real-Ip":["172.18.0.1"],"Accept-Encoding":["gzip"],"X-Forwarded-Server":["4513febd0e98"],"User-Agent":["Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.26100.7920"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Host":["localhost"],"X-Forwarded-Prefix":["/api"]}},"bytes_read":0,"user_id":"","duration":1.968647671,"size":735,"status":200,"resp_headers":{"Content-Encoding":["gzip"],"Vary":["Accept-Encoding"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1775042310.766326,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"49692","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"caddy","uri":"/economic/payment-terms","headers":{"User-Agent":["curl/8.14.1"],"Accept":["*/*"],"Authorization":[]}},"bytes_read":0,"user_id":"","duration":2.173924739,"size":2514,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1775042310.819993,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"49690","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"caddy","uri":"/economic/layouts","headers":{"User-Agent":["curl/8.14.1"],"Accept":["*/*"],"Authorization":[]}},"bytes_read":0,"user_id":"","duration":2.227691681,"size":721,"status":200,"resp_headers":{"Content-Type":["application/json; charset=utf-8"],"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"]}}
{"level":"info","ts":1775042311.5330641,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"49706","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"caddy","uri":"/economic/config","headers":{"User-Agent":["curl/8.14.1"],"Accept":["*/*"],"Authorization":[]}},"bytes_read":0,"user_id":"","duration":2.692967287,"size":604,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.30"],"Content-Type":["application/json; charset=utf-8"]}}
{"level":"info","ts":1775042615.6337473,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"172.18.0.5","remote_port":"54866","client_ip":"172.18.0.5","proto":"HTTP/1.1","method":"GET","host":"caddy","uri":"/customers?page=1&limit=100","headers":{"User-Agent":["curl/8.14.1"],"Accept":["*/*"],"Authorization":[]}},"bytes_read":0,"user_id":"","duration":10.029899933,"size":164529,"status":200,"resp_headers":{"Server":["Caddy"],"X-Powered-By":["PHP/8.2.15"],"Content-Type":["application/json; charset=utf-8"]}}
+42
View File
@@ -0,0 +1,42 @@
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /dev/stdout;
error_log /dev/stderr warn;
sendfile on;
keepalive_timeout 65;
client_max_body_size 64m;
gzip on;
gzip_types application/json application/javascript application/xml text/css text/plain;
server {
listen 80 default_server;
server_name _;
root /var/www/html;
index index.php;
location / {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_read_timeout 60s;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param X_REQUEST_ID $http_x_request_id;
}
location ~ /\.(?!well-known) {
deny all;
}
}
}
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
set -e
mkdir -p /run/nginx
php-fpm -D
exec nginx -g "daemon off;"
+1 -3
View File
@@ -3,9 +3,7 @@ FROM php:8.2-cli
WORKDIR /opt/truckwash-edge-agent
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates; \
rm -rf /var/lib/apt/lists/*
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
COPY services/nginx/app/resources/edge-gateway-agent/ ./
+382 -36
View File
@@ -17,6 +17,8 @@ const DEFAULT_UPDATE_VERIFICATION_TIMEOUT_SECONDS = 45;
const DEFAULT_UPDATE_VERIFY_INTERVAL_MS = 500;
const DEFAULT_UPDATE_RESTART_GRACE_MS = 150;
const DEFAULT_BROKER_RECONNECT_DELAY_MS = 1500;
const DEFAULT_SHELLY_LOCAL_HTTP_TIMEOUT_MS = 1200;
const MAX_RELAY_TOGGLE_AFTER_SECONDS = 5;
const UPDATE_VERIFY_COMMAND = "post-update-verify";
const execFile = promisify(execFileCallback);
@@ -137,16 +139,23 @@ function buildTransportHeartbeatState(brokerState = {}) {
status: "ONLINE",
metadata: {
command_transport: brokerConnected ? "BROKER_FAST_PATH" : "API_POLLING",
shell_transport: brokerConnected ? "BROKER_FAST_PATH" : "API_POLLING",
broker_connected: brokerConnected,
broker_url: brokerState.url || null,
broker_last_error: brokerState.lastError || null,
broker_last_connected_at: brokerState.lastConnectedAt || null,
broker_last_disconnected_at: brokerState.lastDisconnectedAt || null,
broker_disconnect_reason: brokerState.disconnectReason || null,
broker_last_close_code: brokerState.lastCloseCode ?? null,
broker_last_close_clean: brokerState.lastCloseClean ?? null,
},
};
}
function isShellAccessEnabled(config = {}) {
return config.enableShellAccess === true;
}
function normalizeBrokerBaseUrl(value) {
const trimmed = String(value || "").trim().replace(/\/+$/, "");
if (trimmed === "") {
@@ -177,6 +186,30 @@ function buildBrokerSocketUrl(brokerUrl, gatewayId, token) {
return `${baseUrl}/ws/agent?${search.toString()}`;
}
function redactBrokerSocketUrl(value) {
try {
const parsed = new URL(String(value || ""));
for (const key of ["token", "agentToken", "agent_token"]) {
if (parsed.searchParams.has(key)) {
parsed.searchParams.set(key, "***");
}
}
return parsed.toString();
} catch {
return String(value || "").replace(/([?&](?:token|agentToken|agent_token)=)[^&]+/gi, "$1***");
}
}
function normalizeBrokerSocketError(error, socketUrl) {
const message = error instanceof Error && error.message
? error.message
: error?.message
? String(error.message)
: "Broker connection failed";
const redactedUrl = redactBrokerSocketUrl(socketUrl);
return redactedUrl ? `${message} (${redactedUrl})` : message;
}
async function readSocketMessageText(data) {
if (typeof data === "string") {
return data;
@@ -425,8 +458,64 @@ export async function claimIfNeeded(config, configPath, fetchImpl = fetch) {
return nextConfig;
}
async function fetchJson(url, fetchImpl = fetch) {
const response = await fetchImpl(url);
function makeHttpTimeoutError(timeoutMs) {
const error = new Error(`HTTP request timed out after ${timeoutMs}ms`);
error.code = "EDGE_AGENT_HTTP_TIMEOUT";
return error;
}
function isHttpTimeoutError(error) {
return error?.code === "EDGE_AGENT_HTTP_TIMEOUT";
}
function resolveShellyLocalHttpTimeoutMs(options = {}) {
const configured = Number(options.timeoutMs ?? process.env.EDGE_SHELLY_LOCAL_HTTP_TIMEOUT_MS);
if (Number.isFinite(configured) && configured > 0) {
return Math.max(50, Math.floor(configured));
}
return DEFAULT_SHELLY_LOCAL_HTTP_TIMEOUT_MS;
}
function resolveRelayToggleAfterSeconds(payload = {}) {
const configured = Number(payload.toggleAfter ?? payload.toggle_after ?? payload.timer);
if (!Number.isFinite(configured) || configured <= 0) {
return null;
}
return Math.min(Math.floor(configured), MAX_RELAY_TOGGLE_AFTER_SECONDS);
}
async function fetchJson(url, fetchImpl = fetch, options = {}) {
const timeoutMs = Number(options.timeoutMs || 0);
let timeout = null;
let controller = null;
const requestOptions = {};
if (timeoutMs > 0 && typeof AbortController !== "undefined") {
controller = new AbortController();
requestOptions.signal = controller.signal;
}
let response;
try {
const fetchPromise = Promise.resolve().then(() => fetchImpl(url, requestOptions));
response = timeoutMs > 0
? await Promise.race([
fetchPromise,
new Promise((_, reject) => {
timeout = setTimeout(() => {
controller?.abort();
reject(makeHttpTimeoutError(timeoutMs));
}, timeoutMs);
}),
])
: await fetchPromise;
} finally {
if (timeout !== null) {
clearTimeout(timeout);
}
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
@@ -451,13 +540,99 @@ function expandCandidateIps(options = {}) {
return [];
}
function normalizeShellyDeviceGeneration(value) {
if (Number.isInteger(value) && value > 0) {
return value;
}
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
return Math.trunc(value);
}
return inferShellyDeviceGenerationFromString(value);
}
function inferShellyDeviceGenerationFromString(value) {
const normalized = String(value || "").trim();
if (!normalized) {
return null;
}
const explicit = normalized.match(/\bgen(?:eration)?\s*([1-9]\d*)\b/i);
if (explicit) {
return Number(explicit[1]);
}
const upper = normalized.toUpperCase();
const sSeries = upper.match(/\bS([3-9])(?:[A-Z0-9]+)?-[A-Z0-9-]+\b/);
if (sSeries) {
return Number(sSeries[1]);
}
if (/\b(?:SHELLY\s+)?(?:PLUS|PRO)\b/i.test(normalized) || /\bSP[A-Z0-9]+-[A-Z0-9-]+\b/.test(upper)) {
return 2;
}
if (/\bSH[A-Z0-9]+-?[A-Z0-9-]*\b/.test(upper)) {
return 1;
}
return null;
}
function resolveShellyDeviceGeneration(identity = {}) {
for (const candidate of [
identity.gen,
identity.generation,
identity.device_generation,
identity.capabilities?.generation,
]) {
const generation = normalizeShellyDeviceGeneration(candidate);
if (generation !== null) {
return generation;
}
}
for (const candidate of [
identity.model,
identity.type,
identity.app,
identity.name,
identity.id,
identity.mac,
]) {
const generation = inferShellyDeviceGenerationFromString(candidate);
if (generation !== null) {
return generation;
}
}
return null;
}
function resolveShellyCommandGeneration(payload = {}) {
return resolveShellyDeviceGeneration({
gen: payload.gen ?? payload.generation ?? payload.deviceGeneration ?? payload.device_generation,
device_generation: payload.device_generation,
capabilities: payload.capabilities,
model: payload.model ?? payload.deviceModel ?? payload.device_model ?? payload.deviceType ?? payload.device_type,
type: payload.type ?? payload.deviceType ?? payload.device_type,
app: payload.app,
name: payload.name ?? payload.deviceName ?? payload.device_name,
id: payload.deviceId ?? payload.device_id ?? payload.relayId ?? payload.relay_id,
mac: payload.mac,
});
}
export async function discoverShellyDevices(options = {}, fetchImpl = fetch) {
const candidateIps = expandCandidateIps(options);
const discovered = [];
await Promise.all(candidateIps.map(async (ip) => {
try {
const identity = await fetchJson(`http://${ip}/shelly`, fetchImpl);
const identity = await fetchJson(`http://${ip}/shelly`, fetchImpl, {
timeoutMs: resolveShellyLocalHttpTimeoutMs(options),
});
discovered.push({
id: identity.mac || identity.id || ip,
device_id: identity.mac || identity.id || ip,
@@ -466,7 +641,7 @@ export async function discoverShellyDevices(options = {}, fetchImpl = fetch) {
channel_count: Number(identity.num_outputs || identity.num_switches || 1),
online: true,
capabilities: {
generation: identity.gen || null,
generation: resolveShellyDeviceGeneration(identity),
},
metadata: identity,
});
@@ -481,24 +656,41 @@ export async function discoverShellyDevices(options = {}, fetchImpl = fetch) {
export async function getRelayStatus(payload, fetchImpl = fetch) {
const ip = payload.localIp || payload.local_ip || payload.ip;
const channel = Number.isInteger(payload.channel) ? payload.channel : 0;
const timeoutMs = resolveShellyLocalHttpTimeoutMs(payload);
if (!ip) {
throw new Error("Missing relay local IP");
}
const attempts = [
async () => {
const rpc = await fetchJson(`http://${ip}/rpc/Switch.GetStatus?id=${channel}`, fetchImpl, { timeoutMs });
return {
online: true,
on: Boolean(rpc.output),
raw: rpc,
};
},
async () => {
const legacy = await fetchJson(`http://${ip}/relay/${channel}`, fetchImpl, { timeoutMs });
return {
online: true,
on: Boolean(legacy.ison ?? legacy.output),
raw: legacy,
};
},
];
try {
const rpc = await fetchJson(`http://${ip}/rpc/Switch.GetStatus?id=${channel}`, fetchImpl);
return {
online: true,
on: Boolean(rpc.output),
raw: rpc,
};
} catch {
const legacy = await fetchJson(`http://${ip}/relay/${channel}`, fetchImpl);
return {
online: true,
on: Boolean(legacy.ison ?? legacy.output),
raw: legacy,
};
return await Promise.any(attempts.map((attempt) => attempt()));
} catch (error) {
const errors = Array.isArray(error?.errors) ? error.errors : [error];
const message = errors
.map((entry) => entry?.message || String(entry))
.filter(Boolean)
.join("; ");
const relayStatusError = new Error(message ? `Unable to read relay status: ${message}` : "Unable to read relay status");
relayStatusError.cause = error;
throw relayStatusError;
}
}
@@ -506,25 +698,114 @@ export async function setRelayState(payload, fetchImpl = fetch) {
const ip = payload.localIp || payload.local_ip || payload.ip;
const channel = Number.isInteger(payload.channel) ? payload.channel : 0;
const on = Boolean(payload.on);
const timeoutMs = resolveShellyLocalHttpTimeoutMs(payload);
const toggleAfter = resolveRelayToggleAfterSeconds(payload);
const timerQuery = toggleAfter === null ? "" : `&toggle_after=${encodeURIComponent(String(toggleAfter))}`;
const legacyTimerQuery = toggleAfter === null ? "" : `&timer=${encodeURIComponent(String(toggleAfter))}`;
const deviceGeneration = resolveShellyCommandGeneration(payload);
if (!ip) {
throw new Error("Missing relay local IP");
}
try {
const rpc = await fetchJson(`http://${ip}/rpc/Switch.Set?id=${channel}&on=${on ? "true" : "false"}`, fetchImpl);
const runRpcSwitch = async () => {
const rpc = await fetchJson(`http://${ip}/rpc/Switch.Set?id=${channel}&on=${on ? "true" : "false"}${timerQuery}`, fetchImpl, { timeoutMs });
return {
online: true,
on: Boolean(rpc.output ?? on),
raw: rpc,
};
} catch {
const legacy = await fetchJson(`http://${ip}/relay/${channel}?turn=${on ? "on" : "off"}`, fetchImpl);
};
const runLegacySwitch = async () => {
const legacy = await fetchJson(`http://${ip}/relay/${channel}?turn=${on ? "on" : "off"}${legacyTimerQuery}`, fetchImpl, { timeoutMs });
return {
online: true,
on: Boolean(legacy.ison ?? legacy.output ?? on),
raw: legacy,
};
};
if (toggleAfter !== null) {
const attempts = deviceGeneration === 1
? [runLegacySwitch, runRpcSwitch]
: [runRpcSwitch, runLegacySwitch];
let lastError = null;
for (const attempt of attempts) {
try {
return await attempt();
} catch (error) {
if (isHttpTimeoutError(error)) {
throw error;
}
lastError = error;
}
}
throw lastError || new Error("Unable to switch relay");
}
try {
return await runRpcSwitch();
} catch (error) {
if (isHttpTimeoutError(error)) {
throw error;
}
return await runLegacySwitch();
}
}
async function mapWithConcurrency(items, limit, mapper) {
const results = new Array(items.length);
let nextIndex = 0;
const workerCount = Math.max(1, Math.min(Number(limit) || 1, items.length || 1));
await Promise.all(Array.from({ length: workerCount }, async () => {
while (nextIndex < items.length) {
const index = nextIndex;
nextIndex += 1;
results[index] = await mapper(items[index], index);
}
}));
return results;
}
async function executeRelayBatch(command, handler, fetchImpl = fetch) {
const commands = Array.isArray(command?.payload?.commands)
? command.payload.commands
: Array.isArray(command?.commands)
? command.commands
: [];
const concurrency = Math.max(1, Math.min(Number(command?.payload?.concurrency || command?.concurrency || 5), 5));
const results = await mapWithConcurrency(commands, concurrency, async (entry = {}) => {
const target = String(entry.target || entry.relay || "");
const relayId = String(entry.relayId || entry.relay_id || "");
try {
const payload = await handler(entry, fetchImpl);
return {
target,
relayId,
relay_id: relayId,
ok: true,
payload,
};
} catch (error) {
return {
target,
relayId,
relay_id: relayId,
ok: false,
error: error instanceof Error ? error.message : String(error),
};
}
});
return {
batchId: command?.payload?.batchId || command?.payload?.batch_id || command?.batchId || command?.batch_id || null,
batch_id: command?.payload?.batch_id || command?.payload?.batchId || command?.batch_id || command?.batchId || null,
results,
};
}
async function fetchArtifactBuffer(url, expectedSha256, label, fetchImpl = fetch) {
@@ -532,6 +813,15 @@ async function fetchArtifactBuffer(url, expectedSha256, label, fetchImpl = fetch
return null;
}
if (!expectedSha256) {
throw new Error(`${label} checksum is required`);
}
const normalizedExpectedSha256 = String(expectedSha256).toLowerCase();
if (!/^[a-f0-9]{64}$/.test(normalizedExpectedSha256)) {
throw new Error(`${label} checksum must be a valid sha256 hex digest`);
}
const response = await fetchImpl(url);
if (!response.ok) {
throw new Error(`${label} download failed: HTTP ${response.status}`);
@@ -539,7 +829,7 @@ async function fetchArtifactBuffer(url, expectedSha256, label, fetchImpl = fetch
const buffer = Buffer.from(await response.arrayBuffer());
const sha256 = createHash("sha256").update(buffer).digest("hex");
if (expectedSha256 && String(expectedSha256).toLowerCase() !== sha256.toLowerCase()) {
if (normalizedExpectedSha256 !== sha256.toLowerCase()) {
throw new Error(`${label} checksum mismatch`);
}
@@ -1122,12 +1412,13 @@ export function createShellBridge(sendMessage, { createPtyProcess = createDefaul
sessions.set(sessionId, sessionRecord);
sendMessage({ type: "SHELL_OPENED", sessionId });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
sendMessage({
type: "SHELL_OUTPUT",
sessionId,
data: `Failed to start root shell: ${error instanceof Error ? error.message : String(error)}\r\n`,
data: `Failed to start root shell: ${message}\r\n`,
});
sendMessage({ type: "SHELL_EXIT", sessionId, code: 1 });
sendMessage({ type: "SHELL_EXIT", sessionId, code: 1, reason: "shell_spawn_failed", message });
}
};
@@ -1179,6 +1470,10 @@ export async function handleAgentCommand(command, deps = {}) {
return await getRelayStatus(command.payload || {}, fetchImpl);
case "SET_RELAY_STATE":
return await setRelayState(command.payload || {}, fetchImpl);
case "BATCH_RELAY_STATUS":
return await executeRelayBatch(command, getRelayStatus, fetchImpl);
case "BATCH_SET_RELAY_STATE":
return await executeRelayBatch(command, setRelayState, fetchImpl);
case "RUN_UPDATE":
return await runUpdate(command.payload || {}, fetchImpl, deps);
case "UNINSTALL_AGENT":
@@ -1462,13 +1757,15 @@ function normalizeShellEvent(message) {
}
if (message.type === "SHELL_EXIT") {
const reason = String(message.reason || "agent_exit");
return {
sessionId: String(sessionId),
event: {
type: "CLOSED",
payload: {
code: Number.isFinite(message.code) ? message.code : 0,
reason: "agent_exit",
reason,
message: message.message ? String(message.message) : null,
},
},
};
@@ -1560,6 +1857,10 @@ export async function processPolledShellAction(config, action, shell, fetchImpl
}
try {
if (!isShellAccessEnabled(config)) {
throw new Error("Shell access is disabled by local configuration");
}
if (actionType === "OPEN") {
await shell.open(payload);
} else if (actionType === "INPUT") {
@@ -1599,6 +1900,8 @@ function createBrokerBridge({
disconnectReason: null,
lastConnectedAt: null,
lastDisconnectedAt: null,
lastCloseCode: null,
lastCloseClean: null,
};
let stopped = false;
@@ -1656,6 +1959,8 @@ function createBrokerBridge({
state.connected = true;
state.lastError = null;
state.disconnectReason = null;
state.lastCloseCode = null;
state.lastCloseClean = null;
state.lastConnectedAt = formatUpdateTimestamp();
};
@@ -1690,18 +1995,30 @@ function createBrokerBridge({
}
if (message.type === "OPEN_ROOT_SHELL") {
if (!isShellAccessEnabled(config)) {
throw new Error("Shell access is disabled by local configuration");
}
await shell.open(message.payload || {});
return;
}
if (message.type === "SHELL_INPUT") {
if (!isShellAccessEnabled(config)) {
throw new Error("Shell access is disabled by local configuration");
}
shell.input(message.payload || {});
return;
}
if (message.type === "RESIZE_ROOT_SHELL") {
if (!isShellAccessEnabled(config)) {
throw new Error("Shell access is disabled by local configuration");
}
shell.resize(message.payload || {});
return;
}
if (message.type === "CLOSE_ROOT_SHELL") {
if (!isShellAccessEnabled(config)) {
throw new Error("Shell access is disabled by local configuration");
}
shell.close(message.payload || {});
}
} catch {
@@ -1709,14 +2026,19 @@ function createBrokerBridge({
}
};
socket.onerror = () => {
state.lastError = "Broker connection failed";
socket.onerror = (error) => {
state.lastError = normalizeBrokerSocketError(error, socketUrl);
};
socket.onclose = (event) => {
socket = null;
state.connected = false;
state.disconnectReason = event.reason || "broker_disconnected";
state.lastCloseCode = Number.isFinite(Number(event.code)) ? Number(event.code) : null;
state.lastCloseClean = typeof event.wasClean === "boolean" ? event.wasClean : null;
if (state.lastCloseCode && state.lastCloseCode !== 1000 && !state.lastError) {
state.lastError = `Broker websocket closed with code ${state.lastCloseCode}`;
}
state.lastDisconnectedAt = formatUpdateTimestamp();
if (!stopped) {
scheduleReconnect();
@@ -1764,19 +2086,18 @@ export async function startAgent({
const commandPollTimeoutSeconds = Number(config.commandPollTimeoutSeconds || 20);
const commandPollRetryDelayMs = Number(config.commandPollRetryDelayMs || 1000);
const brokerReconnectDelayMs = Number(config.brokerReconnectDelayMs || DEFAULT_BROKER_RECONNECT_DELAY_MS);
const shellActionPollTimeoutSeconds = Number(config.shellActionPollTimeoutSeconds || 20);
const shellActionPollRetryDelayMs = Number(config.shellActionPollRetryDelayMs || 1000);
let stopped = false;
let cpuSnapshot = null;
let lastHeartbeatLatencyMs = null;
void createShellBridgeImpl;
let brokerBridge = null;
const shell = {
open: async () => {},
input: () => {},
resize: () => {},
close: () => {},
dispose: () => {},
};
const shellEventPublisher = createShellEventPublisher(config, fetchImpl);
const shell = createShellBridgeImpl((message) => {
brokerBridge?.send(message);
shellEventPublisher.publish(message);
});
brokerBridge = createBrokerBridge({
config,
shell,
@@ -1841,8 +2162,32 @@ export async function startAgent({
}
};
const runShellActionPollLoop = async () => {
while (!stopped) {
try {
const action = await pollShellActionJob(config, fetchImpl, shellActionPollTimeoutSeconds);
if (stopped) {
break;
}
if (!action) {
continue;
}
await processPolledShellAction(config, action, shell, fetchImpl);
} catch {
if (stopped) {
break;
}
await new Promise((resolve) => setTimeout(resolve, shellActionPollRetryDelayMs));
}
}
};
await sendTransportHeartbeat();
const commandPollPromise = runCommandPollLoop();
const shellActionPollPromise = runShellActionPollLoop();
const timer = setInterval(() => {
sendTransportHeartbeat().catch(() => {});
@@ -1853,8 +2198,9 @@ export async function startAgent({
stopped = true;
clearInterval(timer);
brokerBridge?.stop();
await shellEventPublisher.drain();
shell.dispose();
await Promise.allSettled([commandPollPromise]);
await Promise.allSettled([commandPollPromise, shellActionPollPromise]);
};
return {
+351 -2
View File
@@ -1,6 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
import { execFile as execFileCallback } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -11,11 +12,13 @@ import {
buildStatusReport,
claimIfNeeded,
createShellBridge,
discoverShellyDevices,
finalizePendingUpdateOnStartup,
getAgentStatus,
getRelayStatus,
loadConfig,
parseCliArgs,
processPolledShellAction,
processPolledCommand,
runCli,
runUpdate,
@@ -38,6 +41,10 @@ function makeFetchResponse(body) {
};
}
function sha256Hex(body) {
return createHash("sha256").update(body).digest("hex");
}
async function waitFor(predicate, { timeoutMs = 1000, intervalMs = 10, description = "condition" } = {}) {
const deadline = Date.now() + timeoutMs;
@@ -120,6 +127,215 @@ test("relay status and switch commands support both Shelly RPC and legacy endpoi
assert.equal(switched.on, false);
});
test("batch relay commands return per-relay results without failing the whole batch", async () => {
const fakeFetch = async (url) => {
const value = String(url);
if (value.includes("10.1.0.31")) {
return {
ok: true,
async json() {
return { output: true };
},
};
}
throw new Error("relay offline");
};
const result = await handleAgentCommand({
commandType: "BATCH_SET_RELAY_STATE",
payload: {
batch_id: "batch-1",
commands: [
{ target: "MACHINE", relayId: "relay-machine", localIp: "10.1.0.31", channel: 0, on: true },
{ target: "EXIT", relayId: "relay-out", localIp: "10.1.0.32", channel: 0, on: true },
],
},
}, { fetchImpl: fakeFetch });
assert.equal(result.batch_id, "batch-1");
assert.equal(result.results.length, 2);
assert.equal(result.results[0].ok, true);
assert.equal(result.results[0].target, "MACHINE");
assert.equal(result.results[1].ok, false);
assert.match(result.results[1].error, /relay offline/);
});
test("Shelly discovery infers Gen3 from S3 relay model codes when generation is omitted", async () => {
const inventory = await discoverShellyDevices({ candidateIps: ["192.168.1.2"] }, async (url) => {
assert.equal(String(url), "http://192.168.1.2/shelly");
return {
ok: true,
async json() {
return {
id: "shelly1minig3-e4b3231f6410",
mac: "E4B3231F6410",
model: "S3SW-001X8EU",
type: "Shelly 1 Mini Gen3",
num_switches: 1,
};
},
};
});
assert.equal(inventory.length, 1);
assert.equal(inventory[0].device_id, "E4B3231F6410");
assert.equal(inventory[0].model, "S3SW-001X8EU");
assert.equal(inventory[0].capabilities.generation, 3);
});
test("relay status reads use the legacy endpoint when the RPC status endpoint stalls", async () => {
const urls = [];
const fakeFetch = async (url) => {
urls.push(String(url));
if (String(url).includes("Switch.GetStatus")) {
return new Promise(() => {});
}
if (String(url) === "http://10.1.0.31/relay/0") {
return {
ok: true,
async json() {
return { ison: false };
},
};
}
throw new Error(`Unexpected URL: ${url}`);
};
const startedAt = Date.now();
const status = await getRelayStatus({ localIp: "10.1.0.31", channel: 0, timeoutMs: 50 }, fakeFetch);
assert.equal(status.online, true);
assert.equal(status.on, false);
assert.deepEqual(urls.sort(), [
"http://10.1.0.31/relay/0",
"http://10.1.0.31/rpc/Switch.GetStatus?id=0",
]);
assert.ok(Date.now() - startedAt < 250);
});
test("relay switch commands fail quickly when the local Shelly request stalls", async () => {
let calls = 0;
const hangingFetch = async () => {
calls += 1;
return new Promise(() => {});
};
const startedAt = Date.now();
await assert.rejects(
() => setRelayState({ localIp: "10.1.0.31", channel: 0, on: true, timeoutMs: 50 }, hangingFetch),
/HTTP request timed out after 50ms/
);
assert.equal(calls, 1);
assert.ok(Date.now() - startedAt < 500);
});
test("relay switch commands pass timer values to local Shelly APIs", async () => {
const legacyUrls = [];
const legacyFetch = async (url) => {
legacyUrls.push(String(url));
return {
ok: true,
async json() {
return { ison: true, has_timer: true, timer_duration: 3 };
},
};
};
await setRelayState({
localIp: "10.1.0.31",
channel: 0,
on: true,
toggleAfter: 3,
deviceGeneration: 1,
}, legacyFetch);
assert.equal(
legacyUrls[0],
"http://10.1.0.31/relay/0?turn=on&timer=3"
);
const gen3Urls = [];
const gen3Fetch = async (url) => {
gen3Urls.push(String(url));
return {
ok: true,
async json() {
return { output: true };
},
};
};
await setRelayState({
localIp: "10.1.0.31",
channel: 0,
on: true,
toggle_after: 3,
device_generation: 3,
}, gen3Fetch);
assert.equal(
gen3Urls[0],
"http://10.1.0.31/rpc/Switch.Set?id=0&on=true&toggle_after=3"
);
const fallbackUrls = [];
const legacyFallbackFetch = async (url) => {
fallbackUrls.push(String(url));
if (String(url).includes("/rpc/")) {
throw new Error("RPC switch endpoint unsupported");
}
return {
ok: true,
async json() {
return { ison: true, has_timer: true, timer_duration: 3 };
},
};
};
await setRelayState({
localIp: "10.1.0.31",
channel: 0,
on: true,
toggle_after: 3,
device_model: "S3SW-001X8EU",
}, legacyFallbackFetch);
assert.deepEqual(fallbackUrls, [
"http://10.1.0.31/rpc/Switch.Set?id=0&on=true&toggle_after=3",
"http://10.1.0.31/relay/0?turn=on&timer=3",
]);
const cappedUrls = [];
const cappedFetch = async (url) => {
cappedUrls.push(String(url));
return {
ok: true,
async json() {
return { output: true };
},
};
};
await setRelayState({
localIp: "10.1.0.31",
channel: 0,
on: true,
toggle_after: 999999999,
device_generation: 3,
}, cappedFetch);
assert.equal(
cappedUrls[0],
"http://10.1.0.31/rpc/Switch.Set?id=0&on=true&toggle_after=5"
);
});
test("runUpdate stages a pending verification restart after installing new artifacts", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-update-"));
const configPath = path.join(tempDir, "config.json");
@@ -142,19 +358,23 @@ test("runUpdate stages a pending verification restart after installing new artif
execCalls.push({ command, args, options });
return { stdout: "{}" };
};
const agentBody = "// new agent\n";
const packageBody = JSON.stringify({ name: "new-edge-agent" }, null, 2);
const fakeFetch = async (url) => {
if (String(url).endsWith("/agent.mjs")) {
return makeFetchResponse("// new agent\n");
return makeFetchResponse(agentBody);
}
if (String(url).endsWith("/package.json")) {
return makeFetchResponse(JSON.stringify({ name: "new-edge-agent" }, null, 2));
return makeFetchResponse(packageBody);
}
throw new Error(`Unexpected URL: ${url}`);
};
const result = await runUpdate({
artifactUrl: "https://api.example.test/edge-agent/artifacts/agent.mjs",
sha256: sha256Hex(agentBody),
packageUrl: "https://api.example.test/edge-agent/artifacts/package.json",
packageSha256: sha256Hex(packageBody),
targetVersion: "1.1.0",
releaseChannel: "stable",
restartMode: "spawn",
@@ -182,6 +402,45 @@ test("runUpdate stages a pending verification restart after installing new artif
await rm(tempDir, { recursive: true, force: true });
});
test("runUpdate rejects artifacts without required checksums", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-update-checksum-"));
const configPath = path.join(tempDir, "config.json");
const liveConfig = {
apiUrl: "https://api.example.test",
gatewayId: 42,
agentToken: "agent-token",
installDir: tempDir,
restartMode: "spawn",
installedVersion: "1.0.0",
targetVersion: "1.0.0",
};
await writeFile(configPath, JSON.stringify(liveConfig, null, 2));
await writeFile(path.join(tempDir, "agent.mjs"), "// old agent\n");
let fetchCalled = false;
await assert.rejects(
runUpdate({
artifactUrl: "https://api.example.test/edge-agent/artifacts/agent.mjs",
targetVersion: "1.1.0",
}, async () => {
fetchCalled = true;
return makeFetchResponse("// new agent\n");
}, {
configPath,
config: liveConfig,
liveConfig,
execFileImpl: async () => ({ stdout: "{}" }),
}),
/Agent artifact checksum is required/
);
assert.equal(fetchCalled, false);
assert.equal(await readFile(path.join(tempDir, "agent.mjs"), "utf8"), "// old agent\n");
await rm(tempDir, { recursive: true, force: true });
});
test("handleAgentCommand returns an uninstall follow-up envelope for gateway removal", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-uninstall-envelope-"));
@@ -473,6 +732,39 @@ test("shell bridge proxies PTY output, input, resize, close, and dispose events"
assert.ok(messages.some((message) => message.type === "SHELL_EXIT" && message.code === 0));
});
test("shell bridge reports structured spawn failures", async () => {
const messages = [];
const shell = createShellBridge(
(message) => messages.push(message),
{
createPtyProcess: async () => {
throw new Error("node-pty unavailable");
},
}
);
await shell.open({ sessionId: "spawn-failure-shell" });
assert.ok(
messages.some(
(message) =>
message.type === "SHELL_OUTPUT" &&
message.sessionId === "spawn-failure-shell" &&
/Failed to start root shell: node-pty unavailable/.test(message.data)
)
);
assert.ok(
messages.some(
(message) =>
message.type === "SHELL_EXIT" &&
message.sessionId === "spawn-failure-shell" &&
message.code === 1 &&
message.reason === "shell_spawn_failed" &&
message.message === "node-pty unavailable"
)
);
});
test("startAgent reports API polling metadata, executes polled commands, and uploads shell events", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-"));
const configPath = path.join(tempDir, "config.json");
@@ -485,6 +777,7 @@ test("startAgent reports API polling metadata, executes polled commands, and upl
commandPollRetryDelayMs: 5,
shellActionPollTimeoutSeconds: 0,
shellActionPollRetryDelayMs: 5,
enableShellAccess: true,
}));
const heartbeats = [];
@@ -709,6 +1002,7 @@ test("startAgent reports API polling metadata, executes polled commands, and upl
assert.equal(commandResultPosts[0].body.ok, true);
assert.equal(Array.isArray(commandResultPosts[0].body.payload.inventory), true);
assert.equal(commandResultPosts[0].body.payload.inventory[0].device_id, "AA:BB:CC:DD:EE:FF");
assert.equal(commandResultPosts[0].body.payload.inventory[0].capabilities.generation, 2);
assert.ok(shellCalls.some((call) => call.type === "open"));
assert.ok(shellCalls.some((call) => call.type === "close"));
@@ -742,6 +1036,61 @@ test("startAgent reports API polling metadata, executes polled commands, and upl
}
});
test("processPolledShellAction denies shell access when locally disabled", async () => {
const submissions = [];
const fakeFetch = async (url, options = {}) => {
if (/\/shell-actions\/\d+\/result$/.test(String(url))) {
submissions.push({ url, body: JSON.parse(options.body) });
return {
ok: true,
async json() {
return { data: { acknowledged: true } };
},
};
}
throw new Error(`Unexpected URL: ${url}`);
};
const shell = {
async open() {
throw new Error("should not run");
},
input() {
throw new Error("should not run");
},
resize() {
throw new Error("should not run");
},
close() {
throw new Error("should not run");
},
};
const result = await processPolledShellAction(
{
apiUrl: "https://api.example.test",
gatewayId: 42,
agentToken: "agent-token",
enableShellAccess: false,
},
{
id: 501,
actionType: "OPEN",
payload: {
sessionId: 44,
},
},
shell,
fakeFetch
);
assert.equal(result.ok, false);
assert.match(result.error, /Shell access is disabled/);
assert.equal(submissions.length, 1);
assert.equal(submissions[0].body.ok, false);
});
test("status helpers report config without exposing the agent token", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "edge-agent-status-"));
const configPath = path.join(tempDir, "config.json");
+3 -3
View File
@@ -4,9 +4,9 @@
"requires": true,
"packages": {
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
+37
View File
@@ -40,6 +40,10 @@ class Receiver extends Writable {
* extensions
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
* client or server mode
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
* buffered data chunks
* @param {Number} [options.maxFragments=0] The maximum number of message
* fragments
* @param {Number} [options.maxPayload=0] The maximum allowed message length
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
@@ -54,6 +58,8 @@ class Receiver extends Writable {
this._binaryType = options.binaryType || BINARY_TYPES[0];
this._extensions = options.extensions || {};
this._isServer = !!options.isServer;
this._maxBufferedChunks = options.maxBufferedChunks | 0;
this._maxFragments = options.maxFragments | 0;
this._maxPayload = options.maxPayload | 0;
this._skipUTF8Validation = !!options.skipUTF8Validation;
this[kWebSocket] = undefined;
@@ -71,6 +77,7 @@ class Receiver extends Writable {
this._totalPayloadLength = 0;
this._messageLength = 0;
this._numFragments = 0;
this._fragments = [];
this._errored = false;
@@ -89,6 +96,22 @@ class Receiver extends Writable {
_write(chunk, encoding, cb) {
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
if (
this._maxBufferedChunks > 0 &&
this._buffers.length >= this._maxBufferedChunks
) {
cb(
this.createError(
RangeError,
'Too many buffered chunks',
false,
1008,
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
)
);
return;
}
this._bufferedBytes += chunk.length;
this._buffers.push(chunk);
this.startLoop(cb);
@@ -478,6 +501,19 @@ class Receiver extends Writable {
return;
}
if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
const error = this.createError(
RangeError,
'Too many message fragments',
false,
1008,
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
);
cb(error);
return;
}
if (this._compressed) {
this._state = INFLATING;
this.decompress(data, cb);
@@ -550,6 +586,7 @@ class Receiver extends Writable {
this._totalPayloadLength = 0;
this._messageLength = 0;
this._fragmented = 0;
this._numFragments = 0;
this._fragments = [];
if (this._opcode === 2) {
+6 -1
View File
@@ -4,6 +4,9 @@
const { Duplex } = require('stream');
const { randomFillSync } = require('crypto');
const {
types: { isUint8Array }
} = require('util');
const PerMessageDeflate = require('./permessage-deflate');
const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants');
@@ -200,8 +203,10 @@ class Sender {
if (typeof data === 'string') {
buf.write(data, 2);
} else {
} else if (isUint8Array(data)) {
buf.set(data, 2);
} else {
throw new TypeError('Second argument must be a string or a Uint8Array');
}
}
+8
View File
@@ -43,6 +43,10 @@ class WebSocketServer extends EventEmitter {
* called
* @param {Function} [options.handleProtocols] A hook to handle protocols
* @param {String} [options.host] The hostname where to bind the server
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
* buffered data chunks
* @param {Number} [options.maxFragments=16384] The maximum number of message
* fragments
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
* size
* @param {Boolean} [options.noServer=false] Enable no server mode
@@ -65,6 +69,8 @@ class WebSocketServer extends EventEmitter {
options = {
allowSynchronousEvents: true,
autoPong: true,
maxBufferedChunks: 256 * 1024,
maxFragments: 16 * 1024,
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
perMessageDeflate: false,
@@ -424,6 +430,8 @@ class WebSocketServer extends EventEmitter {
ws.setSocket(socket, head, {
allowSynchronousEvents: this.options.allowSynchronousEvents,
maxBufferedChunks: this.options.maxBufferedChunks,
maxFragments: this.options.maxFragments,
maxPayload: this.options.maxPayload,
skipUTF8Validation: this.options.skipUTF8Validation
});
+14
View File
@@ -201,6 +201,10 @@ class WebSocket extends EventEmitter {
* multiple times in the same tick
* @param {Function} [options.generateMask] The function used to generate the
* masking key
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
* buffered data chunks
* @param {Number} [options.maxFragments=0] The maximum number of message
* fragments
* @param {Number} [options.maxPayload=0] The maximum allowed message size
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
@@ -212,6 +216,8 @@ class WebSocket extends EventEmitter {
binaryType: this.binaryType,
extensions: this._extensions,
isServer: this._isServer,
maxBufferedChunks: options.maxBufferedChunks,
maxFragments: options.maxFragments,
maxPayload: options.maxPayload,
skipUTF8Validation: options.skipUTF8Validation
});
@@ -640,6 +646,10 @@ module.exports = WebSocket;
* masking key
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
* handshake request
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
* buffered data chunks
* @param {Number} [options.maxFragments=16384] The maximum number of message
* fragments
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
* size
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
@@ -660,6 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
autoPong: true,
closeTimeout: CLOSE_TIMEOUT,
protocolVersion: protocolVersions[1],
maxBufferedChunks: 256 * 1024,
maxFragments: 16 * 1024,
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
perMessageDeflate: true,
@@ -1017,6 +1029,8 @@ function initAsClient(websocket, address, protocols, options) {
websocket.setSocket(socket, head, {
allowSynchronousEvents: opts.allowSynchronousEvents,
generateMask: opts.generateMask,
maxBufferedChunks: opts.maxBufferedChunks,
maxFragments: opts.maxFragments,
maxPayload: opts.maxPayload,
skipUTF8Validation: opts.skipUTF8Validation
});
+5 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ws",
"version": "8.20.0",
"version": "8.21.1",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
"keywords": [
"HyBi",
@@ -66,5 +66,9 @@
"nyc": "^15.0.0",
"prettier": "^3.0.0",
"utf-8-validate": "^6.0.0"
},
"allowScripts": {
"bufferutil": true,
"utf-8-validate": true
}
}
+4 -4
View File
@@ -6,13 +6,13 @@
"": {
"name": "truckwash-edge-broker",
"dependencies": {
"ws": "^8.18.0"
"ws": "^8.21.1"
}
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
+1 -1
View File
@@ -7,6 +7,6 @@
"test:live": "node --test live/live-smoke.mjs"
},
"dependencies": {
"ws": "^8.18.0"
"ws": "^8.21.1"
}
}
+328 -35
View File
@@ -3,6 +3,9 @@ import { randomUUID } from "node:crypto";
import { fileURLToPath } from "node:url";
import { WebSocketServer } from "ws";
const DEFAULT_SHELL_OPEN_TIMEOUT_MS = 15000;
const TELEMETRY_INGEST_ERROR_MESSAGE = "Telemetry ingestion failed";
function parseJsonBody(req) {
return new Promise((resolve, reject) => {
let raw = "";
@@ -46,14 +49,40 @@ function resolveManagerUrl(options = {}) {
return trimTrailingSlash(options.managerUrl || process.env.EDGE_MANAGER_URL || process.env.EDGE_PUBLIC_API_URL || "");
}
function resolveAuthMode(options = {}, managerUrl = "") {
function resolveAuthMode(options = {}) {
if (options.authMode) {
return options.authMode;
}
if (process.env.EDGE_AUTH_MODE) {
return process.env.EDGE_AUTH_MODE;
}
return managerUrl ? "manager" : "stub";
return "strict";
}
function resolveSharedSecret(options = {}) {
return String(options.sharedSecret ?? process.env.EDGE_BROKER_SHARED_SECRET ?? "").trim();
}
function requireSharedSecret(req, res, sharedSecret) {
if (sharedSecret === "") {
jsonResponse(res, 503, {
ok: false,
error: "Edge broker shared secret is not configured",
shared_secret_required: true,
});
return false;
}
if (req.headers["x-edge-broker-secret"] !== sharedSecret) {
jsonResponse(res, 403, {
ok: false,
error: "Forbidden",
shared_secret_required: true,
});
return false;
}
return true;
}
function parseScopes(value) {
@@ -106,11 +135,53 @@ function sendJson(ws, payload) {
return true;
}
function currentTimestamp() {
return new Date().toISOString();
}
function normalizeErrorMessage(error, fallback = "Connection step failed") {
if (error instanceof Error && error.message) {
return error.message;
}
const message = String(error || "").trim();
return message || fallback;
}
function rejectUpgrade(socket, statusCode, errorCode, message, details = {}) {
const statusText = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
500: "Internal Server Error",
503: "Service Unavailable",
}[statusCode] || "WebSocket Upgrade Rejected";
const body = JSON.stringify({
ok: false,
error_code: errorCode,
message,
details,
});
socket.end(
[
`HTTP/1.1 ${statusCode} ${statusText}`,
"content-type: application/json; charset=utf-8",
`content-length: ${Buffer.byteLength(body)}`,
"connection: close",
"",
body,
].join("\r\n")
);
}
export function createBrokerServer(options = {}) {
const sharedSecret = options.sharedSecret ?? process.env.EDGE_BROKER_SHARED_SECRET ?? "";
const sharedSecret = resolveSharedSecret(options);
const managerUrl = resolveManagerUrl(options);
const authMode = resolveAuthMode(options, managerUrl);
const authMode = resolveAuthMode(options);
const commandTimeoutMs = options.commandTimeoutMs ?? 10000;
const shellOpenTimeoutMs = options.shellOpenTimeoutMs ?? DEFAULT_SHELL_OPEN_TIMEOUT_MS;
const agents = new Map();
const pendingCommands = new Map();
@@ -134,7 +205,11 @@ export function createBrokerServer(options = {}) {
});
const json = await parseJsonResponse(response);
if (!response.ok) {
throw new Error(json?.data?.message || json?.message || json?.error || `HTTP ${response.status}`);
const error = new Error(json?.data?.message || json?.message || json?.error || `HTTP ${response.status}`);
error.status = response.status;
error.code = json?.data?.error_code || json?.error_code || null;
error.details = json?.data?.diagnostics || json?.diagnostics || null;
throw error;
}
return json?.data ?? json;
@@ -164,11 +239,12 @@ export function createBrokerServer(options = {}) {
options.closeShellSession ||
(authMode === "stub"
? async () => ({})
: async (_id, token, transcript, reason) =>
: async (_id, token, transcript, reason, details = {}) =>
managerRequest("/edge-agent/internal/shell-sessions/close", {
token,
transcript,
reason,
...details,
}));
const validateBrowserStream =
options.validateBrowserStream ||
@@ -220,6 +296,12 @@ export function createBrokerServer(options = {}) {
? async (_gatewayId, payload = {}) => payload
: async (gatewayId, payload = {}) =>
managerRequest(`/edge-agent/internal/gateways/${gatewayId}/logs`, payload));
const ingestMachineSignal =
options.ingestMachineSignal ||
(authMode === "stub"
? async (_gatewayId, payload = {}) => payload
: async (gatewayId, payload = {}) =>
managerRequest(`/edge-agent/internal/gateways/${gatewayId}/selfserve/machine-signal`, payload));
const broadcastGatewayEvent = (gatewayId, message) => {
const sessionIds = gatewayStreamSessions.get(String(gatewayId));
@@ -241,29 +323,66 @@ export function createBrokerServer(options = {}) {
}
};
const closeBrowserShellSession = async (sessionRecord, reason) => {
const closeBrowserShellSession = async (sessionRecord, reason, details = {}) => {
try {
await closeShellSession(
sessionRecord.session.id,
sessionRecord.ws.sessionToken,
sessionRecord.transcript,
reason
reason,
{
message: sessionRecord.closedMessage || null,
code: sessionRecord.closedCode ?? null,
stage: sessionRecord.closedStage || null,
broker_connection_id: sessionRecord.agentConnectionId || null,
details: sessionRecord.closedDetails || {},
...details,
}
);
} catch {
// Preserve socket teardown even when the manager callback is unavailable.
}
};
const clearShellOpenTimer = (sessionRecord) => {
if (sessionRecord?.openTimer) {
clearTimeout(sessionRecord.openTimer);
sessionRecord.openTimer = null;
}
};
const closeBrowserShellSocket = (sessionRecord, reason, message = null, code = 1000, details = {}) => {
sessionRecord.closedReason = reason;
sessionRecord.closedMessage = message || null;
sessionRecord.closedCode = code;
sessionRecord.closedDetails = details && typeof details === "object" ? details : {};
sessionRecord.closedStage = String(sessionRecord.closedDetails.stage || (sessionRecord.opened ? "shell_active" : "shell_open"));
clearShellOpenTimer(sessionRecord);
if (sessionRecord.ws.readyState >= 2) {
return;
}
sendJson(sessionRecord.ws, {
type: "closed",
reason,
code,
...(message ? { message } : {}),
...(Object.keys(sessionRecord.closedDetails).length ? { details: sessionRecord.closedDetails } : {}),
});
sessionRecord.ws.close(code, reason);
};
const markGatewayShellSessionsClosed = (gatewayId, reason) => {
for (const sessionRecord of browserShellSessions.values()) {
if (String(sessionRecord.session.gateway_id) !== String(gatewayId)) {
continue;
}
sessionRecord.closedReason = reason;
if (sessionRecord.ws.readyState < 2) {
sessionRecord.ws.close();
}
closeBrowserShellSocket(sessionRecord, reason, "Gateway agent disconnected from the broker.", 1011, {
stage: "agent_disconnect",
gateway_id: gatewayId,
shell_session_id: sessionRecord.session.id,
});
}
};
@@ -320,12 +439,73 @@ export function createBrokerServer(options = {}) {
return syncPromise;
};
const sendAgentWelcome = (ws) =>
sendJson(ws, {
type: "WELCOME",
target: "edge-agent",
gatewayId: String(ws.gatewayId || ""),
connectionId: ws.connectionId || null,
agentInstanceId: ws.agentInstanceId || null,
serverTime: currentTimestamp(),
broker: {
authMode,
},
});
const sendAgentConnectionProgress = (ws, stage, status, message, details = {}) =>
sendJson(ws, {
type: "CONNECTION_PROGRESS",
gatewayId: String(ws.gatewayId || ""),
connectionId: ws.connectionId || null,
stage,
status,
message,
...details,
serverTime: currentTimestamp(),
});
const sendAgentConnectionError = (ws, stage, error, details = {}) =>
sendJson(ws, {
type: "CONNECTION_ERROR",
gatewayId: String(ws.gatewayId || ""),
connectionId: ws.connectionId || null,
stage,
status: "failed",
message: normalizeErrorMessage(error),
error: normalizeErrorMessage(error),
...details,
serverTime: currentTimestamp(),
});
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url, "http://localhost");
if (req.method === "GET" && url.pathname === "/api/health") {
jsonResponse(res, 200, {
ok: true,
service: "edge-broker",
auth_mode: authMode,
manager_url_configured: Boolean(managerUrl),
shared_secret_configured: Boolean(sharedSecret),
agents_connected: agents.size,
});
return;
}
if (req.method === "POST" && url.pathname === "/api/diagnostics/shared-secret") {
if (!requireSharedSecret(req, res, sharedSecret)) {
return;
}
jsonResponse(res, 200, {
ok: true,
shared_secret_required: true,
});
return;
}
if (req.method === "POST" && /^\/api\/gateways\/\d+\/commands$/.test(url.pathname)) {
if (sharedSecret && req.headers["x-edge-broker-secret"] !== sharedSecret) {
jsonResponse(res, 403, { error: "Forbidden" });
if (!requireSharedSecret(req, res, sharedSecret)) {
return;
}
@@ -368,8 +548,7 @@ export function createBrokerServer(options = {}) {
}
if (req.method === "POST" && /^\/api\/gateways\/\d+\/sync$/.test(url.pathname)) {
if (sharedSecret && req.headers["x-edge-broker-secret"] !== sharedSecret) {
jsonResponse(res, 403, { error: "Forbidden" });
if (!requireSharedSecret(req, res, sharedSecret)) {
return;
}
@@ -398,7 +577,18 @@ export function createBrokerServer(options = {}) {
return;
}
const gatewayInfo = await validateAgent({ gatewayId, token, headers: req.headers });
let gatewayInfo;
try {
gatewayInfo = await validateAgent({ gatewayId, token, headers: req.headers });
} catch (error) {
const status = Number(error?.status) === 403 ? 403 : Number(error?.status) === 401 ? 401 : 503;
rejectUpgrade(socket, status, error?.code || "agent_validation_failed", "Gateway agent could not be validated.", {
stage: "agent_validate",
});
return;
}
wss.handleUpgrade(req, socket, head, (ws) => {
const existing = agents.get(gatewayId);
if (existing && existing.readyState < 2) {
@@ -410,6 +600,9 @@ export function createBrokerServer(options = {}) {
ws.agentInstanceId = agentInstanceId || null;
ws.connectionId = randomUUID();
agents.set(gatewayId, ws);
wss.emit("connection", ws, req);
sendAgentWelcome(ws);
sendAgentConnectionProgress(ws, "presence", "started", "Reporting broker presence to the edge manager.");
reportGatewayPresence(gatewayId, {
status: "connected",
connectionId: ws.connectionId,
@@ -417,15 +610,30 @@ export function createBrokerServer(options = {}) {
remote_address: req.socket.remoteAddress || null,
agent_instance_id: ws.agentInstanceId,
},
}).catch(() => {});
})
.then(() => {
sendAgentConnectionProgress(ws, "presence", "succeeded", "Broker presence was reported.");
})
.catch((error) => {
sendAgentConnectionError(ws, "presence", error);
});
broadcastGatewayEvent(gatewayId, {
type: "presence.changed",
gatewayId,
status: "connected",
connectionId: ws.connectionId,
});
syncGatewayBacklog(gatewayId, ws).catch(() => {});
wss.emit("connection", ws, req);
sendAgentConnectionProgress(ws, "backlog", "started", "Synchronizing queued gateway work.");
syncGatewayBacklog(gatewayId, ws)
.then((result) => {
sendAgentConnectionProgress(ws, "backlog", "succeeded", "Queued gateway work was synchronized.", {
queued: Boolean(result?.queued),
dispatchCount: Array.isArray(result?.dispatch) ? result.dispatch.length : 0,
});
})
.catch((error) => {
sendAgentConnectionError(ws, "backlog", error);
});
});
return;
}
@@ -433,11 +641,21 @@ export function createBrokerServer(options = {}) {
if (url.pathname === "/ws/browser-shell") {
const token = String(url.searchParams.get("token") || "");
if (token === "") {
socket.destroy();
rejectUpgrade(socket, 400, "shell_session_token_missing", "Missing shell session token.");
return;
}
const session = await validateShellSession({ token, headers: req.headers });
let session;
try {
session = await validateShellSession({ token, headers: req.headers });
} catch (error) {
rejectUpgrade(socket, Number(error?.status) === 403 ? 403 : 401, error?.code || "shell_session_invalid", "Shell session could not be validated.", {
stage: "shell_session_validate",
});
return;
}
wss.handleUpgrade(req, socket, head, (ws) => {
ws.sessionToken = token;
ws.sessionInfo = session;
@@ -446,11 +664,20 @@ export function createBrokerServer(options = {}) {
session,
transcript: "",
closedReason: null,
closedMessage: null,
closedCode: null,
closedDetails: {},
closedStage: null,
opened: false,
openTimer: null,
agentConnectionId: null,
};
browserShellSessions.set(String(session.id), sessionRecord);
wss.emit("connection", ws, req);
const agent = agents.get(String(session.gateway_id));
if (agent && agent.readyState === 1) {
sessionRecord.agentConnectionId = agent.connectionId || null;
sendJson(agent, {
type: "OPEN_ROOT_SHELL",
payload: {
@@ -463,11 +690,34 @@ export function createBrokerServer(options = {}) {
shellArgs: session.shell_args ?? session.metadata?.shell_args ?? [],
},
});
sessionRecord.openTimer = setTimeout(() => {
closeBrowserShellSocket(
sessionRecord,
"shell_open_timeout",
"Gateway agent did not confirm that the shell opened before the broker timeout.",
1011,
{
stage: "shell_open",
timeout_ms: shellOpenTimeoutMs,
gateway_id: session.gateway_id,
shell_session_id: session.id,
}
);
}, Math.max(250, shellOpenTimeoutMs));
sessionRecord.openTimer.unref?.();
} else {
sessionRecord.closedReason = "agent_offline";
ws.close();
closeBrowserShellSocket(
sessionRecord,
"agent_offline",
"Gateway agent is not connected to the broker.",
1011,
{
stage: "agent_lookup",
gateway_id: session.gateway_id,
shell_session_id: session.id,
}
);
}
wss.emit("connection", ws, req);
});
return;
}
@@ -499,8 +749,8 @@ export function createBrokerServer(options = {}) {
});
return;
}
} catch {
socket.destroy();
} catch (error) {
rejectUpgrade(socket, 500, "websocket_upgrade_failed", "WebSocket upgrade failed.");
return;
}
@@ -534,18 +784,34 @@ export function createBrokerServer(options = {}) {
}
if (message.type === "TELEMETRY") {
const payload = message.payload || {};
const ingested = await ingestTelemetry(String(ws.gatewayId), payload);
const payload = {
...(message.payload || {}),
broker_connection_id: ws.connectionId || null,
broker_agent_instance_id: ws.agentInstanceId || null,
};
let ingested = null;
let ingestError = null;
try {
ingested = await ingestTelemetry(String(ws.gatewayId), payload);
} catch {
ingestError = TELEMETRY_INGEST_ERROR_MESSAGE;
}
const fallbackStatistics = {
system_metrics: payload?.metadata?.system_metrics || {},
container_health: payload?.metadata?.container_health || {},
};
broadcastGatewayEvent(String(ws.gatewayId), {
type: "gateway.telemetry",
gatewayId: String(ws.gatewayId),
telemetry: payload,
gateway: ingested?.gateway || ingested || null,
error: ingestError,
});
broadcastGatewayEvent(String(ws.gatewayId), {
type: "stats.updated",
gatewayId: String(ws.gatewayId),
statistics: ingested?.statistics || ingested || null,
statistics: ingested?.statistics || ingested || fallbackStatistics,
error: ingestError,
});
return;
}
@@ -593,6 +859,11 @@ export function createBrokerServer(options = {}) {
return;
}
if (message.type === "MACHINE_SIGNAL") {
await ingestMachineSignal(String(ws.gatewayId), message.payload || {});
return;
}
if (["SHELL_OUTPUT", "SHELL_OPENED", "SHELL_EXIT"].includes(message.type)) {
const sessionRecord = browserShellSessions.get(String(message.sessionId));
if (!sessionRecord) {
@@ -606,17 +877,35 @@ export function createBrokerServer(options = {}) {
}
if (message.type === "SHELL_OPENED") {
sessionRecord.opened = true;
sessionRecord.agentConnectionId = ws.connectionId || sessionRecord.agentConnectionId || null;
clearShellOpenTimer(sessionRecord);
await markShellSessionOpened(sessionRecord.ws.sessionToken, ws.connectionId || null).catch(() => {});
sendJson(sessionRecord.ws, { type: "opened" });
return;
}
if (message.type === "SHELL_EXIT") {
sendJson(sessionRecord.ws, { type: "closed", code: message.code ?? 0 });
await closeBrowserShellSession(sessionRecord, "agent_exit");
const reason = String(message.reason || "agent_exit");
const parsedExitCode = Number(message.code);
const exitCode = Number.isFinite(parsedExitCode) ? parsedExitCode : 0;
const closeMessage = message.message ? String(message.message) : null;
clearShellOpenTimer(sessionRecord);
sendJson(sessionRecord.ws, {
type: "closed",
reason,
code: exitCode,
...(closeMessage ? { message: closeMessage } : {}),
});
await closeBrowserShellSession(sessionRecord, reason, {
message: closeMessage,
code: exitCode,
stage: sessionRecord.opened ? "shell_active" : "shell_start",
broker_connection_id: ws.connectionId || null,
});
browserShellSessions.delete(String(message.sessionId));
if (sessionRecord.ws.readyState < 2) {
sessionRecord.ws.close();
sessionRecord.ws.close(1000, reason);
}
}
}
@@ -696,7 +985,7 @@ export function createBrokerServer(options = {}) {
}
});
ws.on("close", async (_code, buffer) => {
ws.on("close", async (code, buffer) => {
const closeReason = buffer?.toString?.("utf8") || null;
if (ws.gatewayId) {
@@ -732,7 +1021,11 @@ export function createBrokerServer(options = {}) {
}
const sessionRecord = browserShellSessions.get(sessionId);
if (sessionRecord) {
await closeBrowserShellSession(sessionRecord, sessionRecord.closedReason || "browser_closed");
await closeBrowserShellSession(sessionRecord, sessionRecord.closedReason || "browser_closed", {
code,
stage: sessionRecord.closedStage || "browser_socket_close",
message: sessionRecord.closedMessage || null,
});
browserShellSessions.delete(sessionId);
}
return;
+533 -10
View File
@@ -1,15 +1,10 @@
import test from "node:test";
import assert from "node:assert/strict";
import net from "node:net";
import WebSocket from "ws";
import { createBrokerServer } from "../server.mjs";
function waitForMessage(socket) {
return new Promise((resolve) => {
socket.once("message", (raw) => resolve(JSON.parse(raw.toString())));
});
}
function collectMessages(socket) {
const messages = [];
socket.on("message", (raw) => {
@@ -24,6 +19,44 @@ function waitForClose(socket) {
});
}
function waitForCloseOrError(socket) {
return new Promise((resolve) => {
const onDone = () => {
socket.off("error", onDone);
socket.off("close", onDone);
resolve();
};
socket.once("error", onDone);
socket.once("close", onDone);
});
}
function rawUpgradeRequest(port, path) {
return new Promise((resolve, reject) => {
const socket = net.createConnection({ host: "127.0.0.1", port }, () => {
socket.write(
[
`GET ${path} HTTP/1.1`,
`Host: 127.0.0.1:${port}`,
"Connection: Upgrade",
"Upgrade: websocket",
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==",
"Sec-WebSocket-Version: 13",
"",
"",
].join("\r\n")
);
});
let response = "";
socket.setEncoding("utf8");
socket.on("data", (chunk) => {
response += chunk;
});
socket.on("end", () => resolve(response));
socket.on("error", reject);
});
}
async function waitFor(predicate, { timeoutMs = 1000, intervalMs = 10, description = "condition" } = {}) {
const deadline = Date.now() + timeoutMs;
@@ -38,6 +71,100 @@ async function waitFor(predicate, { timeoutMs = 1000, intervalMs = 10, descripti
throw new Error(`Timed out waiting for ${description}`);
}
test("broker defaults to strict auth and fails closed when manager URL is missing", async () => {
const previousEnv = {
EDGE_AUTH_MODE: process.env.EDGE_AUTH_MODE,
EDGE_MANAGER_URL: process.env.EDGE_MANAGER_URL,
EDGE_PUBLIC_API_URL: process.env.EDGE_PUBLIC_API_URL,
};
delete process.env.EDGE_AUTH_MODE;
delete process.env.EDGE_MANAGER_URL;
delete process.env.EDGE_PUBLIC_API_URL;
let broker;
try {
broker = createBrokerServer({ sharedSecret: "secret" });
assert.equal(broker.state.authMode, "strict");
assert.equal(broker.state.managerUrl, "");
const address = await broker.listen(0);
const port = address.port;
const shellResponse = await rawUpgradeRequest(port, "/ws/browser-shell?token=session-token");
const agentResponse = await rawUpgradeRequest(port, "/ws/agent?gatewayId=701&token=agent-token");
assert.doesNotMatch(shellResponse, /101 Switching Protocols/);
assert.match(shellResponse, /^HTTP\/1\.1 401 Unauthorized/m);
assert.match(shellResponse, /"error_code":"shell_session_invalid"/);
assert.match(shellResponse, /"message":"Shell session could not be validated\."/);
assert.doesNotMatch(shellResponse, /Edge manager URL is not configured/);
assert.doesNotMatch(agentResponse, /101 Switching Protocols/);
assert.match(agentResponse, /^HTTP\/1\.1 503 Service Unavailable/m);
assert.match(agentResponse, /"error_code":"agent_validation_failed"/);
assert.match(agentResponse, /"stage":"agent_validate"/);
assert.match(agentResponse, /"message":"Gateway agent could not be validated\."/);
assert.doesNotMatch(agentResponse, /Edge manager URL is not configured/);
} finally {
if (broker) {
await broker.close();
}
for (const [key, value] of Object.entries(previousEnv)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
});
test("broker rejects protected HTTP endpoints when shared secret is missing", async () => {
const broker = createBrokerServer({ authMode: "stub", sharedSecret: "", commandTimeoutMs: 2000 });
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
await new Promise((resolve) => agent.once("open", resolve));
const agentMessages = collectMessages(agent);
const commandResponse = await fetch(`http://127.0.0.1:${port}/api/gateways/701/commands`, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
commandType: "SET_RELAY_STATE",
payload: { relayId: "M-7", on: true },
}),
});
const commandJson = await commandResponse.json();
assert.equal(commandResponse.status, 503);
assert.equal(commandJson.ok, false);
assert.equal(commandJson.shared_secret_required, true);
assert.match(commandJson.error, /shared secret is not configured/);
assert.equal(agentMessages.some((message) => message.type === "COMMAND"), false);
const diagnosticsResponse = await fetch(`http://127.0.0.1:${port}/api/diagnostics/shared-secret`, {
method: "POST",
});
const diagnosticsJson = await diagnosticsResponse.json();
assert.equal(diagnosticsResponse.status, 503);
assert.equal(diagnosticsJson.shared_secret_required, true);
const syncResponse = await fetch(`http://127.0.0.1:${port}/api/gateways/701/sync`, {
method: "POST",
});
const syncJson = await syncResponse.json();
assert.equal(syncResponse.status, 503);
assert.equal(syncJson.shared_secret_required, true);
agent.terminate();
await broker.close();
});
test("broker dispatches commands to connected agents", async () => {
const broker = createBrokerServer({ authMode: "stub", sharedSecret: "secret", commandTimeoutMs: 2000 });
const address = await broker.listen(0);
@@ -78,6 +205,48 @@ test("broker dispatches commands to connected agents", async () => {
await broker.close();
});
test("broker exposes health and shared-secret diagnostics", async () => {
const broker = createBrokerServer({ authMode: "manager", sharedSecret: "secret", managerUrl: "http://manager.test" });
const address = await broker.listen(0);
const port = address.port;
const healthResponse = await fetch(`http://127.0.0.1:${port}/api/health`);
const healthJson = await healthResponse.json();
assert.equal(healthResponse.status, 200);
assert.equal(healthJson.ok, true);
assert.equal(healthJson.service, "edge-broker");
assert.equal(healthJson.auth_mode, "manager");
assert.equal(healthJson.manager_url_configured, true);
assert.equal(healthJson.shared_secret_configured, true);
const invalidSecretResponse = await fetch(`http://127.0.0.1:${port}/api/diagnostics/shared-secret`, {
method: "POST",
headers: {
"x-edge-broker-secret": "wrong-secret",
},
});
const invalidSecretJson = await invalidSecretResponse.json();
assert.equal(invalidSecretResponse.status, 403);
assert.equal(invalidSecretJson.ok, false);
assert.equal(invalidSecretJson.shared_secret_required, true);
const validSecretResponse = await fetch(`http://127.0.0.1:${port}/api/diagnostics/shared-secret`, {
method: "POST",
headers: {
"x-edge-broker-secret": "secret",
},
});
const validSecretJson = await validSecretResponse.json();
assert.equal(validSecretResponse.status, 200);
assert.equal(validSecretJson.ok, true);
assert.equal(validSecretJson.shared_secret_required, true);
await broker.close();
});
test("broker bridges browser shell sessions through the connected agent", async () => {
const closedSessions = [];
const broker = createBrokerServer({
@@ -109,7 +278,7 @@ test("broker bridges browser shell sessions through the connected agent", async
assert.ok(browserMessages.some((message) => message.type === "opened"));
assert.ok(browserMessages.some((message) => message.type === "output" && /root@pi/.test(message.data)));
assert.ok(browserMessages.some((message) => message.type === "closed" && message.code === 0));
assert.ok(browserMessages.some((message) => message.type === "closed" && message.reason === "agent_exit" && message.code === 0));
assert.equal(closedSessions.length, 1);
assert.equal(closedSessions[0].reason, "agent_exit");
@@ -132,7 +301,10 @@ test("broker forwards browser shell input, resize, and close events to the agent
const agentMessages = collectMessages(agent);
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-shell?token=session-token`);
await new Promise((resolve) => browser.once("open", resolve));
await waitForMessage(agent);
await waitFor(
() => agentMessages.some((message) => message.type === "OPEN_ROOT_SHELL" && message.payload.sessionId === "shell-2"),
{ description: "agent shell open request" }
);
browser.send(JSON.stringify({ type: "input", data: "ls\r" }));
browser.send(JSON.stringify({ type: "resize", cols: 140, rows: 44 }));
@@ -177,15 +349,124 @@ test("broker closes browser shell sessions immediately when no agent is connecte
const port = address.port;
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-shell?token=session-token`);
const browserMessages = collectMessages(browser);
await new Promise((resolve) => browser.once("open", resolve));
await waitForClose(browser);
await waitFor(() => closedSessions.length === 1, { description: "offline shell session close callback" });
assert.ok(
browserMessages.some(
(message) =>
message.type === "closed" &&
message.reason === "agent_offline" &&
/not connected to the broker/i.test(message.message)
)
);
assert.deepEqual(closedSessions, [{ transcript: "", reason: "agent_offline" }]);
await broker.close();
});
test("broker rejects browser shell upgrades without a token using HTTP diagnostics", async () => {
const broker = createBrokerServer({ authMode: "stub" });
const address = await broker.listen(0);
const port = address.port;
const response = await rawUpgradeRequest(port, "/ws/browser-shell");
assert.match(response, /^HTTP\/1\.1 400 Bad Request/m);
assert.match(response, /"error_code":"shell_session_token_missing"/);
assert.match(response, /"message":"Missing shell session token\."/);
await broker.close();
});
test("broker rejects invalid browser shell upgrades without leaking the token", async () => {
const broker = createBrokerServer({
authMode: "stub",
validateShellSession: async () => {
const error = new Error("Shell session expired");
error.status = 401;
error.code = "shell_session_expired";
throw error;
},
});
const address = await broker.listen(0);
const port = address.port;
const rawToken = "session-token-secret";
const response = await rawUpgradeRequest(port, `/ws/browser-shell?token=${rawToken}`);
assert.match(response, /^HTTP\/1\.1 401 Unauthorized/m);
assert.match(response, /"error_code":"shell_session_expired"/);
assert.match(response, /"message":"Shell session could not be validated\."/);
assert.doesNotMatch(response, /Shell session expired/);
assert.doesNotMatch(response, new RegExp(rawToken));
await broker.close();
});
test("broker rejects websocket upgrade errors without exposing exception text", async () => {
const broker = createBrokerServer({
authMode: "stub",
validateBrowserStream: async () => {
throw new Error("UPSTREAM-SENSITIVE: redis://cache.internal:6379 timeout");
},
});
const address = await broker.listen(0);
const port = address.port;
const response = await rawUpgradeRequest(port, "/ws/browser-gateway-stream?token=session-token");
assert.match(response, /^HTTP\/1\.1 500 Internal Server Error/m);
assert.match(response, /"error_code":"websocket_upgrade_failed"/);
assert.match(response, /"message":"WebSocket upgrade failed\."/);
assert.doesNotMatch(response, /UPSTREAM-SENSITIVE/);
assert.doesNotMatch(response, /redis:\/\/cache\.internal/);
await broker.close();
});
test("broker closes browser shell sessions when the agent never reports shell opened", async () => {
const closedSessions = [];
const broker = createBrokerServer({
authMode: "stub",
shellOpenTimeoutMs: 30,
validateShellSession: async () => ({ id: "shell-timeout", gateway_id: "701", reason: "diagnostic" }),
closeShellSession: async (_id, _token, transcript, reason, details) => {
closedSessions.push({ transcript, reason, details });
},
});
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
const agentMessages = collectMessages(agent);
await new Promise((resolve) => agent.once("open", resolve));
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-shell?token=session-token`);
const browserMessages = collectMessages(browser);
await new Promise((resolve) => browser.once("open", resolve));
await waitFor(
() =>
agentMessages.some(
(message) => message.type === "OPEN_ROOT_SHELL" && message.payload.sessionId === "shell-timeout"
),
{ description: "agent shell open request before timeout" }
);
await waitForClose(browser);
await waitFor(() => closedSessions.length === 1, { description: "timeout shell session close callback" });
assert.ok(browserMessages.some((message) => message.type === "closed" && message.reason === "shell_open_timeout"));
assert.equal(closedSessions[0].reason, "shell_open_timeout");
assert.equal(closedSessions[0].details.stage, "shell_open");
assert.equal(closedSessions[0].details.code, 1011);
agent.terminate();
await broker.close();
});
test("broker closes browser shell sessions when the agent disconnects before shell open", async () => {
const closedSessions = [];
const broker = createBrokerServer({
@@ -203,19 +484,148 @@ test("broker closes browser shell sessions when the agent disconnects before she
const agentMessages = collectMessages(agent);
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-shell?token=session-token`);
const browserMessages = collectMessages(browser);
await new Promise((resolve) => browser.once("open", resolve));
await waitForMessage(agent);
await waitFor(
() => agentMessages.some((message) => message.type === "OPEN_ROOT_SHELL" && message.payload.sessionId === "shell-4"),
{ description: "agent shell open request before disconnect" }
);
agent.terminate();
await waitForClose(browser);
await waitFor(() => closedSessions.length === 1, { description: "disconnect shell session close callback" });
assert.ok(agentMessages.some((message) => message.type === "OPEN_ROOT_SHELL" && message.payload.sessionId === "shell-4"));
assert.ok(browserMessages.some((message) => message.type === "closed" && message.reason === "agent_disconnected"));
assert.deepEqual(closedSessions, [{ transcript: "", reason: "agent_disconnected" }]);
await broker.close();
});
test("broker defaults to strict auth when no validators are configured", async () => {
const broker = createBrokerServer();
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
await waitForCloseOrError(agent);
await broker.close();
});
test("broker sends an agent welcome before connection progress and backlog dispatch", async () => {
const broker = createBrokerServer({
authMode: "stub",
reportGatewayPresence: async () => ({}),
requestGatewayBacklog: async () => ({
dispatch: [
{
type: "TASK_DISPATCH",
taskType: "OPERATION",
operation: {
id: 91,
type: "DISCOVERY",
request: {},
},
},
],
}),
});
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
const messages = collectMessages(agent);
await new Promise((resolve) => agent.once("open", resolve));
await waitFor(
() => messages.some((message) => message.type === "TASK_DISPATCH" && message.operation?.id === 91),
{ description: "backlog dispatch after connection welcome" }
);
assert.equal(messages[0].type, "WELCOME");
assert.equal(messages[0].gatewayId, "701");
assert.equal(typeof messages[0].connectionId, "string");
assert.ok(messages[0].connectionId.length > 0);
const firstProgressIndex = messages.findIndex((message) => message.type === "CONNECTION_PROGRESS");
const dispatchIndex = messages.findIndex((message) => message.type === "TASK_DISPATCH");
assert.ok(firstProgressIndex > 0);
assert.ok(dispatchIndex > firstProgressIndex);
assert.ok(
messages.some(
(message) =>
message.type === "CONNECTION_PROGRESS" &&
message.stage === "presence" &&
message.status === "started"
)
);
assert.ok(
messages.some(
(message) =>
message.type === "CONNECTION_PROGRESS" &&
message.stage === "backlog" &&
message.status === "started"
)
);
assert.ok(
messages.some(
(message) =>
message.type === "CONNECTION_PROGRESS" &&
message.stage === "backlog" &&
message.status === "succeeded" &&
message.dispatchCount === 1
)
);
agent.terminate();
await broker.close();
});
test("broker sends connection errors to agents after the welcome message", async () => {
const broker = createBrokerServer({
authMode: "stub",
reportGatewayPresence: async () => {
throw new Error("presence callback unavailable");
},
requestGatewayBacklog: async () => {
throw new Error("backlog sync unavailable");
},
});
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
const messages = collectMessages(agent);
await new Promise((resolve) => agent.once("open", resolve));
await waitFor(
() => messages.filter((message) => message.type === "CONNECTION_ERROR").length >= 2,
{ description: "agent connection error notifications" }
);
assert.equal(messages[0].type, "WELCOME");
assert.ok(
messages.some(
(message) =>
message.type === "CONNECTION_ERROR" &&
message.stage === "presence" &&
message.error === "presence callback unavailable"
)
);
assert.ok(
messages.some(
(message) =>
message.type === "CONNECTION_ERROR" &&
message.stage === "backlog" &&
message.error === "backlog sync unavailable"
)
);
agent.terminate();
await broker.close();
});
test("broker syncs queued gateway backlog on agent connect and manual sync", async () => {
const backlogRequests = [];
const broker = createBrokerServer({
@@ -274,6 +684,7 @@ test("broker syncs queued gateway backlog on agent connect and manual sync", asy
});
test("broker fans out telemetry, task, log, and presence updates to browser gateway streams", async () => {
const telemetryPayloads = [];
const broker = createBrokerServer({
authMode: "stub",
validateAgent: async () => ({ id: "701", gateway_id: "701", label: "CPH Edge 01" }),
@@ -282,7 +693,10 @@ test("broker fans out telemetry, task, log, and presence updates to browser gate
gateway_id: "701",
scopes: ["overview", "tasks", "logs", "statistics"],
}),
ingestTelemetry: async (_gatewayId, payload) => ({ gateway: { id: 701, metadata: payload.metadata || {} } }),
ingestTelemetry: async (_gatewayId, payload) => {
telemetryPayloads.push(payload);
return { gateway: { id: 701, metadata: payload.metadata || {} } };
},
ingestTaskEvent: async (_gatewayId, operationId, payload) => ({
id: operationId,
status: "IN_PROGRESS",
@@ -345,12 +759,58 @@ test("broker fans out telemetry, task, log, and presence updates to browser gate
assert.ok(browserMessages.some((message) => message.type === "stats.updated"));
assert.ok(browserMessages.some((message) => message.type === "task.updated" && message.operationId === 41));
assert.ok(browserMessages.some((message) => message.type === "log.append" && /heartbeat/.test(message.entry?.message)));
assert.equal(telemetryPayloads[0]?.broker_connection_id?.length > 0, true);
browser.terminate();
agent.terminate();
await broker.close();
});
test("broker ingests self-serve machine signals from connected agents", async () => {
const machineSignals = [];
const broker = createBrokerServer({
authMode: "stub",
validateAgent: async () => ({ id: "701", gateway_id: "701", label: "CPH Edge 01" }),
ingestMachineSignal: async (gatewayId, payload) => {
machineSignals.push({ gatewayId, payload });
return { recorded: true, lane_id: payload.lane_id };
},
});
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
await new Promise((resolve) => agent.once("open", resolve));
agent.send(
JSON.stringify({
type: "MACHINE_SIGNAL",
payload: {
lane_id: 3,
relay_id: "machine-relay",
component: "input",
channel: 0,
event: "input.toggle_on",
state: true,
},
})
);
await waitFor(() => machineSignals.length === 1, { description: "machine signal ingestion" });
assert.equal(machineSignals[0].gatewayId, "701");
assert.deepEqual(machineSignals[0].payload, {
lane_id: 3,
relay_id: "machine-relay",
component: "input",
channel: 0,
event: "input.toggle_on",
state: true,
});
agent.terminate();
await broker.close();
});
test("broker survives telemetry ingestion failures for stale gateways", async () => {
const broker = createBrokerServer({
authMode: "stub",
@@ -380,3 +840,66 @@ test("broker survives telemetry ingestion failures for stale gateways", async ()
agent.terminate();
await broker.close();
});
test("broker still fans out telemetry when manager ingestion fails", async () => {
const broker = createBrokerServer({
authMode: "stub",
validateAgent: async () => ({ id: "701", gateway_id: "701", label: "CPH Edge 01" }),
validateBrowserStream: async () => ({
id: "stream-telemetry-fallback",
gateway_id: "701",
scopes: ["overview", "statistics"],
}),
ingestTelemetry: async () => {
throw new Error("manager unavailable");
},
});
const address = await broker.listen(0);
const port = address.port;
const browser = new WebSocket(`ws://127.0.0.1:${port}/ws/browser-gateway-stream?token=stream-token`);
const browserMessages = collectMessages(browser);
await new Promise((resolve) => browser.once("open", resolve));
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
await new Promise((resolve) => agent.once("open", resolve));
agent.send(
JSON.stringify({
type: "TELEMETRY",
payload: {
status: "ONLINE",
metadata: {
system_metrics: {
cpu_usage_pct: 31,
},
container_health: {
state: "ONLINE",
summary: "6/6 containers healthy",
},
},
},
})
);
await waitFor(
() =>
browserMessages.some(
(message) => message.type === "gateway.telemetry" && message.error === "Telemetry ingestion failed"
),
{ description: "telemetry fanout after ingest failure" }
);
assert.ok(
browserMessages.every((message) => message.error !== "manager unavailable"),
"raw manager errors must not be sent to browser streams"
);
assert.ok(
browserMessages.some(
(message) => message.type === "stats.updated" && message.statistics?.system_metrics?.cpu_usage_pct === 31
)
);
browser.terminate();
agent.terminate();
await broker.close();
});
+14 -9
View File
@@ -24,7 +24,7 @@ const traefikSource = [
function readComposeServiceBlock(composeSource, serviceName) {
const escapedServiceName = serviceName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const servicePattern = new RegExp(
`^\\s{2}${escapedServiceName}:\\n([\\s\\S]*?)(?=^\\s{2}[A-Za-z0-9_-]+:|^volumes:|^networks:|\\Z)`,
`^[ ]{2}${escapedServiceName}:\\r?\\n([\\s\\S]*?)(?=^[ ]{2}[A-Za-z0-9_-]+:|^volumes:|^networks:|(?![\\s\\S]))`,
"m"
);
const match = composeSource.match(servicePattern);
@@ -39,11 +39,13 @@ test("traefik does not expose a dedicated public edge broker port", () => {
test("base docker compose routes edge broker traffic through traefik", () => {
const serviceBlock = readComposeServiceBlock(baseComposeSource, "edge-broker");
assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\{EDGE_MANAGER_URL:-http:\/\/caddy\}/);
assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-strict\x7d/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.priority=200/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.priority=200/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.truckwash\.dk`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-io\.rule=Host\(`api\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-v2\.rule=Host\(`api-v2\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.middlewares\.edge-broker-strip\.stripPrefix\.prefixes=\/edge-broker/);
assert.match(serviceBlock, /traefik\.http\.middlewares\.edge-broker-strip-local\.stripPrefix\.prefixes=\/api\/edge-broker/);
@@ -53,7 +55,8 @@ test("base docker compose routes edge broker traffic through traefik", () => {
test("example docker compose routes edge broker traffic through traefik", () => {
const serviceBlock = readComposeServiceBlock(exampleComposeSource, "edge-broker");
assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\{EDGE_MANAGER_URL:-http:\/\/caddy\}/);
assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-strict\x7d/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.example\.com`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.services\.edge-broker\.loadbalancer\.server\.port=4300/);
@@ -62,19 +65,21 @@ test("example docker compose routes edge broker traffic through traefik", () =>
test("standalone production compose routes edge broker traffic through traefik", () => {
const serviceBlock = readComposeServiceBlock(standaloneProdComposeSource, "edge-broker");
assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\{EDGE_MANAGER_URL:-http:\/\/caddy\}/);
assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-manager\x7d/);
assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.priority=200/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.priority=200/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.truckwash\.dk`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-io\.rule=Host\(`api\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-v2\.rule=Host\(`api-v2\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
assert.match(serviceBlock, /traefik\.http\.services\.edge-broker\.loadbalancer\.server\.port=4300/);
});
test("php services receive broker websocket environment defaults", () => {
test("compose config does not provide insecure broker secret defaults", () => {
for (const composeSource of [baseComposeSource, exampleComposeSource]) {
assert.match(composeSource, /EDGE_BROKER_URL:\s*\$\{EDGE_BROKER_URL:-http:\/\/edge-broker:4300\}/);
assert.match(composeSource, /EDGE_BROKER_SHARED_SECRET:\s*\$\{EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev\}/);
assert.match(composeSource, /EDGE_BROKER_URL:\s*\$\x7bEDGE_BROKER_URL:-http:\/\/edge-broker:4300\x7d/);
assert.match(composeSource, /EDGE_BROKER_SHARED_SECRET:\s*\$\x7bEDGE_BROKER_SHARED_SECRET:\?set EDGE_BROKER_SHARED_SECRET in \.env\x7d/);
}
});
@@ -82,7 +87,7 @@ test("base docker compose wires the broker into each php worker", () => {
for (const serviceName of ["php1", "php2", "php3", "php4", "php5", "php-staging", "php-cron"]) {
const serviceBlock = readComposeServiceBlock(baseComposeSource, serviceName);
assert.match(serviceBlock, /\n\s+depends_on:\s*\n[\s\S]*?\n\s+- edge-broker/);
assert.match(serviceBlock, /EDGE_BROKER_URL:\s*\$\{EDGE_BROKER_URL:-http:\/\/edge-broker:4300\}/);
assert.match(serviceBlock, /EDGE_BROKER_SHARED_SECRET:\s*\$\{EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev\}/);
assert.match(serviceBlock, /EDGE_BROKER_URL:\s*\$\x7bEDGE_BROKER_URL:-http:\/\/edge-broker:4300\x7d/);
assert.match(serviceBlock, /EDGE_BROKER_SHARED_SECRET:\s*\$\x7bEDGE_BROKER_SHARED_SECRET:\?set EDGE_BROKER_SHARED_SECRET in \.env\x7d/);
}
});
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
<?php
namespace classes;
use RuntimeException;
class account_deletion_http_exception extends RuntimeException
{
public function __construct(string $message, public readonly int $status)
{
parent::__construct($message);
}
}
@@ -0,0 +1,127 @@
<?php
namespace classes;
/**
* Explicit account-deletion schema management.
*
* apply() must only be invoked by the dedicated CLI. Web requests and cron jobs
* are deliberately limited to the read-only check().
*/
class account_deletion_schema_bootstrap
{
/** @return array{ready:bool,missing:array<int,string>} */
public static function check(): array
{
global $db;
$missing = [];
foreach (['account_deletion_requests', 'account_deletion_credential_attempts', 'account_deletion_outbox'] as $table) {
$tableSql = $db->escape_string($table);
$result = $db->query("SHOW TABLES LIKE '$tableSql'");
if ($result === false || $result->num_rows === 0) {
$missing[] = 'table:' . $table;
}
}
foreach (['users' => 'deleted_at', 'subusers' => 'deleted_at'] as $table => $column) {
$result = $db->query("SHOW COLUMNS FROM `$table` LIKE '$column'");
if ($result === false || $result->num_rows === 0) {
$missing[] = 'column:' . $table . '.' . $column;
}
}
if (!in_array('table:account_deletion_requests', $missing, true)) {
$result = $db->query("SHOW COLUMNS FROM account_deletion_requests LIKE 'manual_review_required_at'");
if ($result === false || $result->num_rows === 0) {
$missing[] = 'column:account_deletion_requests.manual_review_required_at';
}
}
return ['ready' => $missing === [], 'missing' => $missing];
}
public static function apply(): void
{
if (PHP_SAPI !== 'cli') {
throw new \RuntimeException('Account deletion schema changes are CLI-only.');
}
global $db;
self::execute("CREATE TABLE IF NOT EXISTS account_deletion_requests (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
request_id CHAR(36) NOT NULL,
principal_type VARCHAR(16) NOT NULL,
principal_id BIGINT UNSIGNED NOT NULL,
customer_number_snapshot INT NULL,
active_principal_key VARCHAR(191) NULL,
status VARCHAR(32) NOT NULL DEFAULT 'requested',
policy_version VARCHAR(32) NOT NULL,
retained_data_json LONGTEXT NOT NULL,
request_ip VARCHAR(45) NULL,
request_user_agent VARCHAR(512) NULL,
retry_count INT UNSIGNED NOT NULL DEFAULT 0,
failure_code VARCHAR(191) NULL,
requested_at DATETIME NOT NULL,
processing_at DATETIME NULL,
completed_at DATETIME NULL,
next_attempt_at DATETIME NULL,
manual_review_required_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uniq_account_deletion_request_id (request_id),
UNIQUE KEY uniq_account_deletion_active_principal (active_principal_key),
INDEX idx_account_deletion_worker (status, next_attempt_at, requested_at),
INDEX idx_account_deletion_principal (principal_type, principal_id, requested_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
self::ensureColumn('account_deletion_requests', 'manual_review_required_at', 'DATETIME NULL AFTER `next_attempt_at`');
self::execute("CREATE TABLE IF NOT EXISTS account_deletion_credential_attempts (
throttle_key CHAR(64) NOT NULL,
attempt_count INT UNSIGNED NOT NULL DEFAULT 1,
window_started_at DATETIME NOT NULL,
blocked_until DATETIME NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (throttle_key), INDEX idx_account_deletion_throttle_expiry (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
self::execute("CREATE TABLE IF NOT EXISTS account_deletion_outbox (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
request_id CHAR(36) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload_json LONGTEXT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
attempts INT UNSIGNED NOT NULL DEFAULT 0,
available_at DATETIME NOT NULL,
processing_at DATETIME NULL,
delivered_at DATETIME NULL,
last_error VARCHAR(191) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id), UNIQUE KEY uniq_account_deletion_outbox_event (request_id, event_type),
INDEX idx_account_deletion_outbox_delivery (status, available_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
self::ensureColumn('account_deletion_outbox', 'processing_at', 'DATETIME NULL AFTER `available_at`');
self::ensureColumn('users', 'deleted_at', 'DATETIME NULL AFTER `updated_at`');
self::ensureColumn('subusers', 'deleted_at', 'DATETIME NULL AFTER `suspended_at`');
self::ensureIndex('users', 'idx_users_deleted_at', '`deleted_at`');
self::ensureIndex('subusers', 'idx_subusers_deleted_at', '`deleted_at`');
}
private static function execute(string $sql): void
{
global $db;
if ($db->query($sql) === false) {
throw new \RuntimeException('Account deletion schema operation failed.');
}
}
private static function ensureColumn(string $table, string $column, string $definition): void
{
global $db;
$result = $db->query("SHOW COLUMNS FROM `$table` LIKE '$column'");
if ($result === false) throw new \RuntimeException('Unable to inspect account deletion schema.');
if ($result->num_rows === 0) self::execute("ALTER TABLE `$table` ADD COLUMN `$column` $definition");
}
private static function ensureIndex(string $table, string $index, string $columns): void
{
global $db;
$result = $db->query("SHOW INDEX FROM `$table` WHERE Key_name = '$index'");
if ($result === false) throw new \RuntimeException('Unable to inspect account deletion indexes.');
if ($result->num_rows === 0) self::execute("ALTER TABLE `$table` ADD INDEX `$index` ($columns)");
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,110 @@
<?php
namespace classes;
use RuntimeException;
class application_write_freeze
{
public static function freeze(string $reason, string $owner, int $ttlSeconds = 300): void
{
$payload = [
'reason' => $reason,
'owner' => $owner,
'created_at' => date('c'),
'expires_at' => date('c', time() + max(30, $ttlSeconds)),
];
self::writeState($payload);
}
public static function unfreeze(?string $owner = null): void
{
$state = self::state();
if ($owner !== null && isset($state['owner']) && $state['owner'] !== $owner) {
return;
}
$path = self::statePath();
if (is_file($path)) {
@unlink($path);
}
}
public static function state(): array
{
$path = self::statePath();
if (!is_file($path)) {
return [];
}
$state = json_decode((string)file_get_contents($path), true);
if (!is_array($state)) {
@unlink($path);
return [];
}
$expiresAt = strtotime((string)($state['expires_at'] ?? ''));
if ($expiresAt !== false && $expiresAt < time()) {
@unlink($path);
return [];
}
return $state;
}
public static function isFrozen(): bool
{
return self::state() !== [];
}
public static function shouldBlock(string $method, string $uri, bool $isCronOrCli): bool
{
if (!self::isFrozen()) {
return false;
}
if ($isCronOrCli) {
return true;
}
$method = strtoupper($method);
if (in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
return false;
}
$path = parse_url($uri, PHP_URL_PATH) ?: '';
return !str_starts_with($path, '/superuser/replication');
}
private static function writeState(array $state): void
{
$path = self::statePath();
$dir = dirname($path);
if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) {
throw new RuntimeException('Could not create write-freeze directory.');
}
$tempPath = tempnam($dir, 'write-freeze-');
if ($tempPath === false) {
throw new RuntimeException('Could not create write-freeze temp file.');
}
try {
file_put_contents($tempPath, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL, LOCK_EX);
if (!rename($tempPath, $path)) {
throw new RuntimeException('Could not atomically replace write-freeze state.');
}
} finally {
if (is_file($tempPath)) {
@unlink($tempPath);
}
}
}
private static function statePath(): string
{
$root = defined('WD') ? WD : dirname(__DIR__);
return $root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'application-write-freeze.json';
}
}
@@ -37,8 +37,21 @@ class attachment_store implements minio_uploads_i
*/
public function isValidFilePath(string $filePath): bool
{
// Check if the file path is valid
return preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $filePath) === 1;
if (
$filePath === ''
|| str_starts_with($filePath, '/')
|| preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $filePath) !== 1
) {
return false;
}
foreach (explode('/', $filePath) as $segment) {
if ($segment === '' || $segment === '.' || $segment === '..') {
return false;
}
}
return true;
}
/**
@@ -82,7 +95,10 @@ class attachment_store implements minio_uploads_i
{
$host = 'https://api.truckwash.io';
$this->requireValidFilePath($fileName);
$encodedPath = implode('/', array_map('rawurlencode', explode('/', $fileName)));
// Generate a direct download URL for the given file name
return $host . '/files/' . $fileName;
return $host . '/files/' . $encodedPath;
}
}
}
+7 -1
View File
@@ -133,8 +133,14 @@ class attachments implements attachments_i
protected function fetchAttachmentRows(string $type, array $object_ids, array $options = []): array
{
$options = $this->normalizeAttachmentOptions($options);
$rawType = trim($type, '`');
$objectTypes = array_values(array_unique([
$rawType,
'`' . $rawType . '`',
]));
return (new object_attachments_o())->getFieldsWhereIn([
'object_type' => $type,
'object_type' => $objectTypes,
'object_id' => $object_ids,
'deleted_at' => null
], $options);
+55 -13
View File
@@ -2,10 +2,13 @@
namespace classes;
require_once WD . '/classes/account_deletion_service.php';
use classes\totp;
use Exception;
use interfaces\authentication_i;
use objects\plate_scanners_o;
use objects\subuser_grants_o;
use objects\tokens_o;
use objects\users_o;
use objects\subusers_o;
@@ -68,6 +71,10 @@ class authentication implements authentication_i
public function create_2fa_token(int $id, string $type): string
{
$principalType = $type === '2FA_VERIFICATION_SUBUSER' ? 'subuser' : 'customer';
if (account_deletion_service::principalIsBlocked($principalType, $id)) {
throw new Exception('Account unavailable');
}
// Create a temporary 2FA token
$token = bin2hex(random_bytes(32));
(new tokens_o())->create($id, $token, $type);
@@ -99,6 +106,9 @@ class authentication implements authentication_i
throw new \Exception('User not found for customer number: ' . $customer_number);
}
$user_id = $user->id;
if (account_deletion_service::principalIsBlocked('customer', (int)$user_id)) {
throw new Exception('Account unavailable');
}
// Save the token in the database
(new tokens_o())->create($user_id, $token, 'AUTH_TOKEN');
return $token;
@@ -106,6 +116,9 @@ class authentication implements authentication_i
public function create_token_by_user_id(int $user_id): string
{
if (account_deletion_service::principalIsBlocked('customer', $user_id)) {
throw new Exception('Account unavailable');
}
// Create a token
$token = bin2hex(random_bytes(32));
// Save the token in the database
@@ -115,6 +128,9 @@ class authentication implements authentication_i
public function create_employee_token(int $employee_id): string
{
if (account_deletion_service::principalIsBlocked('customer', $employee_id)) {
throw new Exception('Account unavailable');
}
// Create a token
$token = bin2hex(random_bytes(32));
// Save the token in the database
@@ -122,17 +138,34 @@ class authentication implements authentication_i
return $token;
}
public function create_impersonation_token(int $target_user_id, int $actor_user_id): string
{
if ($actor_user_id <= 0 || account_deletion_service::principalIsBlocked('customer', $target_user_id)) {
throw new Exception('Account unavailable');
}
$token = bin2hex(random_bytes(32));
(new tokens_o())->create($target_user_id, $token, 'AUTH_TOKEN_IMPERSONATION:' . $actor_user_id);
return $token;
}
public function validate_token(string $token): bool
{
// First: try validating as a classic user auth token
$dbToken = (new tokens_o())->getToken($token);
if ($dbToken && $dbToken->id) {
return true;
try {
$dbToken = (new tokens_o())->getToken($token);
if ($dbToken && $dbToken->id && $this->isClassicAuthTokenType((string)$dbToken->type->value())) {
return !account_deletion_service::principalIsBlocked(
'customer',
(int)$dbToken->user_id->value()
);
}
} catch (Exception) {
// Ignore and continue to subuser session validation
}
// Fallback: try validating as a subuser session token
$subuser = (new subusers_o())->getSubuserBySessionToken($token);
if ($subuser !== null) {
return true;
return !account_deletion_service::principalIsBlocked('subuser', (int)$subuser->id);
}
return false;
}
@@ -154,19 +187,20 @@ class authentication implements authentication_i
// Strip the Bearer prefix
$rawToken = str_replace('Bearer ', '', $rawToken);
// Get the token from the database
$token = (new tokens_o())->getToken($rawToken);
try {
$token = (new tokens_o())->getToken($rawToken);
} catch (Exception) {
return false;
}
// Check if the token exists
if (!$token->id) {
return false;
}
if ($token->type->value() === "AUTH_TOKEN_SUBUSER") {
// Get the customer number from the headers
if (!isset($headers['X-Customer-Number'])) {
return false;
}
$customer_number = (int)$headers['X-Customer-Number'];
// Get the user by the customer number
return (new users_o())->getUserByCustomerNumber($customer_number);
if (!$this->isClassicAuthTokenType((string)$token->type->value())) {
return false;
}
if (account_deletion_service::principalIsBlocked('customer', (int)$token->user_id->value())) {
return false;
}
// Get the user from the database
$user = (new users_o())->getUserById($token->user_id->value());
@@ -174,6 +208,11 @@ class authentication implements authentication_i
return $user;
}
private function isClassicAuthTokenType(string $type): bool
{
return $type === 'AUTH_TOKEN' || str_starts_with($type, 'AUTH_TOKEN_IMPERSONATION:');
}
public function get_plate_scanner(): plate_scanners_o|false
{
// Get the token from the headers
@@ -224,6 +263,9 @@ class authentication implements authentication_i
if ($subuser === null) {
return false;
}
if (account_deletion_service::principalIsBlocked('subuser', (int)$subuser->id)) {
return false;
}
$customerNumberContext = null;
if (isset($headers['X-Customer-Number'])) {
$customerNumberContext = (int)$headers['X-Customer-Number'];
@@ -0,0 +1,127 @@
<?php
namespace classes;
class backup_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
$db->query(
"CREATE TABLE IF NOT EXISTS backup_records (
backup_uuid VARCHAR(64) NOT NULL PRIMARY KEY,
name VARCHAR(191) NOT NULL,
description TEXT NULL,
source VARCHAR(32) NOT NULL DEFAULT 'manual',
status VARCHAR(32) NOT NULL DEFAULT 'queued',
schema_version INT UNSIGNED NOT NULL DEFAULT 2,
storage_bucket VARCHAR(191) NOT NULL DEFAULT 'backups',
storage_prefix VARCHAR(255) NOT NULL,
manifest_key VARCHAR(255) NULL,
manifest_sha256 CHAR(64) NULL,
encryption_key_id VARCHAR(191) NULL,
component_count INT UNSIGNED NOT NULL DEFAULT 0,
object_count INT UNSIGNED NOT NULL DEFAULT 0,
total_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
requested_by_user_id INT NULL,
started_at DATETIME NULL,
completed_at DATETIME NULL,
verified_at DATETIME NULL,
expires_at DATETIME NULL,
last_error TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_backup_records_status_created (status, created_at),
KEY idx_backup_records_verified (verified_at),
KEY idx_backup_records_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$db->query(
"CREATE TABLE IF NOT EXISTS backup_components (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
backup_uuid VARCHAR(64) NOT NULL,
component_type VARCHAR(32) NOT NULL,
logical_name VARCHAR(191) NOT NULL,
source_bucket VARCHAR(191) NULL,
source_prefix VARCHAR(255) NULL,
storage_key VARCHAR(255) NULL,
manifest_key VARCHAR(255) NULL,
object_count INT UNSIGNED NOT NULL DEFAULT 0,
byte_size BIGINT UNSIGNED NOT NULL DEFAULT 0,
content_sha256 CHAR(64) NULL,
encrypted_sha256 CHAR(64) NULL,
encryption_key_id VARCHAR(191) NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_backup_components_backup (backup_uuid),
KEY idx_backup_components_status (status),
KEY idx_backup_components_type_name (component_type, logical_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$db->query(
"CREATE TABLE IF NOT EXISTS backup_jobs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
job_type VARCHAR(32) NOT NULL,
backup_uuid VARCHAR(64) NULL,
status VARCHAR(32) NOT NULL DEFAULT 'queued',
progress_percent TINYINT UNSIGNED NOT NULL DEFAULT 0,
progress_message VARCHAR(255) NULL,
payload_json LONGTEXT NULL,
result_json LONGTEXT NULL,
actor_user_id INT NULL,
locked_at DATETIME NULL,
lock_owner VARCHAR(191) NULL,
started_at DATETIME NULL,
completed_at DATETIME NULL,
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_backup_jobs_status_created (status, created_at),
KEY idx_backup_jobs_backup (backup_uuid),
KEY idx_backup_jobs_type_status (job_type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$db->query(
"CREATE TABLE IF NOT EXISTS backup_restore_audit (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
restore_job_id BIGINT UNSIGNED NULL,
preview_job_id BIGINT UNSIGNED NULL,
backup_uuid VARCHAR(64) NOT NULL,
actor_user_id INT NULL,
target_environment VARCHAR(64) NOT NULL DEFAULT 'production',
confirmation_fingerprint CHAR(64) NULL,
reason TEXT NULL,
ip_address VARCHAR(64) NULL,
user_agent VARCHAR(255) NULL,
pre_restore_backup_uuid VARCHAR(64) NULL,
status VARCHAR(32) NOT NULL DEFAULT 'queued',
started_at DATETIME NULL,
completed_at DATETIME NULL,
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_backup_restore_audit_backup (backup_uuid),
KEY idx_backup_restore_audit_job (restore_job_id),
KEY idx_backup_restore_audit_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
self::$initialized = true;
}
}
File diff suppressed because it is too large Load Diff
-13
View File
@@ -212,19 +212,7 @@ class bird implements bird_i
throw new Exception('cURL error: ' . $err);
}
curl_close($ch);
// Debug slack
$data = json_decode($body, true) ?? [];
$resp = $resp === false ? 'cURL error with no response' : $resp;
$slack_debug_message = "*Bird API Request Debug:*"
. "\nEndpoint: $url"
. "\nMethod: $method"
. "\nStatus: $code"
. "\nPayload Keys: " . implode(',', array_keys($data))
. "\nResponse: $resp";
// Send slack notification for every request for easier debugging of issues in production (can be removed later if too noisy)
$slack = new \classes\slack();
$slack->send_message($slack_debug_message);
return [
'status_code' => (int)$code,
'body' => $resp,
@@ -1073,4 +1061,3 @@ class bird implements bird_i
}
}
@@ -9,7 +9,11 @@ use interfaces\shelly_transport_i;
class cloud_shelly_transport implements shelly_transport_i
{
public function __construct(private readonly ?shelly $client = null)
public function __construct(
private readonly ?shelly $client = null,
private readonly bool $logRelaySignals = true,
private readonly ?edge_gateway_manager $manager = null
)
{
}
@@ -30,6 +34,47 @@ class cloud_shelly_transport implements shelly_transport_i
public function sendPostRequest(string $endpoint, array $data, ?int $department_id = null): array|object|null
{
return $this->client()->sendPostRequest($endpoint, $data);
try {
$response = $this->client()->sendPostRequest($endpoint, $data);
$this->logRelaySignal($endpoint, $data, $department_id, $response, null);
return $response;
} catch (\Throwable $exception) {
$this->logRelaySignal($endpoint, $data, $department_id, null, $exception);
throw $exception;
}
}
private function logRelaySignal(
string $endpoint,
array $data,
?int $department_id,
array|object|null $response,
?\Throwable $exception
): void {
if (!$this->logRelaySignals || $department_id === null || $department_id <= 0 || !$this->isRelayEndpoint($endpoint)) {
return;
}
try {
$this->manager()->appendRelayTransportLog(
$department_id,
$endpoint,
$data,
$response,
'cloud',
$exception?->getMessage()
);
} catch (\Throwable) {
}
}
private function isRelayEndpoint(string $endpoint): bool
{
return in_array($endpoint, ['/v2/devices/api/get', '/v2/devices/api/set/switch'], true);
}
private function manager(): edge_gateway_manager
{
return $this->manager ?? new edge_gateway_manager();
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace classes;
require_once WD . '/interfaces/universal_module_i.php';
require_once WD . '/modules/coolify/coolify_c.php';
use Exception;
use interfaces\universal_module_i;
use modules\coolify\coolify_c;
class coolify implements universal_module_i
{
public coolify_c $config;
public function __construct()
{
$this->config = new coolify_c();
}
/**
* @throws Exception
*/
public function requireModuleEnabled(): void
{
if (!$this->config->enabled->isTrue()) {
throw new Exception('The Coolify module is not enabled');
}
}
public function isEnabled(): bool
{
try {
return $this->config->enabled->isTrue();
} catch (Exception) {
return false;
}
}
}
@@ -0,0 +1,294 @@
<?php
namespace classes;
use RuntimeException;
class coolify_api_client
{
private string $baseUrl;
private string $token;
private int $timeoutSeconds;
public function __construct(string $baseUrl, string $token, int $timeoutSeconds = 4)
{
$this->baseUrl = self::normalizeBaseUrl($baseUrl);
$this->token = trim($token);
$this->timeoutSeconds = max(1, $timeoutSeconds);
if ($this->baseUrl === '' || $this->token === '') {
throw new RuntimeException('Coolify base URL and API token are required.');
}
}
public static function normalizeBaseUrl(string $baseUrl): string
{
$baseUrl = rtrim(trim($baseUrl), '/');
if ($baseUrl === '') {
return '';
}
if (preg_match('#/api/v[0-9]+$#i', $baseUrl) === 1) {
return $baseUrl;
}
return $baseUrl . '/api/v1';
}
public function healthcheck(): array
{
return $this->request('GET', '/health', null, false);
}
public function version(): array
{
return $this->request('GET', '/version');
}
public function listServers(): array
{
return $this->request('GET', '/servers');
}
public function listProjects(): array
{
return $this->request('GET', '/projects');
}
public function listProjectEnvironments(string $projectUuid): array
{
return $this->request('GET', '/projects/' . rawurlencode($projectUuid) . '/environments');
}
public function listServices(): array
{
return $this->request('GET', '/services');
}
public function listApplications(): array
{
return $this->request('GET', '/applications');
}
public function listGithubApps(): array
{
return $this->request('GET', '/github-apps');
}
public function getService(string $uuid): array
{
return $this->request('GET', '/services/' . rawurlencode($uuid));
}
public function createService(array $payload): array
{
return $this->request('POST', '/services', $payload);
}
public function createPrivateGithubAppApplication(array $payload): array
{
return $this->request('POST', '/applications/private-github-app', $payload);
}
public function getApplication(string $uuid): array
{
return $this->request('GET', '/applications/' . rawurlencode($uuid));
}
public function updateApplication(string $uuid, array $payload): array
{
return $this->request('PATCH', '/applications/' . rawurlencode($uuid), $payload);
}
public function updateService(string $uuid, array $payload): array
{
return $this->request('PATCH', '/services/' . rawurlencode($uuid), $payload);
}
public function updateServiceEnvsBulk(string $uuid, array $env): array
{
if ($env === []) {
return [];
}
return $this->request('PATCH', '/services/' . rawurlencode($uuid) . '/envs/bulk', [
'data' => self::bulkEnvData($env),
]);
}
public function updateApplicationEnvsBulk(string $uuid, array $env): array
{
if ($env === []) {
return [];
}
return $this->request('PATCH', '/applications/' . rawurlencode($uuid) . '/envs/bulk', [
'data' => self::bulkEnvData($env),
]);
}
public function listApplicationEnvs(string $uuid): array
{
return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/envs');
}
public function deleteApplicationEnv(string $uuid, string $envUuid): array
{
return $this->request('DELETE', '/applications/' . rawurlencode($uuid) . '/envs/' . rawurlencode($envUuid));
}
private static function bulkEnvData(array $env): array
{
$data = [];
foreach ($env as $key => $value) {
$data[] = [
'key' => (string)$key,
'value' => (string)$value,
'is_preview' => false,
'is_literal' => true,
'is_multiline' => str_contains((string)$value, "\n"),
'is_shown_once' => false,
];
}
return $data;
}
public function deployResource(string $uuid, bool $force = false): array
{
$path = '/deploy?uuid=' . rawurlencode($uuid) . '&force=' . ($force ? 'true' : 'false');
return $this->request('GET', $path);
}
public function startService(string $uuid): array
{
return $this->request('GET', '/services/' . rawurlencode($uuid) . '/start');
}
public function restartService(string $uuid): array
{
return $this->request('GET', '/services/' . rawurlencode($uuid) . '/restart');
}
public function restartApplication(string $uuid): array
{
return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/restart');
}
public function stopService(string $uuid): array
{
return $this->request('GET', '/services/' . rawurlencode($uuid) . '/stop');
}
public function stopApplication(string $uuid): array
{
return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/stop');
}
public function deleteService(string $uuid): array
{
return $this->request('DELETE', '/services/' . rawurlencode($uuid));
}
public function deleteApplication(string $uuid): array
{
return $this->request('DELETE', '/applications/' . rawurlencode($uuid));
}
public function listDeployments(): array
{
return $this->request('GET', '/deployments');
}
protected function request(string $method, string $path, ?array $payload = null, bool $versionedApi = true): array
{
$url = ($versionedApi ? $this->baseUrl : $this->apiRootUrl()) . '/' . ltrim($path, '/');
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException('Could not initialize Coolify API request.');
}
$headers = [
'Accept: application/json',
'Authorization: Bearer ' . $this->token,
];
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, min(2, $this->timeoutSeconds));
curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeoutSeconds);
curl_setopt($curl, CURLOPT_NOSIGNAL, true);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
if ($payload !== null) {
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($body === false) {
throw new RuntimeException('Could not encode Coolify API payload.');
}
$headers[] = 'Content-Type: application/json';
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
$raw = curl_exec($curl);
$error = curl_error($curl);
$status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($raw === false) {
throw new RuntimeException('Coolify API request failed: ' . $error);
}
$decoded = null;
if (trim((string)$raw) !== '') {
$decoded = json_decode((string)$raw, true);
if (!is_array($decoded)) {
$decoded = ['raw' => (string)$raw];
}
}
if ($status < 200 || $status >= 300) {
$message = is_array($decoded)
? (string)($decoded['message'] ?? $decoded['error'] ?? ('HTTP ' . $status))
: ('HTTP ' . $status);
if (is_array($decoded)) {
$details = self::validationErrorSummary($decoded);
if ($details !== '') {
$message .= ': ' . $details;
}
}
throw new RuntimeException('Coolify API request failed: ' . $message);
}
return is_array($decoded) ? $decoded : [];
}
private static function validationErrorSummary(array $decoded): string
{
$errors = $decoded['errors'] ?? $decoded['data']['errors'] ?? null;
if (!is_array($errors)) {
return '';
}
$parts = [];
foreach ($errors as $field => $messages) {
$fieldName = trim((string)$field);
$fieldPrefix = $fieldName !== '' ? $fieldName . ': ' : '';
if (is_array($messages)) {
$messages = implode(', ', array_filter(array_map(static fn(mixed $message): string => trim((string)$message), $messages)));
} else {
$messages = trim((string)$messages);
}
if ($messages !== '') {
$parts[] = $fieldPrefix . $messages;
}
}
return implode('; ', array_slice($parts, 0, 5));
}
private function apiRootUrl(): string
{
return preg_replace('#/v[0-9]+$#i', '', $this->baseUrl) ?: $this->baseUrl;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,243 @@
<?php
namespace classes;
/**
* Additive schema bootstrap for the Coolify infrastructure integration.
*
* The legacy stack has no centralized migration runner, so this class must be
* safe to call from request handlers, cron, and tests.
*/
class coolify_schema_bootstrap
{
private static bool $initialized = false;
private static ?bool $tablesExist = null;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
$queries = [
"CREATE TABLE IF NOT EXISTS coolify_instances (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
label VARCHAR(128) NOT NULL,
base_url VARCHAR(512) NOT NULL,
api_token_secret TEXT NOT NULL,
default_project_uuid VARCHAR(128) NULL,
default_environment_uuid VARCHAR(128) NULL,
default_environment_name VARCHAR(128) NULL,
default_server_uuid VARCHAR(128) NULL,
default_destination_uuid VARCHAR(128) NULL,
status VARCHAR(32) NOT NULL DEFAULT 'unknown',
last_checked_at DATETIME NULL,
last_error TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at DATETIME NULL,
INDEX idx_coolify_instances_status (status),
INDEX idx_coolify_instances_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS coolify_targets (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
instance_id BIGINT UNSIGNED NOT NULL,
replication_host_id BIGINT UNSIGNED NULL,
kind VARCHAR(16) NOT NULL,
label VARCHAR(128) NOT NULL,
role VARCHAR(16) NOT NULL DEFAULT 'replica',
server_uuid VARCHAR(128) NULL,
project_uuid VARCHAR(128) NULL,
environment_uuid VARCHAR(128) NULL,
environment_name VARCHAR(128) NULL,
destination_uuid VARCHAR(128) NULL,
resource_uuid VARCHAR(128) NULL,
resource_type VARCHAR(32) NOT NULL DEFAULT 'service',
resource_name VARCHAR(128) NULL,
deployment_status VARCHAR(32) NOT NULL DEFAULT 'pending',
availability_state VARCHAR(32) NOT NULL DEFAULT 'degraded',
desired_compose_hash CHAR(64) NULL,
last_reconcile_status VARCHAR(32) NULL,
last_reconcile_json LONGTEXT NULL,
last_reconciled_at DATETIME NULL,
options_json LONGTEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at DATETIME NULL,
INDEX idx_coolify_targets_instance (instance_id),
INDEX idx_coolify_targets_replication_host (replication_host_id),
INDEX idx_coolify_targets_kind_status (kind, deployment_status),
INDEX idx_coolify_targets_availability (availability_state),
INDEX idx_coolify_targets_resource_uuid (resource_uuid),
INDEX idx_coolify_targets_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS coolify_operations (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
target_id BIGINT UNSIGNED NULL,
instance_id BIGINT UNSIGNED NULL,
operation VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'running',
guarded TINYINT(1) NOT NULL DEFAULT 1,
message VARCHAR(512) NULL,
error_message TEXT NULL,
context_json LONGTEXT NULL,
actor_user_id INT NULL,
started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
completed_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_coolify_operations_target (target_id),
INDEX idx_coolify_operations_instance (instance_id),
INDEX idx_coolify_operations_status (status),
INDEX idx_coolify_operations_operation (operation)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS coolify_audit_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
target_id BIGINT UNSIGNED NULL,
instance_id BIGINT UNSIGNED NULL,
replication_host_id BIGINT UNSIGNED NULL,
action VARCHAR(64) NOT NULL,
actor_user_id INT NULL,
severity VARCHAR(16) NOT NULL DEFAULT 'info',
context_json LONGTEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_coolify_audit_target (target_id),
INDEX idx_coolify_audit_instance (instance_id),
INDEX idx_coolify_audit_replication_host (replication_host_id),
INDEX idx_coolify_audit_action (action),
INDEX idx_coolify_audit_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS coolify_instance_gateways (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
instance_id BIGINT UNSIGNED NULL,
hostname VARCHAR(255) NOT NULL,
target_ip VARCHAR(64) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
priority INT NOT NULL DEFAULT 100,
health_state VARCHAR(32) NOT NULL DEFAULT 'unknown',
lb_state VARCHAR(32) NOT NULL DEFAULT 'unknown',
last_probe_json LONGTEXT NULL,
last_probed_at DATETIME NULL,
last_reconciled_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at DATETIME NULL,
UNIQUE KEY uniq_coolify_instance_gateways_target_ip (target_ip),
INDEX idx_coolify_instance_gateways_instance (instance_id),
INDEX idx_coolify_instance_gateways_enabled (enabled),
INDEX idx_coolify_instance_gateways_lb_state (lb_state),
INDEX idx_coolify_instance_gateways_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($queries as $sql) {
$db->query($sql);
}
self::ensureColumn('coolify_instances', 'default_destination_uuid', 'VARCHAR(128) NULL');
self::ensureColumn('coolify_targets', 'availability_state', "VARCHAR(32) NOT NULL DEFAULT 'degraded'");
self::ensureColumn('coolify_targets', 'desired_compose_hash', 'CHAR(64) NULL');
self::ensureColumn('coolify_targets', 'last_reconcile_json', 'LONGTEXT NULL');
self::ensureColumn('coolify_operations', 'guarded', 'TINYINT(1) NOT NULL DEFAULT 1');
self::ensureColumn('coolify_instance_gateways', 'last_reconciled_at', 'DATETIME NULL');
self::ensureModuleConfigDefault('Coolify', 'enabled', 'false', 'bool');
self::ensureModuleConfigDefault('Coolify', 'lb_automation_enabled', 'false', 'bool');
self::ensureModuleConfigDefault('Coolify', 'lb_automation_mode', 'report_only', 'string');
self::ensureModuleConfigDefault('Coolify', 'hetzner_load_balancer_id', '', 'string');
self::ensureModuleConfigDefault('Coolify', 'hetzner_cloud_api_token', '', 'string');
self::ensureModuleConfigDefault('Coolify', 'public_gateway_host', 'api-v2.truckwash.io', 'string');
self::ensureModuleConfigDefault('Coolify', 'public_gateway_probe_path', '', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_token', '', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_service_uuid', '', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_frontend_repository', 'copenhagentruckwash/pleno-vue', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_backend_repository', 'copenhagentruckwash/api', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_labels', 'self-hosted,Linux,X64,default', 'string');
self::ensureModuleConfigDefault('Coolify', 'github_runner_count_per_repo', '1', 'int');
self::ensureDefaultGateway('node1.truckwash.io', '94.130.142.41', 10);
self::ensureDefaultGateway('node2.truckwash.io', '65.21.214.30', 20);
self::ensureDefaultGateway('node3.truckwash.io', '23.88.23.183', 30);
self::$initialized = true;
self::$tablesExist = true;
}
public static function tablesExist(): bool
{
if (self::$tablesExist !== null) {
return self::$tablesExist;
}
global $db;
foreach (['coolify_instances', 'coolify_targets', 'coolify_operations', 'coolify_audit_logs', 'coolify_instance_gateways'] as $table) {
$tableSql = $db->escape_string($table);
$result = $db->query("SHOW TABLES LIKE '$tableSql'");
if ($result === false || $result->num_rows === 0) {
self::$tablesExist = false;
return false;
}
}
self::$tablesExist = true;
return self::$tablesExist;
}
private static function ensureColumn(string $table, string $column, string $definition): void
{
global $db;
$table = preg_replace('/[^a-zA-Z0-9_]/', '', $table);
$column = preg_replace('/[^a-zA-Z0-9_]/', '', $column);
if ($table === '' || $column === '') {
return;
}
$result = $db->query("SHOW COLUMNS FROM `$table` LIKE '$column'");
if ($result !== false && $result->num_rows > 0) {
return;
}
$db->query("ALTER TABLE `$table` ADD COLUMN `$column` $definition");
}
private static function ensureModuleConfigDefault(string $module, string $variable, string $value, string $type): void
{
global $db;
$moduleSql = $db->escape_string($module);
$variableSql = $db->escape_string($variable);
$result = $db->query("SELECT value FROM module_config WHERE module = '$moduleSql' AND variable = '$variableSql' LIMIT 1");
if ($result !== false && $result->num_rows > 0) {
return;
}
$valueSql = $db->escape_string($value);
$typeSql = $db->escape_string($type);
$db->query("INSERT INTO module_config (module, variable, value, type) VALUES ('$moduleSql', '$variableSql', '$valueSql', '$typeSql')");
}
private static function ensureDefaultGateway(string $hostname, string $targetIp, int $priority): void
{
global $db;
$targetIpSql = $db->escape_string($targetIp);
$result = $db->query("SELECT id FROM coolify_instance_gateways WHERE target_ip = '$targetIpSql' LIMIT 1");
if ($result !== false && $result->num_rows > 0) {
return;
}
$hostnameSql = $db->escape_string($hostname);
$db->query(
"INSERT INTO coolify_instance_gateways (hostname, target_ip, enabled, priority)
VALUES ('$hostnameSql', '$targetIpSql', 1, " . (int)$priority . ")"
);
}
}
+249
View File
@@ -0,0 +1,249 @@
<?php
namespace classes;
class cors_policy
{
public const ALLOWED_HEADERS = 'Content-Type, Authorization, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version, Cache-Control, Pragma, *';
public const ALLOWED_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
public const EXPOSED_HEADERS = 'Server-Timing';
public const MAX_AGE_SECONDS = '86400';
private const REQUIRED_ALLOWED_ORIGINS = [
'https://truckwash.io',
'https://www.truckwash.io',
'https://api.truckwash.io',
'https://api.truckwash.io:4433',
'https://api-v2.truckwash.io',
'https://web.truckwash.dk',
'https://api.truckwash.dk',
'https://truckwash.dk',
'https://www.truckwash.dk',
'https://staging.truckwash.io',
'http://localhost',
'https://localhost',
'http://localhost:4433',
'https://localhost:4433',
'https://twdev.jeppeb.dk',
'http://localhost:5173',
'http://localhost:5174',
'http://127.0.0.1:5173',
'http://127.0.0.1:5174',
'capacitor://localhost',
];
public static function normalizeOrigin(?string $value): string
{
$value = trim((string)$value);
if ($value === '' || $value === '*') {
return $value;
}
if (preg_match('#^[a-z][a-z0-9+.-]*://#i', $value) !== 1) {
return '';
}
$parts = parse_url($value);
if (!is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) {
return '';
}
$scheme = strtolower((string)$parts['scheme']);
if (!in_array($scheme, ['http', 'https', 'capacitor'], true)) {
return '';
}
$host = strtolower((string)$parts['host']);
$port = isset($parts['port']) ? ':' . (int)$parts['port'] : '';
return $scheme . '://' . $host . $port;
}
public static function normalizeRequestOrigin(?string $value): string
{
$value = trim((string)$value);
if ($value === '' || $value === '*') {
return '';
}
$parts = parse_url($value);
if (!is_array($parts)) {
return '';
}
foreach (['user', 'pass', 'path', 'query', 'fragment'] as $disallowedPart) {
if (array_key_exists($disallowedPart, $parts)) {
return '';
}
}
return self::normalizeOrigin($value);
}
/**
* @return array<int,string>
*/
public static function requiredAllowedOrigins(): array
{
return self::REQUIRED_ALLOWED_ORIGINS;
}
/**
* @return array<int,string>
*/
public static function traefikHeadersMiddlewareLabels(string $middlewareName, string $corsConfig = ''): array
{
$middlewareName = trim($middlewareName);
if ($middlewareName === '' || preg_match('/^[a-zA-Z0-9-]+$/', $middlewareName) !== 1) {
return [];
}
$allowedHeaders = array_values(array_filter(
array_map('trim', explode(',', self::ALLOWED_HEADERS)),
static fn(string $header): bool => $header !== '' && $header !== '*'
));
$allowedMethods = array_values(array_filter(array_map('trim', explode(',', self::ALLOWED_METHODS))));
$exposedHeaders = array_values(array_filter(array_map('trim', explode(',', self::EXPOSED_HEADERS))));
$prefix = "traefik.http.middlewares.{$middlewareName}.headers";
$allowedOrigins = self::allowedOrigins($corsConfig);
$originLabel = $allowedOrigins === ['*']
? "{$prefix}.accesscontrolalloworiginlistregex=^(https?://[^/]+|capacitor://[^/]+)$"
: "{$prefix}.accesscontrolalloworiginlist=" . implode(',', $allowedOrigins);
return [
"{$prefix}.accesscontrolallowcredentials=true",
"{$prefix}.accesscontrolallowheaders=" . implode(',', $allowedHeaders),
"{$prefix}.accesscontrolallowmethods=" . implode(',', $allowedMethods),
$originLabel,
"{$prefix}.accesscontrolexposeheaders=" . implode(',', $exposedHeaders),
"{$prefix}.accesscontrolmaxage=" . self::MAX_AGE_SECONDS,
"{$prefix}.addvaryheader=true",
];
}
/**
* @return array<int,string>
*/
public static function allowedOrigins(string $corsConfig): array
{
$origins = [];
foreach (self::splitOrigins($corsConfig) as $configuredOrigin) {
if ($configuredOrigin === '*') {
return ['*'];
}
$origin = self::normalizeOrigin($configuredOrigin);
if ($origin !== '') {
$origins[$origin] = true;
}
}
foreach (self::REQUIRED_ALLOWED_ORIGINS as $requiredOrigin) {
$origin = self::normalizeOrigin($requiredOrigin);
if ($origin !== '') {
$origins[$origin] = true;
}
}
return array_keys($origins);
}
public static function withRequiredOrigins(string $corsConfig): string
{
$allowedOrigins = self::allowedOrigins($corsConfig);
if ($allowedOrigins === ['*']) {
return '*';
}
return implode(',', $allowedOrigins);
}
public static function isOriginAllowed(?string $origin, string $corsConfig): bool
{
$origin = self::normalizeRequestOrigin($origin);
if ($origin === '') {
return false;
}
$allowedOrigins = self::allowedOrigins($corsConfig);
return in_array('*', $allowedOrigins, true) || in_array($origin, $allowedOrigins, true);
}
/**
* @return array<string,string>
*/
public static function responseHeaders(?string $origin, string $corsConfig): array
{
$origin = self::normalizeRequestOrigin($origin);
if ($origin === '' || !self::isOriginAllowed($origin, $corsConfig)) {
return [];
}
return [
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Headers' => self::ALLOWED_HEADERS,
'Access-Control-Allow-Methods' => self::ALLOWED_METHODS,
'Access-Control-Expose-Headers' => self::EXPOSED_HEADERS,
'Access-Control-Max-Age' => self::MAX_AGE_SECONDS,
'Timing-Allow-Origin' => $origin,
'Vary' => 'Origin',
];
}
/**
* @return array{allowed:bool,status:int,headers:array<string,string>,body:string}
*/
public static function preflightResponse(?string $origin, string $corsConfig): array
{
$headers = self::responseHeaders($origin, $corsConfig);
if ($headers === []) {
return [
'allowed' => false,
'status' => 403,
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['success' => false, 'message' => 'CORS origin not allowed']) ?: '',
];
}
$headers['Content-Type'] = 'application/json';
return [
'allowed' => true,
'status' => 200,
'headers' => $headers,
'body' => '',
];
}
public static function applyResponseHeaders(string $corsConfig, ?string $origin = null): bool
{
$headers = self::responseHeaders($origin ?? ($_SERVER['HTTP_ORIGIN'] ?? ''), $corsConfig);
if ($headers === []) {
return false;
}
self::emitHeaders($headers);
return true;
}
/**
* @param array<string,string> $headers
*/
public static function emitHeaders(array $headers): void
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value, strtolower((string)$name) !== 'vary');
}
}
/**
* @return array<int,string>
*/
private static function splitOrigins(string $corsConfig): array
{
return array_values(array_filter(
array_map('trim', explode(',', $corsConfig)),
static fn(string $origin): bool => $origin !== ''
));
}
}
@@ -0,0 +1,58 @@
<?php
namespace classes;
use InvalidArgumentException;
class cron_schedule
{
public static function normalize(array $schedule): array
{
$type = strtolower(trim((string)($schedule['type'] ?? 'interval')));
if ($type !== 'interval') {
throw new InvalidArgumentException('Unsupported cron schedule type: ' . $type);
}
$seconds = (int)($schedule['seconds'] ?? $schedule['interval'] ?? 0);
if ($seconds < 30 || $seconds > 2678400) {
throw new InvalidArgumentException('Cron interval must be between 30 seconds and 31 days.');
}
return [
'type' => 'interval',
'seconds' => $seconds,
];
}
public static function nextRunAt(array $schedule, ?string $anchorDateTime, int $now): string
{
$normalized = self::normalize($schedule);
$anchor = $anchorDateTime !== null && trim($anchorDateTime) !== ''
? strtotime($anchorDateTime)
: false;
$base = $anchor !== false ? (int)$anchor : $now;
$next = $base + (int)$normalized['seconds'];
if ($next <= $now) {
$missed = (int)floor(($now - $next) / (int)$normalized['seconds']) + 1;
$next += $missed * (int)$normalized['seconds'];
}
return date('Y-m-d H:i:s', $next);
}
public static function dueAt(array $schedule, ?string $lastRunAt, int $now, ?int $legacyLastRun = null): string
{
$normalized = self::normalize($schedule);
if ($lastRunAt !== null && trim($lastRunAt) !== '') {
return self::nextRunAt($normalized, $lastRunAt, $now);
}
if ($legacyLastRun !== null && $legacyLastRun > 0) {
return date('Y-m-d H:i:s', $legacyLastRun + (int)$normalized['seconds']);
}
return date('Y-m-d H:i:s', $now);
}
}
@@ -0,0 +1,702 @@
<?php
namespace classes;
use RuntimeException;
use Throwable;
class cron_scheduler
{
private cron_task_registry $registry;
private string $lock_owner;
public function __construct(?cron_task_registry $registry = null)
{
$this->registry = $registry ?? new cron_task_registry();
$this->lock_owner = gethostname() . ':' . getmypid() . ':' . bin2hex(random_bytes(4));
}
public function listTasks(): array
{
$this->ensureReady();
$this->syncDefinitions();
$states = $this->stateRows();
$estimates = $this->durationEstimates();
$tasks = [];
$now = time();
foreach ($this->registry->definitions() as $definition) {
$state = $states[$definition->id] ?? [];
$schedule = is_array($state['schedule'] ?? null) && $state['schedule'] !== []
? $state['schedule']
: $definition->schedule;
$nextRunAt = $state['next_run_at'] ?? null;
if ($nextRunAt === null || trim((string)$nextRunAt) === '') {
$nextRunAt = cron_schedule::dueAt($schedule, $state['last_run_at'] ?? null, $now);
}
$task = $definition->asArray($state + ['next_run_at' => $nextRunAt], $estimates[$definition->id] ?? null);
$task['due'] = strtotime($nextRunAt) !== false && strtotime($nextRunAt) <= $now;
$task['seconds_until_due'] = max(0, (int)strtotime($nextRunAt) - $now);
$tasks[] = $task;
}
return [
'tasks' => $tasks,
'summary' => [
'total' => count($tasks),
'enabled' => count(array_filter($tasks, static fn(array $task): bool => (bool)$task['enabled'])),
'due' => count(array_filter($tasks, static fn(array $task): bool => (bool)$task['due'] && (bool)$task['enabled'])),
],
];
}
public function listRuns(?string $task_id = null, int $limit = 50): array
{
$this->ensureReady();
$limit = max(1, min(200, $limit));
$where = '';
if ($task_id !== null && trim($task_id) !== '') {
$where = "WHERE task_id = " . $this->sql($task_id);
}
return $this->fetchAll(
"SELECT * FROM cron_task_runs $where ORDER BY id DESC LIMIT $limit"
);
}
public function queueTaskRun(string $task_id_or_legacy_name, ?int $actor_user_id = null, bool $force = false): array
{
$this->ensureReady();
$this->syncDefinitions();
$definition = $this->registry->get($task_id_or_legacy_name);
if ($definition === null) {
throw new RuntimeException('Cron task not found.');
}
$state = $this->stateRows()[$definition->id] ?? [];
$enabled = (bool)($state['enabled'] ?? $definition->enabled);
if (!$enabled && !$force) {
throw new RuntimeException('Cron task is disabled.');
}
if ($this->taskIsLocked($state)) {
throw new RuntimeException('Cron task is already running.');
}
$existing = $this->fetchOne(
"SELECT * FROM cron_task_runs
WHERE task_id = " . $this->sql($definition->id) . " AND status = 'queued'
ORDER BY id DESC LIMIT 1"
);
if ($existing !== null) {
$this->markTaskQueued($definition);
return $this->publicRun($existing);
}
$scheduled_for = date('Y-m-d H:i:s');
$this->query(
"INSERT INTO cron_task_runs
(task_id, module, source, status, actor_user_id, scheduled_for, force_run)
VALUES ("
. $this->sql($definition->id) . ', '
. $this->sql($definition->module) . ", 'manual', 'queued', "
. ($actor_user_id === null ? 'NULL' : (string)(int)$actor_user_id) . ', '
. $this->sql($scheduled_for) . ', '
. ($force ? '1' : '0')
. ")"
);
$run_id = (int)$this->insertId();
$this->markTaskQueued($definition);
return $this->publicRun($this->fetchOne("SELECT * FROM cron_task_runs WHERE id = $run_id") ?? []);
}
public function runDue(string $source = 'automatic'): array
{
$this->ensureReady();
$this->syncDefinitions();
$ran = [];
foreach ($this->queuedRuns() as $queuedRun) {
try {
$run = $this->runQueuedRun($queuedRun);
if ($run !== null) {
$ran[] = $run;
}
} catch (Throwable $throwable) {
$ran[] = [
'task_id' => (string)($queuedRun['task_id'] ?? ''),
'module' => (string)($queuedRun['module'] ?? ''),
'source' => (string)($queuedRun['source'] ?? 'manual'),
'status' => 'skipped',
'error_message' => $throwable->getMessage(),
];
}
}
$now = time();
$states = $this->stateRows();
foreach ($this->registry->definitions() as $definition) {
$state = $states[$definition->id] ?? [];
if (!(bool)($state['enabled'] ?? $definition->enabled)) {
continue;
}
$nextRunAt = (string)($state['next_run_at'] ?? '');
if ($nextRunAt === '' || strtotime($nextRunAt) === false || strtotime($nextRunAt) > $now) {
continue;
}
try {
$ran[] = $this->runTask($definition->id, $source, null, false, $nextRunAt);
} catch (Throwable $throwable) {
$ran[] = [
'task_id' => $definition->id,
'module' => $definition->module,
'source' => $source,
'status' => 'skipped',
'error_message' => $throwable->getMessage(),
];
}
}
return [
'ran' => $ran,
'count' => count($ran),
];
}
public function markExpiredRunningRuns(): int
{
$this->ensureReady();
$now = date('Y-m-d H:i:s');
$message = 'Task lock expired before completion.';
$this->query(
"UPDATE cron_task_runs r
INNER JOIN cron_task_state s ON s.task_id = r.task_id AND s.current_run_id = r.id
SET r.status = 'timed_out',
r.completed_at = COALESCE(s.locked_until, " . $this->sql($now) . "),
r.error_message = COALESCE(r.error_message, " . $this->sql($message) . "),
s.current_run_id = NULL,
s.locked_until = NULL,
s.lock_owner = NULL,
s.last_status = 'timed_out',
s.last_error = " . $this->sql($message) . "
WHERE r.status = 'running'
AND s.locked_until IS NOT NULL
AND s.locked_until < " . $this->sql($now)
);
return $this->affectedRows();
}
public function runTask(
string $task_id_or_legacy_name,
string $source = 'manual',
?int $actor_user_id = null,
bool $force = false,
?string $scheduled_for = null
): array {
$this->ensureReady();
$this->syncDefinitions();
$definition = $this->registry->get($task_id_or_legacy_name);
if ($definition === null) {
throw new RuntimeException('Cron task not found.');
}
$state = $this->stateRows()[$definition->id] ?? [];
$enabled = (bool)($state['enabled'] ?? $definition->enabled);
if (!$enabled && !$force) {
throw new RuntimeException('Cron task is disabled.');
}
if (!$this->claimLock($definition)) {
throw new RuntimeException('Cron task is already running.');
}
$started = microtime(true);
$started_at = date('Y-m-d H:i:s', (int)$started);
$run_id = $this->createRun($definition, $source, $actor_user_id, $scheduled_for, $started_at, $force);
$this->query(
"UPDATE cron_task_state SET current_run_id = $run_id WHERE task_id = " . $this->sql($definition->id)
);
return $this->executeClaimedRun($definition, $run_id, $started, $scheduled_for);
}
private function executeClaimedRun(
cron_task_definition $definition,
int $run_id,
float $started,
?string $scheduled_for = null
): array
{
$status = 'succeeded';
$summary = [];
$error_message = null;
$output = '';
try {
if (function_exists('set_time_limit')) {
@set_time_limit($definition->timeout_seconds + 30);
}
$this->ensureLegacyFunctionsLoaded($definition);
if (!is_callable($definition->handler)) {
throw new RuntimeException('Cron task handler is not callable: ' . $definition->handler);
}
ob_start();
$result = call_user_func($definition->handler);
$output = (string)ob_get_clean();
$summary = is_array($result) ? $result : [];
} catch (Throwable $throwable) {
if (ob_get_level() > 0) {
$output .= (string)ob_get_clean();
}
$status = 'failed';
$error_message = $throwable->getMessage();
}
$completed = microtime(true);
$duration_ms = (int)round(($completed - $started) * 1000);
if ($duration_ms > ($definition->timeout_seconds * 1000) && $status === 'succeeded') {
$status = 'timed_out';
$error_message = 'Task exceeded its configured timeout window.';
}
if ($output !== '') {
$summary['output'] = substr($output, 0, 8000);
}
$completed_at = date('Y-m-d H:i:s', (int)$completed);
$this->completeRun($run_id, $status, $completed_at, $duration_ms, $summary, $error_message);
$this->releaseLock($definition, $status, $error_message, $completed_at, $scheduled_for);
return $this->publicRun($this->fetchOne("SELECT * FROM cron_task_runs WHERE id = $run_id") ?? []);
}
public function updateTaskConfig(string $task_id, array $config): array
{
$this->ensureReady();
$this->syncDefinitions();
$definition = $this->registry->get($task_id);
if ($definition === null) {
throw new RuntimeException('Cron task not found.');
}
$updates = [];
if (array_key_exists('enabled', $config)) {
$updates[] = 'enabled = ' . ((bool)$config['enabled'] ? '1' : '0');
}
if (array_key_exists('schedule', $config)) {
$schedule = $config['schedule'] === null ? null : cron_schedule::normalize((array)$config['schedule']);
$updates[] = 'schedule_json = ' . ($schedule === null ? 'NULL' : $this->sql(json_encode($schedule)));
$anchor = (string)($this->fetchOne("SELECT last_run_at FROM cron_task_state WHERE task_id = " . $this->sql($definition->id))['last_run_at'] ?? '');
$updates[] = 'next_run_at = ' . $this->sql(cron_schedule::dueAt($schedule ?? $definition->schedule, $anchor !== '' ? $anchor : null, time()));
}
if ($updates !== []) {
$this->query(
"UPDATE cron_task_state SET " . implode(', ', $updates) . " WHERE task_id = " . $this->sql($definition->id)
);
}
return $this->listTasks();
}
private function ensureReady(): void
{
cron_schema_bootstrap::ensureTables();
}
private function syncDefinitions(): void
{
$now = time();
foreach ($this->registry->definitions() as $definition) {
$row = $this->fetchOne(
"SELECT * FROM cron_task_state WHERE task_id = " . $this->sql($definition->id)
);
if ($row !== null) {
continue;
}
$legacyLastRun = $this->legacyLastRun($definition);
$nextRunAt = cron_schedule::dueAt($definition->schedule, null, $now, $legacyLastRun);
$this->query(
"INSERT INTO cron_task_state (task_id, module, enabled, schedule_json, next_run_at)
VALUES ("
. $this->sql($definition->id) . ', '
. $this->sql($definition->module) . ', '
. ($definition->enabled ? '1' : '0') . ', NULL, '
. $this->sql($nextRunAt)
. ")"
);
}
}
private function legacyLastRun(cron_task_definition $definition): ?int
{
if ($definition->legacy_name === null || !defined('redis')) {
return null;
}
try {
$last_run = redis->get_last_crond_run($definition->legacy_name);
return $last_run !== null ? (int)$last_run : null;
} catch (Throwable) {
return null;
}
}
private function claimLock(cron_task_definition $definition): bool
{
$now = date('Y-m-d H:i:s');
$locked_until = date('Y-m-d H:i:s', time() + $definition->timeout_seconds + 60);
$this->query(
"UPDATE cron_task_state
SET locked_until = " . $this->sql($locked_until) . ",
lock_owner = " . $this->sql($this->lock_owner) . "
WHERE task_id = " . $this->sql($definition->id) . "
AND (locked_until IS NULL OR locked_until < " . $this->sql($now) . ")"
);
return $this->affectedRows() === 1;
}
private function taskIsLocked(array $state): bool
{
$lockedUntil = (string)($state['locked_until'] ?? '');
return $lockedUntil !== ''
&& strtotime($lockedUntil) !== false
&& strtotime($lockedUntil) >= time();
}
private function markTaskQueued(cron_task_definition $definition): void
{
$now = date('Y-m-d H:i:s');
$this->query(
"UPDATE cron_task_state
SET last_status = 'queued',
last_error = NULL,
next_run_at = CASE
WHEN next_run_at IS NULL OR next_run_at > " . $this->sql($now) . " THEN " . $this->sql($now) . "
ELSE next_run_at
END
WHERE task_id = " . $this->sql($definition->id)
);
}
/**
* @return array<int, array<string, mixed>>
*/
private function queuedRuns(): array
{
return $this->fetchAll("SELECT * FROM cron_task_runs WHERE status = 'queued' ORDER BY id ASC LIMIT 50");
}
private function runQueuedRun(array $queuedRun): ?array
{
$run_id = (int)($queuedRun['id'] ?? 0);
$definition = $this->registry->get((string)($queuedRun['task_id'] ?? ''));
if ($run_id < 1 || $definition === null) {
if ($run_id > 0) {
$this->skipQueuedRun($run_id, 'Cron task not found.');
return $this->publicRun($this->fetchOne("SELECT * FROM cron_task_runs WHERE id = $run_id") ?? []);
}
return null;
}
$force = (bool)($queuedRun['force_run'] ?? false);
$state = $this->stateRows()[$definition->id] ?? [];
$enabled = (bool)($state['enabled'] ?? $definition->enabled);
if (!$enabled && !$force) {
$this->skipQueuedRun($run_id, 'Cron task is disabled.', $definition);
return $this->publicRun($this->fetchOne("SELECT * FROM cron_task_runs WHERE id = $run_id") ?? []);
}
if (!$this->claimLock($definition)) {
return null;
}
$started = microtime(true);
$started_at = date('Y-m-d H:i:s', (int)$started);
$this->query(
"UPDATE cron_task_runs
SET status = 'running',
started_at = " . $this->sql($started_at) . ",
lock_owner = " . $this->sql($this->lock_owner) . "
WHERE id = $run_id AND status = 'queued'"
);
if ($this->affectedRows() !== 1) {
$this->clearClaimedLock($definition);
return null;
}
$this->query(
"UPDATE cron_task_state SET current_run_id = $run_id WHERE task_id = " . $this->sql($definition->id)
);
return $this->executeClaimedRun(
$definition,
$run_id,
$started,
isset($queuedRun['scheduled_for']) ? (string)$queuedRun['scheduled_for'] : null
);
}
private function skipQueuedRun(int $run_id, string $message, ?cron_task_definition $definition = null): void
{
$completed_at = date('Y-m-d H:i:s');
$this->query(
"UPDATE cron_task_runs
SET status = 'skipped',
completed_at = " . $this->sql($completed_at) . ",
duration_ms = 0,
error_message = " . $this->sql($message) . "
WHERE id = $run_id AND status = 'queued'"
);
$updated = $this->affectedRows() === 1;
if (!$updated || $definition === null) {
return;
}
$this->query(
"UPDATE cron_task_state
SET last_status = 'skipped',
last_error = " . $this->sql($message) . "
WHERE task_id = " . $this->sql($definition->id)
);
}
private function clearClaimedLock(cron_task_definition $definition): void
{
$this->query(
"UPDATE cron_task_state
SET locked_until = NULL,
lock_owner = NULL
WHERE task_id = " . $this->sql($definition->id) . "
AND lock_owner = " . $this->sql($this->lock_owner)
);
}
private function releaseLock(
cron_task_definition $definition,
string $status,
?string $error_message,
string $completed_at,
?string $scheduled_for = null
): void
{
$state = $this->fetchOne("SELECT schedule_json FROM cron_task_state WHERE task_id = " . $this->sql($definition->id));
$schedule = $this->decodeJson($state['schedule_json'] ?? null);
if ($schedule === []) {
$schedule = $definition->schedule;
}
// Automatic runs stay anchored to their intended schedule slot. Anchoring
// to completion time causes every task to drift by its execution time.
$scheduleAnchor = $scheduled_for !== null && strtotime($scheduled_for) !== false
? $scheduled_for
: $completed_at;
$nextRunAt = cron_schedule::nextRunAt($schedule, $scheduleAnchor, time());
if ($status !== 'succeeded') {
$retrySeconds = min(300, max(60, (int)$schedule['seconds']));
$nextRunAt = date('Y-m-d H:i:s', time() + $retrySeconds);
}
$this->query(
"UPDATE cron_task_state
SET last_run_at = " . $this->sql($completed_at) . ",
next_run_at = " . $this->sql($nextRunAt) . ",
locked_until = NULL,
lock_owner = NULL,
current_run_id = NULL,
last_status = " . $this->sql($status) . ",
last_error = " . $this->nullableSql($error_message) . "
WHERE task_id = " . $this->sql($definition->id) . "
AND lock_owner = " . $this->sql($this->lock_owner)
);
if ($definition->legacy_name !== null && defined('redis')) {
try {
redis->set_last_crond_run($definition->legacy_name, time());
} catch (Throwable) {
}
}
}
private function createRun(
cron_task_definition $definition,
string $source,
?int $actor_user_id,
?string $scheduled_for,
string $started_at,
bool $force = false
): int {
$this->query(
"INSERT INTO cron_task_runs
(task_id, module, source, status, actor_user_id, scheduled_for, started_at, lock_owner, force_run)
VALUES ("
. $this->sql($definition->id) . ', '
. $this->sql($definition->module) . ', '
. $this->sql($source) . ", 'running', "
. ($actor_user_id === null ? 'NULL' : (string)(int)$actor_user_id) . ', '
. $this->nullableSql($scheduled_for) . ', '
. $this->sql($started_at) . ', '
. $this->sql($this->lock_owner) . ', '
. ($force ? '1' : '0')
. ")"
);
return $this->insertId();
}
private function completeRun(
int $run_id,
string $status,
string $completed_at,
int $duration_ms,
array $summary,
?string $error_message
): void {
$this->query(
"UPDATE cron_task_runs
SET status = " . $this->sql($status) . ",
completed_at = " . $this->sql($completed_at) . ",
duration_ms = " . (string)$duration_ms . ",
summary_json = " . $this->sql(json_encode($summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) . ",
error_message = " . $this->nullableSql($error_message) . "
WHERE id = " . (string)$run_id
);
}
private function ensureLegacyFunctionsLoaded(cron_task_definition $definition): void
{
if (function_exists($definition->handler)) {
return;
}
if (!defined('WD')) {
return;
}
if (!defined('CRON_LOAD_LEGACY_FUNCTIONS_ONLY')) {
define('CRON_LOAD_LEGACY_FUNCTIONS_ONLY', true);
}
require_once WD . '/cron/Cron.php';
}
/**
* @return array<string, array<string, mixed>>
*/
private function stateRows(): array
{
$rows = $this->fetchAll("SELECT * FROM cron_task_state");
$states = [];
foreach ($rows as $row) {
$row['enabled'] = (bool)$row['enabled'];
$row['schedule'] = $this->decodeJson($row['schedule_json'] ?? null);
$states[(string)$row['task_id']] = $row;
}
return $states;
}
/**
* @return array<string, int>
*/
private function durationEstimates(): array
{
$rows = $this->fetchAll(
"SELECT task_id, AVG(duration_ms) AS avg_duration_ms
FROM (
SELECT task_id, duration_ms
FROM cron_task_runs
WHERE status = 'succeeded' AND duration_ms IS NOT NULL
ORDER BY id DESC
LIMIT 500
) recent_runs
GROUP BY task_id"
);
$estimates = [];
foreach ($rows as $row) {
$estimates[(string)$row['task_id']] = (int)round((float)$row['avg_duration_ms']);
}
return $estimates;
}
private function decodeJson(mixed $json): array
{
if (!is_string($json) || trim($json) === '') {
return [];
}
$decoded = json_decode($json, true);
return is_array($decoded) ? $decoded : [];
}
private function publicRun(array $run): array
{
if ($run === []) {
return [];
}
$run['force_run'] = (bool)($run['force_run'] ?? false);
$run['summary'] = $this->decodeJson($run['summary_json'] ?? null);
return $run;
}
private function fetchOne(string $sql): ?array
{
$rows = $this->fetchAll($sql);
return $rows[0] ?? null;
}
private function fetchAll(string $sql): array
{
$result = $this->query($sql);
if ($result === false || $result === true) {
return [];
}
return $result->fetch_all(MYSQLI_ASSOC);
}
private function query(string $sql): \mysqli_result|bool
{
global $db;
return $db->query($sql);
}
private function sql(string $value): string
{
global $db;
return "'" . $db->escape_string($value) . "'";
}
private function nullableSql(?string $value): string
{
return $value === null ? 'NULL' : $this->sql($value);
}
private function affectedRows(): int
{
global $db;
return (int)$db->conn()->affected_rows;
}
private function insertId(): int
{
global $db;
return (int)$db->insert_id();
}
}
@@ -0,0 +1,121 @@
<?php
namespace classes;
class cron_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
$db->query(
"CREATE TABLE IF NOT EXISTS cron_task_state (
task_id VARCHAR(191) NOT NULL PRIMARY KEY,
module VARCHAR(64) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
schedule_json LONGTEXT NULL,
last_run_at DATETIME NULL,
next_run_at DATETIME NULL,
locked_until DATETIME NULL,
lock_owner VARCHAR(191) NULL,
current_run_id BIGINT UNSIGNED NULL,
last_status VARCHAR(32) NULL,
last_error TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_cron_task_state_next_run (enabled, next_run_at),
KEY idx_cron_task_state_lock (locked_until)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$db->query(
"CREATE TABLE IF NOT EXISTS cron_task_runs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
task_id VARCHAR(191) NOT NULL,
module VARCHAR(64) NOT NULL,
source VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'running',
actor_user_id INT NULL,
scheduled_for DATETIME NULL,
started_at DATETIME NULL,
completed_at DATETIME NULL,
duration_ms INT UNSIGNED NULL,
summary_json LONGTEXT NULL,
error_message TEXT NULL,
lock_owner VARCHAR(191) NULL,
force_run TINYINT(1) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_cron_task_runs_task_created (task_id, created_at),
KEY idx_cron_task_runs_status_created (status, created_at),
KEY idx_cron_task_runs_module_created (module, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
self::ensureColumn('cron_task_runs', 'force_run', 'TINYINT(1) NOT NULL DEFAULT 0 AFTER lock_owner');
$db->query(
"CREATE TABLE IF NOT EXISTS cron_worker_state (
worker_id VARCHAR(191) NOT NULL PRIMARY KEY,
name VARCHAR(191) NOT NULL,
hostname VARCHAR(191) NULL,
pid INT UNSIGNED NULL,
source VARCHAR(64) NOT NULL DEFAULT 'coolify_worker',
status VARCHAR(32) NOT NULL DEFAULT 'starting',
release_channel_id BIGINT UNSIGNED NULL,
release_target_id BIGINT UNSIGNED NULL,
coolify_resource_uuid VARCHAR(128) NULL,
coolify_resource_type VARCHAR(32) NULL,
commit_sha VARCHAR(64) NULL,
poll_seconds INT UNSIGNED NOT NULL DEFAULT 15,
last_run_count INT UNSIGNED NOT NULL DEFAULT 0,
last_stale_run_count INT UNSIGNED NOT NULL DEFAULT 0,
last_error TEXT NULL,
started_at DATETIME NULL,
last_heartbeat_at DATETIME NULL,
last_loop_started_at DATETIME NULL,
last_loop_finished_at DATETIME NULL,
last_loop_gap_seconds INT UNSIGNED NULL,
consecutive_minute_loops INT UNSIGNED NOT NULL DEFAULT 0,
stopped_at DATETIME NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
KEY idx_cron_worker_state_heartbeat (last_heartbeat_at),
KEY idx_cron_worker_state_status (status),
KEY idx_cron_worker_state_release_target (release_target_id),
KEY idx_cron_worker_state_coolify_resource (coolify_resource_uuid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
self::ensureColumn('cron_worker_state', 'last_loop_gap_seconds', 'INT UNSIGNED NULL AFTER last_loop_finished_at');
self::ensureColumn('cron_worker_state', 'consecutive_minute_loops', 'INT UNSIGNED NOT NULL DEFAULT 0 AFTER last_loop_gap_seconds');
self::$initialized = true;
}
private static function ensureColumn(string $table, string $column, string $definition): void
{
global $db;
$table = preg_replace('/[^a-zA-Z0-9_]/', '', $table);
$column = preg_replace('/[^a-zA-Z0-9_]/', '', $column);
if ($table === '' || $column === '') {
return;
}
$result = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
if ($result && $result->num_rows > 0) {
return;
}
$db->query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
}
}
@@ -0,0 +1,85 @@
<?php
namespace classes;
use InvalidArgumentException;
class cron_task_definition
{
public string $id;
public string $name;
public string $description;
public string $module;
public string $handler;
public array $schedule;
public bool $enabled;
public int $timeout_seconds;
public int $estimated_duration_ms;
public int $priority;
public ?string $legacy_name;
public function __construct(array $definition)
{
$this->id = self::requiredString($definition, 'id');
$this->name = self::requiredString($definition, 'name');
$this->description = (string)($definition['description'] ?? '');
$this->module = self::requiredString($definition, 'module');
$this->handler = self::requiredString($definition, 'handler');
$this->schedule = cron_schedule::normalize($definition['schedule'] ?? []);
$this->enabled = (bool)($definition['enabled'] ?? true);
$this->timeout_seconds = max(30, (int)($definition['timeout_seconds'] ?? 600));
$this->estimated_duration_ms = max(0, (int)($definition['estimated_duration_ms'] ?? 0));
$this->priority = (int)($definition['priority'] ?? 100);
$legacy_name = trim((string)($definition['legacy_name'] ?? ''));
$this->legacy_name = $legacy_name !== '' ? $legacy_name : null;
if (!preg_match('/^[a-z0-9][a-z0-9_.-]{1,190}$/', $this->id)) {
throw new InvalidArgumentException('Invalid cron task id: ' . $this->id);
}
if (!preg_match('/^[a-z0-9][a-z0-9_-]{1,63}$/', $this->module)) {
throw new InvalidArgumentException('Invalid cron task module: ' . $this->module);
}
}
public function asArray(?array $state = null, ?int $estimatedDurationMs = null): array
{
$schedule = is_array($state['schedule'] ?? null) && ($state['schedule'] ?? []) !== []
? $state['schedule']
: $this->schedule;
$enabled = array_key_exists('enabled', $state ?? [])
? (bool)$state['enabled']
: $this->enabled;
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'module' => $this->module,
'handler' => $this->handler,
'schedule' => $schedule,
'default_schedule' => $this->schedule,
'enabled' => $enabled,
'default_enabled' => $this->enabled,
'timeout_seconds' => $this->timeout_seconds,
'estimated_duration_ms' => $estimatedDurationMs ?? $this->estimated_duration_ms,
'priority' => $this->priority,
'legacy_name' => $this->legacy_name,
'last_run_at' => $state['last_run_at'] ?? null,
'next_run_at' => $state['next_run_at'] ?? null,
'locked_until' => $state['locked_until'] ?? null,
'lock_owner' => $state['lock_owner'] ?? null,
'current_run_id' => $state['current_run_id'] ?? null,
'last_status' => $state['last_status'] ?? null,
'last_error' => $state['last_error'] ?? null,
];
}
private static function requiredString(array $definition, string $key): string
{
$value = trim((string)($definition[$key] ?? ''));
if ($value === '') {
throw new InvalidArgumentException('Missing cron task definition field: ' . $key);
}
return $value;
}
}
@@ -0,0 +1,85 @@
<?php
namespace classes;
use InvalidArgumentException;
class cron_task_registry
{
private string $modules_root;
/** @var array<string, cron_task_definition>|null */
private ?array $definitions = null;
public function __construct(?string $modules_root = null)
{
$this->modules_root = $modules_root ?? (defined('WD') ? WD . '/modules' : dirname(__DIR__) . '/modules');
}
/**
* @return array<string, cron_task_definition>
*/
public function definitions(): array
{
if ($this->definitions !== null) {
return $this->definitions;
}
$definitions = [];
foreach ($this->definitionFiles() as $file) {
$module_definitions = require $file;
if (!is_array($module_definitions)) {
throw new InvalidArgumentException('Cron definition file must return an array: ' . $file);
}
foreach ($module_definitions as $definition) {
$task = new cron_task_definition($definition);
if (isset($definitions[$task->id])) {
throw new InvalidArgumentException('Duplicate cron task id: ' . $task->id);
}
$definitions[$task->id] = $task;
}
}
uasort($definitions, static function (cron_task_definition $left, cron_task_definition $right): int {
if ($left->priority !== $right->priority) {
return $left->priority <=> $right->priority;
}
return strcmp($left->id, $right->id);
});
$this->definitions = $definitions;
return $definitions;
}
public function get(string $id_or_legacy_name): ?cron_task_definition
{
$normalized = trim($id_or_legacy_name);
if ($normalized === '') {
return null;
}
$definitions = $this->definitions();
if (isset($definitions[$normalized])) {
return $definitions[$normalized];
}
foreach ($definitions as $definition) {
if ($definition->legacy_name !== null && hash_equals($definition->legacy_name, $normalized)) {
return $definition;
}
}
return null;
}
/**
* @return array<int, string>
*/
private function definitionFiles(): array
{
$files = glob($this->modules_root . '/*/cron/tasks.php') ?: [];
sort($files, SORT_STRING);
return $files;
}
}
+356
View File
@@ -0,0 +1,356 @@
<?php
namespace classes;
use Throwable;
class cron_worker
{
private cron_scheduler $scheduler;
private string $worker_id;
private string $name;
private string $source;
private int $poll_seconds;
private int $heartbeat_seconds;
private int $max_runtime_seconds;
private bool $should_stop = false;
private int $last_heartbeat = 0;
public function __construct(?cron_scheduler $scheduler = null, array $options = [])
{
$this->scheduler = $scheduler ?? new cron_scheduler();
$this->name = $this->stringOption($options, 'name', 'CRON_WORKER_NAME', 'cron-worker');
$this->worker_id = $this->stringOption($options, 'worker_id', 'CRON_WORKER_ID', $this->name);
$this->source = $this->stringOption($options, 'source', 'CRON_WORKER_SOURCE', 'coolify_worker');
$this->poll_seconds = $this->intOption($options, 'poll_seconds', 'CRON_WORKER_POLL_SECONDS', 15, 1, 300);
$this->heartbeat_seconds = $this->intOption($options, 'heartbeat_seconds', 'CRON_WORKER_HEARTBEAT_SECONDS', 30, 5, 300);
$this->max_runtime_seconds = $this->intOption($options, 'max_runtime_seconds', 'CRON_WORKER_MAX_RUNTIME_SECONDS', 0, 0, 86400);
}
public function run(): int
{
if (!$this->boolOption('CRON_WORKER_ENABLED', true)) {
$this->heartbeat('disabled', 0, 0, null, true);
return 0;
}
$this->registerSignalHandlers();
$started = time();
$this->heartbeat('starting', 0, 0, null, true);
while (!$this->should_stop) {
$pollStarted = microtime(true);
$result = $this->tick();
$this->writeStatusLine($result);
if ($this->max_runtime_seconds > 0 && time() - $started >= $this->max_runtime_seconds) {
$this->should_stop = true;
break;
}
$this->sleepUntilNextPoll($pollStarted + $this->poll_seconds);
}
$this->heartbeat('stopped', 0, 0, null, true, true);
return 0;
}
public function tick(): array
{
$this->heartbeat('running');
$loopStartedAt = date('Y-m-d H:i:s');
$staleRuns = 0;
$ran = ['count' => 0, 'ran' => []];
$error = null;
$status = 'running';
try {
$staleRuns = $this->scheduler->markExpiredRunningRuns();
$ran = $this->scheduler->runDue($this->source);
} catch (Throwable $throwable) {
$status = 'failed';
$error = $throwable->getMessage();
}
$this->heartbeat($status, (int)($ran['count'] ?? 0), $staleRuns, $error, true, false, $loopStartedAt);
return [
'worker_id' => $this->worker_id,
'status' => $status,
'ran' => (int)($ran['count'] ?? 0),
'stale_runs' => $staleRuns,
'error' => $error,
];
}
public function listWorkers(): array
{
cron_schema_bootstrap::ensureTables();
$rows = $this->fetchAll('SELECT * FROM cron_worker_state ORDER BY last_heartbeat_at DESC, worker_id');
$workers = [];
foreach ($rows as $row) {
$workers[] = $this->publicWorker($row);
}
return [
'workers' => $workers,
'summary' => [
'total' => count($workers),
'running' => count(array_filter($workers, static fn(array $worker): bool => ($worker['status'] ?? '') === 'running')),
'stale' => count(array_filter($workers, static fn(array $worker): bool => (bool)($worker['stale'] ?? false))),
],
];
}
private function registerSignalHandlers(): void
{
if (!function_exists('pcntl_signal')) {
return;
}
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
}
pcntl_signal(SIGTERM, function (): void {
$this->should_stop = true;
});
pcntl_signal(SIGINT, function (): void {
$this->should_stop = true;
});
}
private function sleepUntilNextPoll(float $nextPollAt): void
{
while (!$this->should_stop) {
$remaining = $nextPollAt - microtime(true);
if ($remaining <= 0) {
return;
}
usleep((int)(min(1.0, $remaining) * 1000000));
if (time() - $this->last_heartbeat >= $this->heartbeat_seconds) {
$this->heartbeat('running');
}
}
}
private function heartbeat(
string $status,
int $runCount = 0,
int $staleRunCount = 0,
?string $error = null,
bool $force = false,
bool $stopped = false,
?string $loopStartedAt = null
): void {
if (!$force && time() - $this->last_heartbeat < $this->heartbeat_seconds) {
return;
}
cron_schema_bootstrap::ensureTables();
$this->last_heartbeat = time();
$now = date('Y-m-d H:i:s');
$workerId = $this->sql($this->worker_id);
$name = $this->sql($this->name);
$hostname = $this->nullableSql(gethostname() ?: null);
$pid = getmypid() ?: 0;
$source = $this->sql($this->source);
$statusSql = $this->sql($status);
$releaseChannelId = $this->nullableInt($this->env('CRON_WORKER_RELEASE_CHANNEL_ID'));
$releaseTargetId = $this->nullableInt($this->env('CRON_WORKER_RELEASE_TARGET_ID'));
$resourceUuid = $this->nullableSql($this->env('COOLIFY_RESOURCE_UUID') ?: $this->env('CRON_WORKER_COOLIFY_RESOURCE_UUID'));
$resourceType = $this->nullableSql($this->env('COOLIFY_RESOURCE_TYPE') ?: $this->env('CRON_WORKER_COOLIFY_RESOURCE_TYPE') ?: 'application');
$commitSha = $this->nullableSql($this->commitSha());
$errorSql = $this->nullableSql($error);
$loopStarted = $this->nullableSql($loopStartedAt);
$stoppedAt = $stopped ? $this->sql($now) : 'NULL';
$nowSql = $this->sql($now);
$this->query(
"INSERT INTO cron_worker_state (
worker_id, name, hostname, pid, source, status, release_channel_id, release_target_id,
coolify_resource_uuid, coolify_resource_type, commit_sha, poll_seconds, last_run_count,
last_stale_run_count, last_error, started_at, last_heartbeat_at, last_loop_started_at,
last_loop_finished_at, last_loop_gap_seconds, consecutive_minute_loops, stopped_at
) VALUES (
$workerId, $name, $hostname, $pid, $source, $statusSql, $releaseChannelId, $releaseTargetId,
$resourceUuid, $resourceType, $commitSha, $this->poll_seconds, $runCount,
$staleRunCount, $errorSql, $nowSql, $nowSql, $loopStarted,
$nowSql, NULL, " . ($loopStartedAt !== null ? '1' : '0') . ", $stoppedAt
)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
hostname = VALUES(hostname),
pid = VALUES(pid),
source = VALUES(source),
status = VALUES(status),
release_channel_id = VALUES(release_channel_id),
release_target_id = VALUES(release_target_id),
coolify_resource_uuid = VALUES(coolify_resource_uuid),
coolify_resource_type = VALUES(coolify_resource_type),
commit_sha = VALUES(commit_sha),
poll_seconds = VALUES(poll_seconds),
last_run_count = VALUES(last_run_count),
last_stale_run_count = VALUES(last_stale_run_count),
last_error = VALUES(last_error),
last_heartbeat_at = VALUES(last_heartbeat_at),
last_loop_gap_seconds = CASE
WHEN VALUES(last_loop_started_at) IS NULL OR last_loop_started_at IS NULL THEN last_loop_gap_seconds
ELSE GREATEST(0, TIMESTAMPDIFF(SECOND, last_loop_started_at, VALUES(last_loop_started_at)))
END,
consecutive_minute_loops = CASE
WHEN VALUES(last_loop_started_at) IS NULL THEN consecutive_minute_loops
WHEN last_loop_started_at IS NULL THEN 1
WHEN TIMESTAMPDIFF(SECOND, last_loop_started_at, VALUES(last_loop_started_at)) BETWEEN 0 AND 60
THEN consecutive_minute_loops + 1
ELSE 1
END,
last_loop_started_at = COALESCE(VALUES(last_loop_started_at), last_loop_started_at),
last_loop_finished_at = VALUES(last_loop_finished_at),
stopped_at = VALUES(stopped_at)"
);
}
private function publicWorker(array $row): array
{
$heartbeatAt = (string)($row['last_heartbeat_at'] ?? '');
$heartbeatTs = strtotime($heartbeatAt);
$threshold = max(60, ((int)($row['poll_seconds'] ?? 15) * 4) + 30);
$age = $heartbeatTs !== false ? max(0, time() - $heartbeatTs) : null;
$loopStartedAt = (string)($row['last_loop_started_at'] ?? '');
$loopStartedTs = strtotime($loopStartedAt);
$loopAge = $loopStartedTs !== false ? max(0, time() - $loopStartedTs) : null;
$loopGap = isset($row['last_loop_gap_seconds']) ? (int)$row['last_loop_gap_seconds'] : null;
$consecutiveMinuteLoops = (int)($row['consecutive_minute_loops'] ?? 0);
$minuteCadenceVerified = ($row['status'] ?? '') === 'running'
&& $loopAge !== null
&& $loopAge <= 60
&& $loopGap !== null
&& $loopGap <= 60
&& $consecutiveMinuteLoops >= 2;
return [
'worker_id' => (string)($row['worker_id'] ?? ''),
'name' => (string)($row['name'] ?? ''),
'hostname' => $row['hostname'] ?? null,
'pid' => isset($row['pid']) ? (int)$row['pid'] : null,
'source' => (string)($row['source'] ?? ''),
'status' => (string)($row['status'] ?? 'unknown'),
'release_channel_id' => isset($row['release_channel_id']) ? (int)$row['release_channel_id'] : null,
'release_target_id' => isset($row['release_target_id']) ? (int)$row['release_target_id'] : null,
'coolify_resource_uuid' => $row['coolify_resource_uuid'] ?? null,
'coolify_resource_type' => $row['coolify_resource_type'] ?? null,
'commit_sha' => $row['commit_sha'] ?? null,
'poll_seconds' => (int)($row['poll_seconds'] ?? 0),
'last_run_count' => (int)($row['last_run_count'] ?? 0),
'last_stale_run_count' => (int)($row['last_stale_run_count'] ?? 0),
'last_error' => $row['last_error'] ?? null,
'started_at' => $row['started_at'] ?? null,
'last_heartbeat_at' => $heartbeatAt !== '' ? $heartbeatAt : null,
'last_heartbeat_age_seconds' => $age,
'last_loop_started_at' => $row['last_loop_started_at'] ?? null,
'last_loop_finished_at' => $row['last_loop_finished_at'] ?? null,
'last_loop_age_seconds' => $loopAge,
'last_loop_gap_seconds' => $loopGap,
'consecutive_minute_loops' => $consecutiveMinuteLoops,
'minute_cadence' => [
'verified' => $minuteCadenceVerified,
'maximum_gap_seconds' => 60,
'last_gap_seconds' => $loopGap,
'consecutive_loops' => $consecutiveMinuteLoops,
],
'stopped_at' => $row['stopped_at'] ?? null,
'stale' => $age === null || $age > $threshold,
'stale_after_seconds' => $threshold,
];
}
private function writeStatusLine(array $result): void
{
echo '[' . date('Y-m-d H:i:s') . '][CRON_WORKER] '
. json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
. PHP_EOL;
}
private function stringOption(array $options, string $key, string $env, string $default): string
{
$value = trim((string)($options[$key] ?? $this->env($env) ?? ''));
return $value !== '' ? $value : $default;
}
private function intOption(array $options, string $key, string $env, int $default, int $min, int $max): int
{
$value = (int)($options[$key] ?? $this->env($env) ?? $default);
return max($min, min($max, $value));
}
private function boolOption(string $env, bool $default): bool
{
$value = $this->env($env);
if ($value === null || trim($value) === '') {
return $default;
}
return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true);
}
private function commitSha(): string
{
foreach (['CRON_WORKER_COMMIT_SHA', 'API_COMMIT_SHA', 'RELEASE_COMMIT_SHA', 'COMMIT_SHA', 'GITHUB_SHA'] as $key) {
$value = trim((string)($this->env($key) ?? ''));
if ($value !== '') {
return $value;
}
}
return '';
}
private function env(string $key): ?string
{
$value = getenv($key);
if ($value !== false) {
return (string)$value;
}
return isset($_SERVER[$key]) ? (string)$_SERVER[$key] : null;
}
private function nullableInt(?string $value): string
{
$value = trim((string)$value);
if ($value === '' || filter_var($value, FILTER_VALIDATE_INT) === false) {
return 'NULL';
}
return (string)max(0, (int)$value);
}
private function nullableSql(?string $value): string
{
$value = $value !== null ? trim($value) : '';
return $value === '' ? 'NULL' : $this->sql($value);
}
private function fetchAll(string $sql): array
{
$result = $this->query($sql);
if ($result === false || $result === true) {
return [];
}
return $result->fetch_all(MYSQLI_ASSOC);
}
private function query(string $sql): \mysqli_result|bool
{
global $db;
return $db->query($sql);
}
private function sql(string $value): string
{
global $db;
return "'" . $db->escape_string($value) . "'";
}
}
@@ -137,6 +137,10 @@ class customer_mass_import_service
if ($cvrLength < 8 || $cvrLength > 20) {
throw new \RuntimeException('CVR must be between 8 and 20 digits.', 400);
}
if ($normalized['ean'] !== null && strlen((string)$normalized['ean']) > 13) {
throw new \RuntimeException('EAN must be at most 13 digits.', 400);
}
}
protected function normalizePositiveInt(mixed $value): ?int
@@ -0,0 +1,94 @@
<?php
namespace classes;
class customer_name_cache_payload_builder
{
/**
* @return array{name:string}|null
*/
public static function build(mixed $cached_name, ?string $fallback_name): ?array
{
$cached_name = self::normalizePayload($cached_name);
$name = self::extractName($cached_name);
if ($name !== null) {
return ['name' => $name];
}
$fallback_name = self::normalizeName($fallback_name);
if ($fallback_name !== null) {
return ['name' => $fallback_name];
}
return null;
}
private static function normalizePayload(mixed $payload): mixed
{
if (!is_string($payload)) {
return $payload;
}
$trimmed = trim($payload);
if ($trimmed === '') {
return null;
}
$decoded = json_decode($trimmed);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
return $trimmed;
}
private static function extractName(mixed $payload): ?string
{
if (is_string($payload)) {
return self::normalizeName($payload);
}
if (!is_object($payload) && !is_array($payload)) {
return null;
}
foreach (['name', 'customerName', 'customer_name', 'displayName', 'display_name'] as $key) {
$name = self::normalizeName(self::payloadValue($payload, $key));
if ($name !== null) {
return $name;
}
}
foreach (['customer', 'data', 'economic_customer'] as $key) {
$name = self::extractName(self::payloadValue($payload, $key));
if ($name !== null) {
return $name;
}
}
return null;
}
private static function payloadValue(mixed $payload, string $key): mixed
{
if (is_object($payload) && property_exists($payload, $key)) {
return $payload->{$key};
}
if (is_array($payload) && array_key_exists($key, $payload)) {
return $payload[$key];
}
return null;
}
private static function normalizeName(mixed $name): ?string
{
if (!is_string($name)) {
return null;
}
$name = trim($name);
return $name === '' ? null : $name;
}
}
@@ -0,0 +1,46 @@
<?php
namespace classes;
use RuntimeException;
class customer_order_product_policy
{
public static function assertOrderAllowsProduct(int $orderId, int $productId): void
{
$message = self::orderProductViolationMessage($orderId, $productId);
if ($message !== null) {
throw new RuntimeException($message);
}
}
public static function orderProductViolationMessage(int $orderId, int $productId): ?string
{
$customerNumber = self::loadOrderCustomerNumber($orderId);
if ($customerNumber === null) {
return null;
}
$violation = (new customer_rule_product_restriction_service())
->violationForCustomerProduct($customerNumber, $productId);
return $violation === null ? null : (string)$violation['message'];
}
private static function loadOrderCustomerNumber(int $orderId): ?int
{
global $db;
if ($orderId < 1) {
return null;
}
$result = $db->query("SELECT customer_id FROM orders WHERE id = {$orderId} LIMIT 1");
if (!$result || $result->num_rows < 1) {
return null;
}
$row = $result->fetch_assoc();
$customerNumber = (int)($row['customer_id'] ?? 0);
return $customerNumber > 0 ? $customerNumber : null;
}
}
@@ -0,0 +1,33 @@
<?php
namespace classes;
use objects\orders_o;
class customer_product_rule_service
{
public const BLOCK_MESSAGE = 'This product is not allowed for the selected customer';
/**
* @return array{rule:string,rules:list<string>,collections:list<int>,product_id:int,code:string,message:string}|null
*/
public function firstViolationForOrderItem(int $orderId, int $productId, ?int $relatedItemId): ?array
{
$order = (new orders_o())->getOrderById($orderId);
if (!$order->exists()) {
return null;
}
$violation = (new customer_rule_product_restriction_service())->violationForCustomerProduct(
(int)$order->customer_id->value(),
$productId
);
if ($violation === null) {
return null;
}
// Keep the singular key during the API migration for existing invoice
// and logging consumers while also returning every matching rule.
return ['rule' => (string)$violation['rules'][0]] + $violation;
}
}
@@ -0,0 +1,291 @@
<?php
namespace classes;
use RuntimeException;
use Throwable;
/**
* Additive schema and the one-time legacy-to-exact-product migration for
* customer-rule product restrictions.
*/
class customer_rule_product_restriction_schema_bootstrap
{
public const LEGACY_SEED_KEY = 'legacy_exact_product_sets_v1';
private static bool $initialized = false;
/** @var array<string, string> */
private const RULES = [
'restrictAdditionalServices' => 'Additional services',
'restrictTankCleaning' => 'Tank cleaning',
'restrictSpotFree' => 'SpotFree',
'restrictInteriorCleaning' => 'Interior cleaning',
'onlyTankCleaning' => 'Non-tank products',
];
public static function ensureSchema(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
self::createTables($db);
self::deduplicateCustomerAttributes($db);
self::seedLegacyProductSets($db);
self::$initialized = true;
}
private static function createTables(object $db): void
{
$statements = [
"CREATE TABLE IF NOT EXISTS customer_rule_product_restrictions (
attribute VARCHAR(191) NOT NULL,
version INT UNSIGNED NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (attribute)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS customer_rule_product_collections (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
attribute VARCHAR(191) NOT NULL,
name VARCHAR(191) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uniq_customer_rule_collection_name (attribute, name),
KEY idx_customer_rule_collection_attribute_order (attribute, sort_order, id),
CONSTRAINT fk_customer_rule_collection_attribute
FOREIGN KEY (attribute) REFERENCES customer_rule_product_restrictions(attribute)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS customer_rule_product_collection_products (
collection_id INT UNSIGNED NOT NULL,
product_id INT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (collection_id, product_id),
KEY idx_customer_rule_collection_product (product_id, collection_id),
CONSTRAINT fk_customer_rule_collection_product_collection
FOREIGN KEY (collection_id) REFERENCES customer_rule_product_collections(id)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS customer_rule_product_migrations (
migration_key VARCHAR(191) NOT NULL,
details_json LONGTEXT NULL,
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (migration_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
"CREATE TABLE IF NOT EXISTS customer_rule_product_audit_logs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
actor_user_id INT UNSIGNED NULL,
attribute VARCHAR(191) NOT NULL,
old_version INT UNSIGNED NOT NULL,
new_version INT UNSIGNED NOT NULL,
changes_json LONGTEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_customer_rule_product_audit_attribute (attribute, created_at),
KEY idx_customer_rule_product_audit_actor (actor_user_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
];
foreach ($statements as $statement) {
if ($db->query($statement) === false) {
throw new RuntimeException('Unable to initialize customer-rule product restriction schema');
}
}
}
private static function deduplicateCustomerAttributes(object $db): void
{
if (!self::tableExists($db, 'customer_attributes')) {
return;
}
if (self::indexExists($db, 'customer_attributes', 'uniq_customer_attributes_user_attribute')) {
return;
}
if ($db->query(
'DELETE duplicate_row FROM customer_attributes duplicate_row
INNER JOIN customer_attributes keep_row
ON keep_row.user_id = duplicate_row.user_id
AND keep_row.attribute = duplicate_row.attribute
AND keep_row.id < duplicate_row.id'
) === false) {
throw new RuntimeException('Unable to deduplicate customer attributes');
}
if ($db->query(
'ALTER TABLE customer_attributes
ADD UNIQUE KEY uniq_customer_attributes_user_attribute (user_id, attribute)'
) === false) {
throw new RuntimeException('Unable to enforce unique customer attributes');
}
}
private static function seedLegacyProductSets(object $db): void
{
if (!self::tableExists($db, 'products') || !self::tableExists($db, 'categories')) {
return;
}
$migrationKey = self::escape($db, self::LEGACY_SEED_KEY);
$existing = $db->query(
"SELECT migration_key FROM customer_rule_product_migrations WHERE migration_key = '{$migrationKey}' LIMIT 1"
);
if ($existing && (int)$existing->num_rows > 0) {
return;
}
if ($db->query('START TRANSACTION') === false) {
throw new RuntimeException('Unable to start customer-rule product migration');
}
try {
if ($db->query(
"INSERT IGNORE INTO customer_rule_product_migrations (migration_key, details_json)
VALUES ('{$migrationKey}', '{\"status\":\"in_progress\"}')"
) === false) {
throw new RuntimeException('Unable to claim customer-rule product migration');
}
if (self::affectedRows($db) === 0) {
$db->query('ROLLBACK');
return;
}
foreach (array_keys(self::RULES) as $attribute) {
$safeAttribute = self::escape($db, $attribute);
if ($db->query(
"INSERT IGNORE INTO customer_rule_product_restrictions (attribute, version)
VALUES ('{$safeAttribute}', 1)"
) === false) {
throw new RuntimeException("Unable to initialize restriction {$attribute}");
}
}
$counts = [];
$seededProductIds = [];
foreach (self::RULES as $attribute => $collectionName) {
$safeAttribute = self::escape($db, $attribute);
$safeName = self::escape($db, 'Legacy migration: ' . $collectionName);
if ($db->query(
"INSERT INTO customer_rule_product_collections (attribute, name, sort_order)
VALUES ('{$safeAttribute}', '{$safeName}', 0)"
) === false) {
throw new RuntimeException("Unable to create seed collection for {$attribute}");
}
$collectionId = (int)$db->insert_id();
if ($collectionId < 1) {
throw new RuntimeException("Unable to create seed collection for {$attribute}");
}
$predicate = self::legacyPredicate($db, $attribute);
$activePredicate = self::columnExists($db, 'products', 'deleted_at')
? 'p.deleted_at IS NULL'
: '1 = 1';
$insert = $db->query(
"INSERT IGNORE INTO customer_rule_product_collection_products (collection_id, product_id)
SELECT {$collectionId}, p.id
FROM products p
LEFT JOIN categories c ON c.id = p.category
WHERE ({$activePredicate}) AND ({$predicate})"
);
if ($insert === false) {
throw new RuntimeException("Unable to seed products for {$attribute}");
}
$counts[$attribute] = self::affectedRows($db);
$seeded = $db->query(
"SELECT product_id FROM customer_rule_product_collection_products
WHERE collection_id = {$collectionId} ORDER BY product_id"
);
$seededProductIds[$attribute] = [];
if ($seeded) {
while ($row = $seeded->fetch_assoc()) {
$seededProductIds[$attribute][] = (int)$row['product_id'];
}
}
}
$details = self::escape($db, (string)json_encode([
'counts' => $counts,
'product_ids' => $seededProductIds,
'seeded_at' => gmdate(DATE_ATOM),
], JSON_UNESCAPED_SLASHES));
if ($db->query(
"UPDATE customer_rule_product_migrations
SET details_json = '{$details}', applied_at = NOW()
WHERE migration_key = '{$migrationKey}'"
) === false) {
throw new RuntimeException('Unable to record customer-rule product migration');
}
if ($db->query('COMMIT') === false) {
throw new RuntimeException('Unable to commit customer-rule product migration');
}
} catch (Throwable $throwable) {
$db->query('ROLLBACK');
throw $throwable;
}
}
private static function legacyPredicate(object $db, string $attribute): string
{
$text = "LOWER(CONCAT(COALESCE(p.name, ''), ' ', COALESCE(c.name, '')))";
return match ($attribute) {
'restrictAdditionalServices' => "p.category = 8 OR LOWER(COALESCE(c.name, '')) IN ('tillægsydelser', 'tillaegsydelser')" .
(self::tableExists($db, 'products_options') && self::columnExists($db, 'products_options', 'option_id')
? ' OR EXISTS (SELECT 1 FROM products_options po WHERE po.option_id = p.id)'
: ''),
'restrictTankCleaning' => "p.category = 5 OR {$text} LIKE '%tank cleaning%' OR {$text} LIKE '%tankcleaning%' OR {$text} LIKE '%tankrens%' OR {$text} LIKE '%tank rens%'",
'restrictSpotFree' => "p.id IN (23, 24) OR {$text} LIKE '%spot free%' OR {$text} LIKE '%spotfree%' OR {$text} LIKE '%skylning med ro%'",
'restrictInteriorCleaning' => "{$text} LIKE '%interior%' OR {$text} LIKE '%indvendig%'",
'onlyTankCleaning' => "NOT (p.category = 5 OR {$text} LIKE '%tank cleaning%' OR {$text} LIKE '%tankcleaning%' OR {$text} LIKE '%tankrens%' OR {$text} LIKE '%tank rens%')",
default => '0 = 1',
};
}
private static function tableExists(object $db, string $table): bool
{
$safeTable = self::escape($db, $table);
$result = $db->query("SHOW TABLES LIKE '{$safeTable}'");
return $result && (int)$result->num_rows > 0;
}
private static function indexExists(object $db, string $table, string $index): bool
{
$safeTable = str_replace('`', '', $table);
$safeIndex = self::escape($db, $index);
$result = $db->query("SHOW INDEX FROM `{$safeTable}` WHERE Key_name = '{$safeIndex}'");
return $result && (int)$result->num_rows > 0;
}
private static function columnExists(object $db, string $table, string $column): bool
{
$safeTable = str_replace('`', '', $table);
$safeColumn = self::escape($db, $column);
$result = $db->query("SHOW COLUMNS FROM `{$safeTable}` LIKE '{$safeColumn}'");
return $result && (int)$result->num_rows > 0;
}
private static function escape(object $db, string $value): string
{
return method_exists($db, 'escape_string')
? $db->escape_string($value)
: addslashes($value);
}
private static function affectedRows(object $db): int
{
if (method_exists($db, 'conn')) {
$connection = $db->conn();
return (int)($connection->affected_rows ?? 0);
}
return (int)($db->affected_rows ?? 0);
}
}
@@ -0,0 +1,508 @@
<?php
namespace classes;
use RuntimeException;
use Throwable;
class customer_rule_product_restriction_exception extends RuntimeException
{
public function __construct(string $message, private readonly int $httpStatus = 422, string $code = 'INVALID_CUSTOMER_RULE_CONFIGURATION')
{
parent::__construct($message);
$this->restrictionCode = $code;
}
private string $restrictionCode;
public function httpStatus(): int
{
return $this->httpStatus;
}
public function restrictionCode(): string
{
return $this->restrictionCode;
}
}
/**
* Source of truth for globally configured customer-rule product collections.
*/
class customer_rule_product_restriction_service
{
/** @var list<string> */
public const PRODUCT_IMPACT_ATTRIBUTES = [
'restrictAdditionalServices',
'restrictTankCleaning',
'restrictSpotFree',
'restrictInteriorCleaning',
'onlyTankCleaning',
];
/** @var list<string> */
public const SUPPORTED_ATTRIBUTES = [
'restrictAdditionalServices',
'restrictTankCleaning',
'restrictSpotFree',
'restrictInteriorCleaning',
'onlyTankCleaning',
'requiresReferenceNumber',
'requiresRegistrationNumbersInvoice',
'invoiceAllOrdersIndividually',
'invoiceWithStripe',
'showPricesOnBookingPage',
'usePONumbers',
'exemptFromAdministrationFee',
];
public function __construct()
{
customer_rule_product_restriction_schema_bootstrap::ensureSchema();
}
/** @return array{rules:list<array<string,mixed>>,products:list<array<string,mixed>>} */
public function listConfiguration(): array
{
return [
'rules' => array_map(fn(string $attribute): array => $this->ruleConfiguration($attribute), self::PRODUCT_IMPACT_ATTRIBUTES),
'products' => $this->productCatalog(),
];
}
/** @return array<string,mixed> */
public function ruleConfiguration(string $attribute): array
{
$this->assertSupportedAttribute($attribute);
global $db;
$safeAttribute = $this->escape($attribute);
$versionResult = $db->query(
"SELECT version FROM customer_rule_product_restrictions WHERE attribute = '{$safeAttribute}' LIMIT 1"
);
if (!$versionResult || $versionResult->num_rows < 1) {
throw new RuntimeException("Unable to load customer-rule restriction version for {$attribute}");
}
$versionRow = $versionResult->fetch_assoc();
$result = $db->query(
"SELECT c.id AS collection_id, c.name, c.sort_order, cp.product_id
FROM customer_rule_product_collections c
LEFT JOIN customer_rule_product_collection_products cp ON cp.collection_id = c.id
WHERE c.attribute = '{$safeAttribute}'
ORDER BY c.sort_order ASC, c.id ASC, cp.product_id ASC"
);
if (!$result) {
throw new RuntimeException("Unable to load customer-rule restriction collections for {$attribute}");
}
$collections = [];
$disabled = [];
while ($row = $result->fetch_assoc()) {
$collectionId = (int)$row['collection_id'];
if (!isset($collections[$collectionId])) {
$collections[$collectionId] = [
'id' => $collectionId,
'name' => (string)$row['name'],
'sort_order' => (int)$row['sort_order'],
'product_ids' => [],
];
}
if ($row['product_id'] !== null) {
$productId = (int)$row['product_id'];
$collections[$collectionId]['product_ids'][] = $productId;
$disabled[$productId] = true;
}
}
return [
'attribute' => $attribute,
'version' => max(1, (int)($versionRow['version'] ?? 1)),
'collections' => array_values($collections),
'disabled_product_ids' => array_map('intval', array_keys($disabled)),
];
}
/**
* @param array<string,mixed> $payload
* @return array<string,mixed>
*/
public function replaceRuleConfiguration(string $attribute, array $payload, int $actorUserId): array
{
$this->assertSupportedAttribute($attribute);
$expectedVersion = $this->positiveInt($payload['version'] ?? null, 'version');
$collections = $this->validateCollections($attribute, $payload['collections'] ?? null);
global $db;
$safeAttribute = $this->escape($attribute);
if ($db->query('START TRANSACTION') === false) {
throw new customer_rule_product_restriction_exception('Unable to start configuration transaction', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
try {
$versionResult = $db->query(
"SELECT version FROM customer_rule_product_restrictions
WHERE attribute = '{$safeAttribute}' FOR UPDATE"
);
if (!$versionResult || $versionResult->num_rows < 1) {
throw new customer_rule_product_restriction_exception('Customer rule configuration was not found', 404, 'CUSTOMER_RULE_CONFIGURATION_NOT_FOUND');
}
$versionRow = $versionResult->fetch_assoc();
$currentVersion = (int)$versionRow['version'];
if ($currentVersion !== $expectedVersion) {
throw new customer_rule_product_restriction_exception(
'Customer rule configuration has changed; reload before saving',
409,
'CUSTOMER_RULE_CONFIGURATION_CONFLICT'
);
}
$old = $this->ruleConfiguration($attribute);
$existingIds = $this->existingCollectionIds($attribute);
foreach ($collections as $collection) {
if ($collection['id'] !== null && !isset($existingIds[$collection['id']])) {
throw new customer_rule_product_restriction_exception('A collection does not belong to this customer rule');
}
}
// Avoid temporary unique-name collisions while two collections swap names.
foreach ($existingIds as $collectionId => $_) {
$temporaryName = $this->escape('__pending_' . $collectionId . '_' . bin2hex(random_bytes(6)));
if ($db->query(
"UPDATE customer_rule_product_collections
SET name = '{$temporaryName}'
WHERE id = {$collectionId} AND attribute = '{$safeAttribute}'"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to prepare collection update', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
}
$keptIds = [];
foreach ($collections as $collection) {
$name = $this->escape($collection['name']);
$sortOrder = (int)$collection['sort_order'];
$collectionId = $collection['id'];
if ($collectionId === null) {
if ($db->query(
"INSERT INTO customer_rule_product_collections (attribute, name, sort_order)
VALUES ('{$safeAttribute}', '{$name}', {$sortOrder})"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to create collection');
}
$collectionId = (int)$db->insert_id();
} else {
if ($db->query(
"UPDATE customer_rule_product_collections
SET name = '{$name}', sort_order = {$sortOrder}
WHERE id = {$collectionId} AND attribute = '{$safeAttribute}'"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to update collection');
}
}
$keptIds[$collectionId] = true;
if ($db->query("DELETE FROM customer_rule_product_collection_products WHERE collection_id = {$collectionId}") === false) {
throw new customer_rule_product_restriction_exception('Unable to replace collection products', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
foreach ($collection['product_ids'] as $productId) {
if ($db->query(
"INSERT INTO customer_rule_product_collection_products (collection_id, product_id)
VALUES ({$collectionId}, {$productId})"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to save collection products');
}
}
}
$removeIds = array_values(array_diff(array_keys($existingIds), array_keys($keptIds)));
if ($removeIds !== []) {
if ($db->query(
'DELETE FROM customer_rule_product_collections WHERE attribute = \'' . $safeAttribute . '\' AND id IN (' .
implode(',', array_map('intval', $removeIds)) . ')'
) === false) {
throw new customer_rule_product_restriction_exception('Unable to remove collections', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
}
$newVersion = $currentVersion + 1;
if ($db->query(
"UPDATE customer_rule_product_restrictions
SET version = {$newVersion}, updated_at = NOW()
WHERE attribute = '{$safeAttribute}'"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to update configuration version', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
$new = $this->ruleConfiguration($attribute);
$changes = $this->escape((string)json_encode([
'before' => $old,
'after' => $new,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
if ($db->query(
"INSERT INTO customer_rule_product_audit_logs
(actor_user_id, attribute, old_version, new_version, changes_json)
VALUES ({$actorUserId}, '{$safeAttribute}', {$currentVersion}, {$newVersion}, '{$changes}')"
) === false) {
throw new customer_rule_product_restriction_exception('Unable to audit configuration update', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
if ($db->query('COMMIT') === false) {
throw new customer_rule_product_restriction_exception('Unable to commit configuration update', 500, 'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED');
}
return $new;
} catch (Throwable $throwable) {
$db->query('ROLLBACK');
throw $throwable;
}
}
/**
* Return configured product restrictions for all active product-impact
* attributes belonging to any account with the customer number.
*
* @return list<array<string,mixed>>
*/
public function restrictionsForCustomerNumber(int $customerNumber): array
{
if ($customerNumber < 1) {
return [];
}
global $db;
$result = $db->query(
"SELECT DISTINCT ca.attribute
FROM users u
INNER JOIN customer_attributes ca ON ca.user_id = u.id
WHERE u.customer_number = {$customerNumber}"
);
if (!$result) {
throw new RuntimeException('Unable to load active customer-rule product restrictions');
}
$activeAttributes = [];
while ($row = $result->fetch_assoc()) {
$attribute = (string)$row['attribute'];
if (in_array($attribute, self::PRODUCT_IMPACT_ATTRIBUTES, true)) {
$activeAttributes[$attribute] = true;
}
}
$active = [];
foreach (self::PRODUCT_IMPACT_ATTRIBUTES as $attribute) {
if (isset($activeAttributes[$attribute])) {
$active[] = $this->ruleConfiguration($attribute);
}
}
return $active;
}
/** @return array{rules:list<string>,collections:list<int>,message:string,code:string,product_id:int}|null */
public function violationForCustomerProduct(int $customerNumber, int $productId): ?array
{
if ($productId < 1) {
return null;
}
$rules = [];
$collections = [];
foreach ($this->restrictionsForCustomerNumber($customerNumber) as $restriction) {
if (!in_array($productId, $restriction['disabled_product_ids'], true)) {
continue;
}
$rules[] = (string)$restriction['attribute'];
foreach ($restriction['collections'] as $collection) {
if (in_array($productId, $collection['product_ids'], true)) {
$collections[] = (int)$collection['id'];
}
}
}
if ($rules === []) {
return null;
}
return [
'code' => 'CUSTOMER_RULE_PRODUCT_RESTRICTED',
'message' => customer_product_rule_service::BLOCK_MESSAGE,
'product_id' => $productId,
'rules' => array_values(array_unique($rules)),
'collections' => array_values(array_unique($collections)),
];
}
/**
* @param list<array<string,mixed>> $attributes
* @return list<array<string,mixed>>
*/
public function enrichAttributes(int $customerNumber, array $attributes): array
{
$restrictions = [];
foreach ($this->restrictionsForCustomerNumber($customerNumber) as $restriction) {
$restrictions[(string)$restriction['attribute']] = [
'attribute' => (string)$restriction['attribute'],
'version' => (int)$restriction['version'],
'collections' => $restriction['collections'],
'disabled_product_ids' => $restriction['disabled_product_ids'],
];
}
foreach ($attributes as &$attribute) {
$key = (string)($attribute['attribute'] ?? '');
$attribute['product_restriction'] = $restrictions[$key] ?? null;
}
unset($attribute);
return $attributes;
}
/** @return list<array<string,mixed>> */
private function productCatalog(): array
{
global $db;
$activeExpression = $this->columnExists('products', 'deleted_at')
? 'CASE WHEN p.deleted_at IS NULL THEN 1 ELSE 0 END'
: '1';
$result = $db->query(
"SELECT p.id, p.name, p.category AS category_id, c.name AS category_name,
{$activeExpression} AS active
FROM products p
LEFT JOIN categories c ON c.id = p.category
ORDER BY c.name ASC, p.name ASC, p.id ASC"
);
if (!$result) {
throw new RuntimeException('Unable to load the customer-rule product catalog');
}
$products = [];
while ($row = $result->fetch_assoc()) {
$products[] = [
'id' => (int)$row['id'],
'name' => (string)$row['name'],
'category_id' => (int)$row['category_id'],
'category_name' => (string)($row['category_name'] ?? ''),
'active' => (bool)$row['active'],
];
}
return $products;
}
/**
* @return array<int,true>
*/
private function existingCollectionIds(string $attribute): array
{
global $db;
$safeAttribute = $this->escape($attribute);
$result = $db->query("SELECT id FROM customer_rule_product_collections WHERE attribute = '{$safeAttribute}'");
if (!$result) {
throw new RuntimeException("Unable to load existing collections for {$attribute}");
}
$ids = [];
while ($row = $result->fetch_assoc()) {
$ids[(int)$row['id']] = true;
}
return $ids;
}
/** @return list<array{id: ?int, name: string, sort_order: int, product_ids: list<int>}> */
private function validateCollections(string $attribute, mixed $value): array
{
if (!is_array($value)) {
throw new customer_rule_product_restriction_exception('collections must be an array');
}
$normalized = [];
$names = [];
$collectionIds = [];
$allProductIds = [];
foreach (array_values($value) as $index => $collection) {
if (!is_array($collection)) {
throw new customer_rule_product_restriction_exception("Collection {$index} must be an object");
}
$name = trim((string)($collection['name'] ?? ''));
if ($name === '' || mb_strlen($name) > 191) {
throw new customer_rule_product_restriction_exception('Collection names must be between 1 and 191 characters');
}
$nameKey = mb_strtolower($name);
if (isset($names[$nameKey])) {
throw new customer_rule_product_restriction_exception('Collection names must be unique within a rule');
}
$names[$nameKey] = true;
if (!isset($collection['product_ids']) || !is_array($collection['product_ids'])) {
throw new customer_rule_product_restriction_exception('product_ids must be an array');
}
$productIds = [];
foreach ($collection['product_ids'] as $productId) {
$id = $this->positiveInt($productId, 'product_id');
$productIds[$id] = true;
$allProductIds[$id] = true;
}
$id = isset($collection['id']) && $collection['id'] !== null
? $this->positiveInt($collection['id'], 'collection id')
: null;
if ($id !== null && isset($collectionIds[$id])) {
throw new customer_rule_product_restriction_exception('Collection IDs must be unique within a rule');
}
if ($id !== null) {
$collectionIds[$id] = true;
}
$normalized[] = [
'id' => $id,
'name' => $name,
'sort_order' => isset($collection['sort_order']) && is_numeric($collection['sort_order'])
? (int)$collection['sort_order']
: $index,
'product_ids' => array_map('intval', array_keys($productIds)),
];
}
$this->assertProductsExist(array_map('intval', array_keys($allProductIds)));
return $normalized;
}
/** @param list<int> $productIds */
private function assertProductsExist(array $productIds): void
{
if ($productIds === []) {
return;
}
global $db;
$result = $db->query('SELECT id FROM products WHERE id IN (' . implode(',', $productIds) . ')');
if (!$result) {
throw new customer_rule_product_restriction_exception(
'Unable to validate collection products',
500,
'CUSTOMER_RULE_CONFIGURATION_SAVE_FAILED'
);
}
$found = [];
while ($row = $result->fetch_assoc()) {
$found[(int)$row['id']] = true;
}
$missing = array_values(array_diff($productIds, array_keys($found)));
if ($missing !== []) {
throw new customer_rule_product_restriction_exception('Unknown product IDs: ' . implode(', ', $missing));
}
}
private function assertSupportedAttribute(string $attribute): void
{
if (!in_array($attribute, self::PRODUCT_IMPACT_ATTRIBUTES, true)) {
throw new customer_rule_product_restriction_exception('Unsupported product-impact customer rule');
}
}
private function positiveInt(mixed $value, string $field): int
{
if (!is_numeric($value) || (int)$value < 1 || (string)(int)$value !== trim((string)$value)) {
throw new customer_rule_product_restriction_exception("{$field} must be a positive integer");
}
return (int)$value;
}
private function escape(string $value): string
{
global $db;
return method_exists($db, 'escape_string') ? $db->escape_string($value) : addslashes($value);
}
private function columnExists(string $table, string $column): bool
{
global $db;
$safeTable = str_replace('`', '', $table);
$safeColumn = $this->escape($column);
$result = $db->query("SHOW COLUMNS FROM `{$safeTable}` LIKE '{$safeColumn}'");
return $result && (int)$result->num_rows > 0;
}
}
+50 -14
View File
@@ -82,7 +82,14 @@ class db
public function close(): void
{
$this->conn->close();
if (!isset($this->conn)) {
return;
}
try {
$this->conn->close();
} catch (\Throwable) {
}
}
public function get(string $table, int $id)
@@ -170,15 +177,19 @@ class db
return $this->database;
}
public function getPort(): int
{
return $this->port;
}
public function getSslMode(): string
{
return $this->ssl_mode;
}
public function backupDatabase(string $path): bool
{
// Save the database to the path
// Build a safe mysqldump command with configurable SSL (MariaDB-compatible flags)
$mode = strtoupper(trim($this->ssl_mode));
// Map ssl_mode to MariaDB client flags
// DISABLED => --skip-ssl (no TLS)
// PREFERRED => (no flag; client decides)
// REQUIRED/VERIFY_* => --ssl (enable TLS without strict verification unless CA materials provided)
$sslFlag = '';
switch ($mode) {
case 'DISABLED':
@@ -194,17 +205,42 @@ class db
$sslFlag = '--ssl';
break;
}
$host = escapeshellarg($this->host);
$user = escapeshellarg($this->user);
$pass = escapeshellarg($this->password);
$db = escapeshellarg($this->database);
$port = (int)$this->port;
$outfile = escapeshellarg($path);
$sslPart = $sslFlag !== '' ? ($sslFlag . ' ') : '';
$command = "mysqldump {$sslPart}-h $host -P $port -u $user --password=$pass $db > $outfile 2>&1";
exec($command, $output, $return);
// Check if the command was successful
return $return === 0;
$command = "mysqldump {$sslPart}--single-transaction --quick --routines --triggers --events --hex-blob -h $host -P $port -u $user $db";
$directory = dirname($path);
if (!is_dir($directory) && !mkdir($directory, 0770, true) && !is_dir($directory)) {
return false;
}
$environment = array_merge(getenv() ?: [], $_ENV);
$environment['MYSQL_PWD'] = $this->password;
$descriptors = [
0 => ['pipe', 'r'],
1 => ['file', $path, 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptors, $pipes, null, $environment);
if (!is_resource($process)) {
return false;
}
fclose($pipes[0]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$return = proc_close($process);
if ($return !== 0 && is_string($stderr) && $stderr !== '') {
@file_put_contents($path . '.error.log', $stderr);
}
return $return === 0 && is_file($path) && filesize($path) !== false;
}
public function getView(string $view): array
@@ -231,4 +267,4 @@ class db
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace classes;
/**
* Ensures additive schema for department-scoped customer price overrides.
*/
class department_customer_price_overrides_schema_bootstrap
{
private static bool $initialized = false;
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
$db->query(
"CREATE TABLE IF NOT EXISTS `department_customer_price_overrides` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`department_id` INT NOT NULL,
`user_id` INT NOT NULL,
`is_category` TINYINT(1) NOT NULL DEFAULT 0,
`product_or_category_id` VARCHAR(191) NOT NULL,
`percentage` INT NOT NULL DEFAULT 0,
`fixed_price` INT NULL DEFAULT NULL,
`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_department_customer_price_overrides_lookup` (`department_id`, `user_id`, `is_category`, `product_or_category_id`),
KEY `idx_department_customer_price_overrides_department` (`department_id`),
KEY `idx_department_customer_price_overrides_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
self::$initialized = true;
}
}
@@ -0,0 +1,458 @@
<?php
namespace classes;
use objects\department_customer_price_overrides_o;
use objects\departments_o;
use objects\products_o;
use objects\users_o;
class department_customer_pricing_service
{
/**
* @return array<string, mixed>
*/
public function getPricing(int $departmentId, int $userId): array
{
$department = $this->department($departmentId);
$customer = $this->customer($userId);
$this->assertEnabled($department);
$overrides = (new department_customer_price_overrides_o())->getAllPrices($departmentId, $userId);
return [
'department' => $department,
'customer' => $customer,
'overrides' => $overrides,
'categories' => $this->catalog($departmentId, $customer['id']),
'revision' => $this->revision($overrides),
];
}
/**
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
public function updatePricing(int $departmentId, int $userId, array $payload): array
{
$department = $this->department($departmentId);
$customer = $this->customer($userId);
$this->assertEnabled($department);
if (array_key_exists('department_id', $payload) && (int)$payload['department_id'] !== $departmentId) {
throw new limited_backoffice_exception('Department ID in body does not match the route.', 400);
}
if (array_key_exists('user_id', $payload) && (int)$payload['user_id'] !== $userId) {
throw new limited_backoffice_exception('User ID in body does not match the route.', 400);
}
$overrides = $payload['overrides'] ?? null;
if (!is_array($overrides)) {
throw new limited_backoffice_exception('Overrides are required.', 400);
}
$normalized = $this->normalizeOverrides($departmentId, $overrides);
$overrideObject = new department_customer_price_overrides_o();
$expectedRevision = $this->normalizeExpectedRevision($payload['expected_revision'] ?? null);
$existingOverrides = [];
$normalizedKeys = [];
foreach ($normalized as $override) {
$normalizedKeys[$this->overrideKey((bool)$override['is_category'], $override['product_or_category_id'])] = true;
}
global $db;
$mysqli = $db->conn();
$mysqli->begin_transaction();
try {
$this->lockDepartment($departmentId);
$existingOverrides = $overrideObject->getAllPrices($departmentId, $customer['id']);
$currentRevision = $this->revision($existingOverrides);
if ($expectedRevision !== null && !hash_equals($currentRevision, $expectedRevision)) {
throw $this->revisionConflict($currentRevision);
}
$deleteStatement = $mysqli->prepare(
'DELETE FROM `department_customer_price_overrides` WHERE `department_id` = ? AND `user_id` = ?'
);
$insertStatement = $mysqli->prepare(
'INSERT INTO `department_customer_price_overrides`
(`department_id`, `user_id`, `is_category`, `product_or_category_id`, `percentage`, `fixed_price`)
VALUES (?, ?, ?, ?, ?, ?)'
);
if ($deleteStatement === false || $insertStatement === false) {
throw new \RuntimeException('Unable to prepare department customer pricing update.');
}
$customerId = (int)$customer['id'];
$deleteStatement->bind_param('ii', $departmentId, $customerId);
if (!$deleteStatement->execute()) {
throw new \RuntimeException('Unable to clear department customer pricing.');
}
foreach ($normalized as $override) {
$isCategory = (int)(bool)$override['is_category'];
$objectId = (string)$override['product_or_category_id'];
$percentage = (int)$override['percentage'];
$fixedPrice = $override['fixed_price'] === null ? null : (int)$override['fixed_price'];
$insertStatement->bind_param(
'iiisii',
$departmentId,
$customerId,
$isCategory,
$objectId,
$percentage,
$fixedPrice
);
if (!$insertStatement->execute()) {
throw new \RuntimeException('Unable to save department customer pricing.');
}
}
$deleteStatement->close();
$insertStatement->close();
$mysqli->commit();
} catch (limited_backoffice_exception $exception) {
$mysqli->rollback();
throw $exception;
} catch (\Throwable) {
$mysqli->rollback();
throw new limited_backoffice_exception('Unable to update department customer pricing.', 500);
}
foreach ($normalized as $override) {
$this->recordVersion($customer, $departmentId, $override);
}
foreach ($existingOverrides as $existingOverride) {
$key = $this->overrideKey((bool)$existingOverride['is_category'], $existingOverride['product_or_category_id']);
if (isset($normalizedKeys[$key])) {
continue;
}
$this->recordVersion($customer, $departmentId, [
'is_category' => (bool)$existingOverride['is_category'],
'product_or_category_id' => $existingOverride['product_or_category_id'],
'percentage' => 0,
'fixed_price' => null,
]);
}
return $this->getPricing($departmentId, $customer['id']);
}
/**
* @return array{id:int,name:string,description:string,custom_pricing_only:bool}
*/
private function department(int $departmentId): array
{
$department = (new departments_o())->getDepartmentById($departmentId);
if (!is_array($department) || empty($department)) {
throw new limited_backoffice_exception('Department not found', 404);
}
return [
'id' => (int)$department['id'],
'name' => (string)$department['name'],
'description' => (string)($department['description'] ?? ''),
'custom_pricing_only' => (bool)(int)($department['custom_pricing_only'] ?? 0),
];
}
/**
* @return array{id:int,customer_number:int,display_name:string}
*/
private function customer(int $userId): array
{
$customer = (new users_o())->getUserById($userId);
if (!$customer->exists()) {
throw new limited_backoffice_exception('Customer not found', 404);
}
return [
'id' => (int)$customer->id,
'customer_number' => (int)$customer->customer_number->value(),
'display_name' => (string)($customer->display_name->value() ?: ('Customer #' . $customer->customer_number->value())),
];
}
/**
* @param array<string, mixed> $department
*/
private function assertEnabled(array $department): void
{
if (!($department['custom_pricing_only'] ?? false)) {
throw new limited_backoffice_exception('Department customer pricing is disabled.', 409, [
'message' => 'Department customer pricing is disabled.',
'code' => 'department_customer_pricing_disabled',
'department' => $department,
]);
}
}
/**
* @return array<int, array<string, mixed>>
*/
private function catalog(int $departmentId, int $userId): array
{
global $db;
$sql = "
SELECT
c.`id` AS `category_id`,
c.`name` AS `category_name`,
c.`description` AS `category_description`,
p.*,
pdp.`price` AS `department_price`
FROM `department_categories` dc
INNER JOIN `categories` c ON c.`id` = dc.`category_id`
INNER JOIN `products` p ON p.`category` = dc.`category_id`
LEFT JOIN `product_department_prices` pdp
ON pdp.`department_id` = dc.`department_id`
AND pdp.`product_id` = p.`id`
WHERE dc.`department_id` = " . (int)$departmentId . "
AND dc.`deleted_at` IS NULL
ORDER BY c.`name` ASC, c.`id` ASC, p.`order_priority` ASC, p.`name` ASC, p.`id` ASC";
$result = $db->query($sql);
$rows = $result ? $db->fetch_all($result) : [];
$customer = (new users_o())->getUserById($userId);
$categories = [];
$seen = [];
foreach ($rows as $row) {
$productId = (int)$row['id'];
if (isset($seen[$productId])) {
continue;
}
$seen[$productId] = true;
$categoryId = (int)$row['category_id'];
if (!isset($categories[$categoryId])) {
$categories[$categoryId] = [
'id' => $categoryId,
'name' => (string)$row['category_name'],
'description' => (string)($row['category_description'] ?? ''),
'products' => [],
];
}
$departmentPrice = $row['department_price'] === null ? null : (int)$row['department_price'];
$effectivePrice = products_o::CUSTOM_PRICING_MISSING_PRICE;
if ($departmentPrice !== null) {
$effectivePrice = $customer->applyProductCustomerPricing($productId, $departmentPrice, true, $departmentId);
}
$categories[$categoryId]['products'][] = [
'id' => $productId,
'name' => (string)$row['name'],
'description' => (string)($row['description'] ?? ''),
'category' => $categoryId,
'apply_category_discount' => (bool)$row['apply_category_discount'],
'base_price' => (int)$row['price'],
'department_price' => $departmentPrice,
'effective_price' => $effectivePrice,
'missing_department_price' => $departmentPrice === null,
];
}
return array_values($categories);
}
/**
* @param array<int, mixed> $overrides
* @return array<int, array{is_category:bool,product_or_category_id:int|string,percentage:int,fixed_price:int|null}>
*/
private function normalizeOverrides(int $departmentId, array $overrides): array
{
$normalized = [];
foreach ($overrides as $override) {
if (!is_array($override)) {
throw new limited_backoffice_exception('Invalid override payload.', 400);
}
$isCategory = (bool)($override['is_category'] ?? false);
$objectId = $override['product_or_category_id'] ?? $override['object_id'] ?? null;
if ($objectId === null || $objectId === '') {
throw new limited_backoffice_exception('Override object is required.', 400);
}
$percentage = filter_var($override['discount'] ?? $override['percentage'] ?? 0, FILTER_VALIDATE_INT);
if ($percentage === false || $percentage < 0 || $percentage > 100) {
throw new limited_backoffice_exception('Discount must be between 0 and 100.', 400);
}
$fixedPrice = null;
if (array_key_exists('fixed_price', $override) && $override['fixed_price'] !== null && $override['fixed_price'] !== '') {
$fixedPrice = filter_var($override['fixed_price'], FILTER_VALIDATE_INT);
if ($fixedPrice === false || $fixedPrice < 0) {
throw new limited_backoffice_exception('Fixed price must be zero or more.', 400);
}
}
if ($isCategory) {
if ($fixedPrice !== null) {
throw new limited_backoffice_exception('Fixed prices can only be assigned to products.', 400);
}
$fixedPrice = null;
$objectId = (string)$objectId;
if ($objectId !== 'global') {
$this->assertDepartmentCategory($departmentId, $objectId);
}
} else {
$objectId = (int)$objectId;
$this->assertDepartmentProduct($departmentId, $objectId);
}
if ($percentage <= 0 && $fixedPrice === null) {
continue;
}
$key = $this->overrideKey($isCategory, $objectId);
if (isset($normalized[$key])) {
throw new limited_backoffice_exception('Duplicate customer price overrides are not allowed.', 400);
}
if ($fixedPrice !== null && $percentage > 0) {
throw new limited_backoffice_exception('Choose either a discount or a fixed price.', 400);
}
$normalized[$key] = [
'is_category' => $isCategory,
'product_or_category_id' => $objectId,
'percentage' => (int)$percentage,
'fixed_price' => $fixedPrice === null ? null : (int)$fixedPrice,
];
}
return array_values($normalized);
}
/**
* @param array{id:int,customer_number:int,display_name:string} $customer
* @param array{is_category:bool,product_or_category_id:int|string,percentage:int,fixed_price:int|null} $override
*/
private function recordVersion(array $customer, int $departmentId, array $override): void
{
try {
(new economic_v2_versioning_service())->recordDiscountOverrideVersion(
(int)$customer['id'],
(int)$customer['customer_number'],
(bool)$override['is_category'],
(string)$override['product_or_category_id'],
(int)$override['percentage'],
date('Y-m-d H:i:s'),
'live.department_discount_override.route',
1.0,
false,
[
'route' => 'department_customer_pricing',
'department_id' => $departmentId,
],
$override['fixed_price'],
$departmentId
);
} catch (\Throwable) {
}
}
private function overrideKey(bool $isCategory, int|string $objectId): string
{
return ((int)$isCategory) . ':' . (string)$objectId;
}
/**
* @param array<int, array<string, mixed>> $overrides
*/
private function revision(array $overrides): string
{
$revisionRows = array_map(static fn(array $override): array => [
'is_category' => (bool)$override['is_category'],
'product_or_category_id' => (string)$override['product_or_category_id'],
'percentage' => (int)$override['percentage'],
'fixed_price' => $override['fixed_price'] === null ? null : (int)$override['fixed_price'],
], $overrides);
usort($revisionRows, static function (array $left, array $right): int {
return [$left['is_category'] ? 0 : 1, $left['product_or_category_id']]
<=> [$right['is_category'] ? 0 : 1, $right['product_or_category_id']];
});
return hash('sha256', json_encode($revisionRows, JSON_THROW_ON_ERROR));
}
private function normalizeExpectedRevision(mixed $value): ?string
{
if ($value === null || $value === '') {
// Keep the backend-first rollout compatible with the currently deployed UI.
return null;
}
if (!is_string($value) || preg_match('/^[a-f0-9]{64}$/', $value) !== 1) {
throw new limited_backoffice_exception('Expected revision is invalid.', 400, [
'message' => 'Expected revision is invalid.',
'code' => 'pricing_revision_invalid',
]);
}
return $value;
}
private function lockDepartment(int $departmentId): void
{
global $db;
$result = $db->query(
'SELECT `id` FROM `departments` WHERE `id` = ' . (int)$departmentId . ' FOR UPDATE'
);
if (!$result || $result->num_rows < 1) {
throw new limited_backoffice_exception('Department not found', 404);
}
}
private function revisionConflict(string $currentRevision): limited_backoffice_exception
{
return new limited_backoffice_exception('Pricing has changed. Reload and try again.', 409, [
'message' => 'Pricing has changed. Reload and try again.',
'code' => 'pricing_revision_conflict',
'current_revision' => $currentRevision,
]);
}
private function assertDepartmentProduct(int $departmentId, int $productId): void
{
global $db;
$result = $db->query(
'SELECT p.`id`
FROM `department_categories` dc
INNER JOIN `products` p ON p.`category` = dc.`category_id`
WHERE dc.`department_id` = ' . (int)$departmentId . '
AND dc.`deleted_at` IS NULL
AND p.`id` = ' . (int)$productId . '
LIMIT 1'
);
if (!$result || $result->num_rows < 1) {
throw new limited_backoffice_exception('Product is not available for this department.', 400);
}
}
private function assertDepartmentCategory(int $departmentId, string $categoryId): void
{
global $db;
$categoryId = $db->escape_string($categoryId);
$result = $db->query(
"SELECT `id`
FROM `department_categories`
WHERE `department_id` = " . (int)$departmentId . "
AND `deleted_at` IS NULL
AND `category_id` = '{$categoryId}'
LIMIT 1"
);
if (!$result || $result->num_rows < 1) {
throw new limited_backoffice_exception('Category is not available for this department.', 400);
}
}
}
@@ -76,11 +76,13 @@ class department_daily_report_complaints_schema_bootstrap
dimension INT NOT NULL DEFAULT 0,
branding INT NOT NULL DEFAULT 0,
visible TINYINT(1) NOT NULL DEFAULT 1,
archived TINYINT(1) NOT NULL DEFAULT 0,
longitude DECIMAL(10,7) NOT NULL DEFAULT 0,
latitude DECIMAL(10,7) NOT NULL DEFAULT 0,
order_priority INT NOT NULL DEFAULT 0,
created_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
updated_at DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_departments_archived (archived)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
@@ -302,10 +302,10 @@ class department_outside_hours_statistics_service
* @param array<int,array<string,mixed>> $opening_hours_by_department_id
* @param array<string,array<int,bool>>|null $missing_lookup_by_day
* @return array{
* counted:bool,
* reason:string,
* candidate_date:?string,
* department_id:int
* counted: bool,
* reason: string,
* candidate_date: ?string,
* department_id: int
* }
*/
public function classifyCandidateAgainstOpeningHours(
@@ -0,0 +1,252 @@
<?php
namespace classes;
require_once WD . '/classes/selfserve_schema_bootstrap.php';
use Exception;
class department_wash_count_service
{
/**
* @throws Exception
*/
public function countInDateRange(string $date_start, string $date_end, int $department_id): int
{
$rows = $this->countByHourForDepartments($date_start, $date_end, [$department_id]);
$total = 0;
foreach ($rows as $row) {
$total += (int)($row['wash_count'] ?? 0);
}
return $total;
}
/**
* @param array<int|string> $department_ids
* @return array<int,array{department_id:int,hour_bucket:string,wash_count:int}>
* @throws Exception
*/
public function countByHourForDepartments(string $date_start, string $date_end, array $department_ids): array
{
global $db;
$this->validateDateRange($date_start, $date_end);
$normalized_department_ids = $this->normalizeIds($department_ids);
if ($normalized_department_ids === []) {
return [];
}
selfserve_schema_bootstrap::ensureTables();
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$candidate_sql = $this->candidateUnionSql($department_ids_sql, $escaped_start, $escaped_end);
$sql = "SELECT deduped.department_id,
DATE_FORMAT(deduped.counted_at, '%Y-%m-%d %H:00:00') AS hour_bucket,
COUNT(*) AS wash_count
FROM (
SELECT dedupe_key,
department_id,
MIN(counted_at) AS counted_at
FROM ($candidate_sql) candidates
GROUP BY dedupe_key, department_id
) deduped
GROUP BY deduped.department_id, DATE_FORMAT(deduped.counted_at, '%Y-%m-%d %H:00:00')
ORDER BY deduped.department_id ASC, hour_bucket ASC";
$result = $db->query($sql);
if (!is_object($result) || $result->num_rows === 0) {
return [];
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = [
'department_id' => (int)($row['department_id'] ?? 0),
'hour_bucket' => (string)($row['hour_bucket'] ?? ''),
'wash_count' => (int)($row['wash_count'] ?? 0),
];
}
return $rows;
}
/**
* @param array<int|string> $department_ids
* @return array{quantity:int,products:int,earnings:int,washes:int}
* @throws Exception
*/
public function transactionSummary(string $date_start, string $date_end, array $department_ids): array
{
global $db;
$this->validateDateRange($date_start, $date_end);
$normalized_department_ids = $this->normalizeIds($department_ids);
if ($normalized_department_ids === []) {
return [
'quantity' => 0,
'products' => 0,
'earnings' => 0,
'washes' => 0,
];
}
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$sql = "SELECT COUNT(DISTINCT o.id) AS quantity,
COALESCE(SUM(oi.quantity), 0) AS products,
COALESCE(SUM(oi.price * oi.quantity), 0) AS earnings
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.department_id IN ($department_ids_sql)
AND o.created_at BETWEEN '$escaped_start' AND '$escaped_end'
AND o.deleted_at IS NULL
AND oi.deleted_at IS NULL";
$result = $db->query($sql);
$row = is_object($result) ? $result->fetch_assoc() : null;
return [
'quantity' => (int)($row['quantity'] ?? 0),
'products' => (int)($row['products'] ?? 0),
'earnings' => (int)round((float)($row['earnings'] ?? 0)),
'washes' => $this->countRows($date_start, $date_end, $normalized_department_ids),
];
}
/**
* @param array<int|string> $department_ids
* @return array<int,array{id:int,department_id:int,created_at:string}>
* @throws Exception
*/
public function listTransactions(string $date_start, string $date_end, array $department_ids): array
{
global $db;
$this->validateDateRange($date_start, $date_end);
$normalized_department_ids = $this->normalizeIds($department_ids);
if ($normalized_department_ids === []) {
return [];
}
selfserve_schema_bootstrap::ensureTables();
$department_ids_sql = implode(',', $normalized_department_ids);
$escaped_start = $db->escape_string($date_start);
$escaped_end = $db->escape_string($date_end);
$candidate_sql = $this->candidateUnionSql($department_ids_sql, $escaped_start, $escaped_end);
$sql = "SELECT CAST(SUBSTRING_INDEX(GROUP_CONCAT(entity_id ORDER BY source_priority ASC, entity_id ASC), ',', 1) AS UNSIGNED) AS id,
department_id,
MIN(counted_at) AS created_at
FROM ($candidate_sql) candidates
GROUP BY dedupe_key, department_id
ORDER BY created_at ASC";
$result = $db->query($sql);
if (!is_object($result) || $result->num_rows === 0) {
return [];
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = [
'id' => (int)($row['id'] ?? 0),
'department_id' => (int)($row['department_id'] ?? 0),
'created_at' => (string)($row['created_at'] ?? ''),
];
}
return $rows;
}
/**
* @param array<int> $department_ids
* @throws Exception
*/
private function countRows(string $date_start, string $date_end, array $department_ids): int
{
$rows = $this->countByHourForDepartments($date_start, $date_end, $department_ids);
$total = 0;
foreach ($rows as $row) {
$total += (int)($row['wash_count'] ?? 0);
}
return $total;
}
private function candidateUnionSql(string $department_ids_sql, string $escaped_start, string $escaped_end): string
{
return "SELECT CONCAT('order:', o.id) AS dedupe_key,
o.id AS entity_id,
o.department_id,
o.created_at AS counted_at,
0 AS source_priority
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.department_id IN ($department_ids_sql)
AND o.created_at BETWEEN '$escaped_start' AND '$escaped_end'
AND o.deleted_at IS NULL
AND oi.deleted_at IS NULL
AND p.is_wash = 1
UNION ALL
SELECT CASE
WHEN linked_o.id IS NOT NULL THEN CONCAT('order:', linked_o.id)
ELSE CONCAT('selfserve:', s.id)
END AS dedupe_key,
CASE
WHEN linked_o.id IS NOT NULL THEN linked_o.id
ELSE s.id
END AS entity_id,
COALESCE(linked_o.department_id, s.department_id) AS department_id,
COALESCE(linked_o.created_at, s.completed_at) AS counted_at,
1 AS source_priority
FROM selfserve_wash_sessions s
LEFT JOIN orders linked_o
ON linked_o.id = s.order_id
AND linked_o.deleted_at IS NULL
WHERE COALESCE(linked_o.department_id, s.department_id) IN ($department_ids_sql)
AND COALESCE(linked_o.created_at, s.completed_at) BETWEEN '$escaped_start' AND '$escaped_end'
AND s.deleted_at IS NULL
AND s.completed_at IS NOT NULL
AND UPPER(TRIM(s.status)) = 'COMPLETED'";
}
/**
* @param array<int|string> $ids
* @return array<int>
*/
private function normalizeIds(array $ids): array
{
$normalized = [];
foreach ($ids as $id) {
$value = (int)$id;
if ($value > 0) {
$normalized[$value] = $value;
}
}
return array_values($normalized);
}
/**
* @throws Exception
*/
private function validateDateRange(string $date_start, string $date_end): void
{
if (strtotime($date_start) === false || strtotime($date_end) === false) {
throw new Exception('Invalid date range provided');
}
if (strtotime($date_start) > strtotime($date_end)) {
throw new Exception('The start date cannot be after the end date');
}
}
}
@@ -0,0 +1,97 @@
<?php
namespace classes;
/**
* Ensures additive schema for department lifecycle metadata.
*/
class departments_schema_bootstrap
{
private static bool $initialized = false;
private const ARCHIVED_INDEX = 'idx_departments_archived';
public static function ensureTables(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
if (!self::tableExists($db, 'departments')) {
return;
}
if (!self::columnExists($db, 'departments', 'archived')) {
$db->query(
"ALTER TABLE departments
ADD COLUMN archived TINYINT(1) NOT NULL DEFAULT 0
AFTER visible"
);
}
if (!self::columnExists($db, 'departments', 'custom_pricing_only')) {
$db->query(
"ALTER TABLE departments
ADD COLUMN custom_pricing_only TINYINT(1) NOT NULL DEFAULT 0
AFTER archived"
);
}
if (!self::indexExists($db, 'departments', self::ARCHIVED_INDEX)) {
$db->query(
"ALTER TABLE departments
ADD INDEX " . self::ARCHIVED_INDEX . " (archived)"
);
}
self::$initialized = true;
}
private static function tableExists(object $db, string $table): bool
{
$table = self::escapeIdentifier($table);
$result = $db->query("SHOW TABLES LIKE '{$table}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function columnExists(object $db, string $table, string $column): bool
{
$table = self::escapeIdentifier($table);
$column = self::escapeIdentifier($column);
$result = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function indexExists(object $db, string $table, string $index): bool
{
$table = self::escapeIdentifier($table);
$index = self::escapeIdentifier($index);
$result = $db->query("SHOW INDEX FROM `{$table}` WHERE Key_name = '{$index}'");
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
return false;
}
return (int)$result->num_rows > 0;
}
private static function escapeIdentifier(string $value): string
{
return str_replace(['\\', "'", '`'], ['\\\\', "\\'", ''], $value);
}
}

Some files were not shown because too many files have changed in this diff Show More