Commit graph

5 commits

Author SHA1 Message Date
27ce0f5b60 Dashboard: the family config, set once and handed to new accounts
Three things, and the third one I first said was impossible.

Nový účet takes the full width — a 420px column inside a full-width panel
left two thirds of the row empty. Three across on a desktop, one on a
phone.

Rodinné nastavení edits `onboarding_template`: the shared sources a new
member's first setup pulls down. The values are plaintext credentials — a
prehraj.to login and a TorBox key — so they are masked behind a reveal
toggle, and this stays a superuser-only noindex page. It PATCHes the one
record and never creates a second; a second row would make the hook's
`-updated` ordering silently decide which config the family gets.

"Použít rodinné nastavení" pre-loads that config onto the account being
created, so the new person never types a family code. I claimed this could
not work because `addon_config` is keyed by profile and a fresh account has
none. The owner pushed back, correctly: the old wizard needed a profile for
the same reason and simply could not make one, whereas this page can — and
`CloudSyncService._findOrCreateDefaultProfile` looks for an existing
profile BEFORE creating one, explicitly "so a second device signing into an
account with data doesn't make a duplicate". A profile made here is adopted
as the default on first sign-in. The other two obstacles were never real:
the template is superuser-readable, and the password is available because
this page just chose it.

The crypto is lifted unchanged from index.html and verified rather than
trusted: the page's own functions, run against
amber-app/test/_fixtures/pbkdf2_vector.json — the same fixture
addon_config_crypto_interop_test pins the Dart side to — derive the
identical key, and a sealed blob round-trips. One wrong iteration count
would have made a new member's app fail to decrypt, which presents as a
password reset rather than as a bug.

The account is created BEFORE the pre-load, and a failed pre-load says so
in warning colour rather than reporting success or rolling back: a
half-made account you were not told about is the worst outcome of the
three.
2026-08-09 01:23:53 +02:00
80c4bd498c The accounts table needed the whole width
Caught in a screenshot, invisible in the source: sharing a grid row with
the create form left the five-column table about half the page, so every
date broke as "2026-" / "08-05", the action button wrapped to two lines,
and a long address split mid-word. A wrapped date reads as two dates.

Full width, the address takes the slack, and the four columns that must
never wrap say so. Verified by counting line boxes per cell rather than
by eye — my first check compared cell HEIGHTS and flagged all five rows,
which was just the button's own height.
2026-08-08 23:33:22 +02:00
449b1e75e0 Dashboard: read the reports, and create accounts without the admin UI
Two views on status.html, and one migration.

Hlášení: one incident per card rather than a table row, because a report
carries fifteen numbers and the job is to read one incident, not scan a
column. The reporter's address is resolved from the `user` relation in the
browser — the app never writes identity onto telemetry rows. The box is
named. Our own test sessions are HIDDEN by default, since the question
this view answers is what the family experienced; `tester` OR an emulator
tell OR a test address, so it stays right for rows written before the
column existed. Day-range filter, and every field says "neměří se" rather
than 0 when that player does not send it — a different claim from zero,
and the TV sends everything while mpv sends part.

Správa: create an account and flip clean/18+. Registration stays CLOSED —
`users.createRule` is still null and this is a plain POST with the
superuser token the page has held since it was written, so it grants
nothing that was not already available in the PocketBase admin UI. The
password field is deliberately visible: you are making it for someone
else and have to read it out.

client_logs.gitSha: which BUILD wrote a row, not which version. Fourth
outing for the silent-drop lesson, and written alongside the client change
rather than discovered after.

Verified against real data before deploying, by splicing production rows
into the shipped page as fixtures and driving it in a browser: seven
reports render with the right reporter and box, the day filter and the
test toggle both work, prefs persist, and the create form's guard fires.
That caught a JS syntax error (a Czech quote closed with an ASCII one
inside a double-quoted string, which would have taken the whole dashboard
down — PocketBase serves this file with no build step) and a missing space
that rendered "ďáblashlédnuto".
2026-08-08 23:29:51 +02:00
48a88e6936 Dashboard: error history, and which publish owns it
GET /api/status/errors plus a panel: 14 daily bars (records vs errors), then
breakdowns by version, platform and flavour.

**Rate, not raw count, is what attributes errors to a publish.** Versions have
wildly different exposure -- right now 1.1.4+13 has 177 records to 1.1.5+14's 20 --
so raw counts would make whichever build the family used most look worst. Every
row carries total, errors and errors/total, coloured on the rate.

**Flavour is not recorded yet and the panel says so.** TelemetryService sends
appVersion, platform and device, but nothing carries clean-vs-adult. The endpoint
reads a top-level  field and then meta.flavor, so the day the app starts
sending it this fills in with no backend change; until then the row reads
'neznámá' with a note explaining why. No app change was made here -- that is left
noted, per the owner.

The window is 14 days because that is the retention ceiling: client_logs.pb.js
trims nightly, so nothing older exists to graph. Every day is pre-seeded so a
missing bar and a zero bar cannot look alike.

Inline SVG rather than a charting library -- this page is served by PocketBase with
no build step, and 14 bars do not justify a dependency. Validated on a clean local
boot: 14 bars, correct axis labels, no crash on empty data.
2026-08-05 21:34:42 +02:00
cb3f1407ff Operator status dashboard at /status.html
Answering "is Amber healthy?" meant opening Coolify, clicking per-app pages for
status, clicking again for logs, then querying PocketBase separately for what the
family is actually being served. This puts the five questions asked when something
breaks on one screen: is it up, did the last deploy succeed, what did it say, are
clients erroring, and is the newest release the one I think it is.

pb_hooks/status.pb.js serves GET /api/status and GET /api/status/logs, both
superuser-gated. Superuser rather than a user flag because there is no operator
field on `users` and inventing one needs a migration; nsfwEnabled was considered
and rejected, since using a content flag as an admin flag would let any
adult-enabled family member read infrastructure logs.

**This is the first hook in the project to make an outbound HTTP request** -- every
prior one read only PocketBase's own database. So each external call is wrapped to
degrade one panel instead of failing the response, and carries a short timeout so a
hung upstream cannot pin a worker. The Coolify token never reaches the browser;
that is the entire reason the endpoint exists rather than the page calling Coolify
directly.

Runtime container logs come from the Coolify API, not SSH. An earlier design
assumed otherwise and asked for VPS root; it is not needed.

Validated against the project's own pinned PocketBase 0.39.6 before committing,
because a broken pb_hooks file crashes PB at boot and would take family logins
with it: hooks load with no JS errors, both routes 401 unauthenticated, /api/status
executes every panel, the degradation path reports actionable "not configured"
messages, and with real credentials it enumerates all seven Amber services and
returns live amber-api vitals. The page was driven in a browser -- login, panels,
and a log fetch returning 1812 chars of real container output.

Two harness bugs found on the way, both mine: `--publicDir` does not exist in
0.39.6 (pb_public is resolved beside the executable), and `pkill -f "pocketbase
serve"` matches the pattern inside its own command line and killed the shell.
2026-08-05 21:17:05 +02:00