From 79c53b5f1a0277f8252c12b5086c6b017a1acb8a Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 8 Oct 2026 13:14:24 +0200 Subject: [PATCH] =?UTF-8?q?website:=20one=20picture=20viewer=20(assets/gal?= =?UTF-8?q?lery.js)=20for=20app=20and=20dashboard=20pictures=20=E2=80=94?= =?UTF-8?q?=20declared=20data-gallery=20sets,=20a=20click=20on=20the=20big?= =?UTF-8?q?=20picture=20closes,=20JS-off=20still=20opens=20the=20file;=20s?= =?UTF-8?q?ite=20gate=2020=20+=204=20decoys;=20demo-hp=20language=20read?= =?UTF-8?q?=20back=20(hu);=20R-908=20(old=20Resend=20key=20in=20homelab-ma?= =?UTF-8?q?nifests=20history);=20131=20->=20132?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb --- .claude/rules/website.md | 7 + STATUS.md | 1 + .../website-gallery-2026-10-08/decoys.txt | 16 + .../demo-hp-language.txt | 20 + documentation/backlog/OPEN-ITEMS.md | 3 +- scripts/CHANGELOG.md | 7 + scripts/site_gates.py | 31 +- scripts/test_gate_decoys.py | 19 +- website/404.html | 2 +- website/CHANGELOG.md | 14 + website/alkalmazasok.html | 526 ++++++------------ website/assets/gallery.js | 170 ++++++ website/assets/site.css | 49 +- website/biztonsagimentes.html | 2 +- website/en/apps.html | 526 ++++++------------ website/en/backups.html | 2 +- website/en/contact.html | 2 +- website/en/download.html | 2 +- website/en/faq.html | 2 +- website/en/index.html | 11 +- website/en/technology.html | 7 +- website/gyik.html | 2 +- website/index.html | 11 +- website/kapcsolat.html | 2 +- website/letoltes.html | 2 +- website/szolgaltatasok-nonpublic.html | 2 +- website/technologiak.html | 7 +- 27 files changed, 711 insertions(+), 734 deletions(-) create mode 100644 documentation/audits/website-gallery-2026-10-08/decoys.txt create mode 100644 documentation/audits/website-gallery-2026-10-08/demo-hp-language.txt create mode 100644 website/assets/gallery.js diff --git a/.claude/rules/website.md b/.claude/rules/website.md index 7acc629d..db76c350 100644 --- a/.claude/rules/website.md +++ b/.claude/rules/website.md @@ -48,6 +48,13 @@ household guest; method and privacy scan: `documentation/audits/website-dashboar release that visibly changes a pictured page (Launcher, Apps, an app's page, Backup → Apps) should refresh them.** Every caption is a claim (`captions-claims.md` there). Gate 19 keeps each page on its own language's pictures. +## The picture viewer + +**One viewer, in `assets/gallery.js`** — never an inline copy (gate 20). A picture set is DECLARED in the page: each +picture is an `` (further pictures of an app card are hidden links in the card); +a set is all links with the same name, in page order. **JavaScript off must still work** — the link opens the picture. +A page with openers loads `gallery.js?v=N`; bump N when the file changes. + ## The encoding fences - **All `website/` HTML is UTF-8 *with BOM*. Preserve it.** An editor that strips the BOM is a diff --git a/STATUS.md b/STATUS.md index 58461684..2e1066d6 100644 --- a/STATUS.md +++ b/STATUS.md @@ -59,6 +59,7 @@ Full designs: `documentation/audits/day-2026-10-08/`. - **A missing-page (404) page exists now.** - **The language link is now a globe icon**, like the dashboard's: a click shows „Magyar" and „English". - **The website now shows the dashboard**: four real pictures on the home page (and two on the technology page), Hungarian on Hungarian pages, English on English ones. Two small dashboard defects were filed. +- **One picture viewer for the whole site:** the dashboard pictures open like the app pictures, and a click on the big picture closes it. - **The contact form's lost program code was searched for everywhere I can reach: not found.** The running program is now saved in git, so it cannot be lost. A plan for a replacement is written. One question for you: is the February folder on your Windows computer? **Needs you:** two choices in `REPORT-website-refresh.md`. If nothing: SparkyFitness stays listed; the contact diff --git a/documentation/audits/website-gallery-2026-10-08/decoys.txt b/documentation/audits/website-gallery-2026-10-08/decoys.txt new file mode 100644 index 00000000..ef02ad12 --- /dev/null +++ b/documentation/audits/website-gallery-2026-10-08/decoys.txt @@ -0,0 +1,16 @@ +### site/opener-without-href rc=1 +mutation (website/en/apps.html): '' -> '' +FAIL: en/apps.html: a data-gallery opener that is not an : + +### site/opener-to-missing-file rc=1 +mutation (website/index.html): '' -> '' +FAIL: index.html: a data-gallery opener points at a missing file: /assets/dashboard-apps-hu-old.webp + +### site/openers-but-no-script rc=1 +mutation (website/en/technology.html): '' -> '' +FAIL: en/technology.html: 2 picture opener(s) (data-gallery) but no \n' +FAIL: alkalmazasok.html: an inline copy of the picture viewer — it lives only in assets/gallery.js + diff --git a/documentation/audits/website-gallery-2026-10-08/demo-hp-language.txt b/documentation/audits/website-gallery-2026-10-08/demo-hp-language.txt new file mode 100644 index 00000000..e7914ef1 --- /dev/null +++ b/documentation/audits/website-gallery-2026-10-08/demo-hp-language.txt @@ -0,0 +1,20 @@ +Read-only copy of the running hub's database (hub.db + -wal + -shm via kubectl exec cat), queried locally, 2026-10-08 ~11:15 UTC. +Columns printed only: ids, language, times, event types — no secret column. + +customer_configs (the household's stored language): [('demo-hp', 'hu', '2026-10-07 16:50:04')] + +reports.language from demo-hp's controller since 10:00 UTC (12:00 CEST): + ('2026-10-08 10:05:20', 'hu') + ('2026-10-08 10:20:18', 'hu') + ('2026-10-08 10:35:18', 'hu') + ('2026-10-08 10:37:34', 'hu') + ('2026-10-08 10:37:50', 'hu') + ('2026-10-08 10:38:16', 'en') + ('2026-10-08 10:38:45', 'hu') + ('2026-10-08 10:39:47', 'hu') + ('2026-10-08 10:40:03', 'hu') + ('2026-10-08 10:50:18', 'hu') + ('2026-10-08 11:05:18', 'hu') + +notification_log rows since 10:00 UTC, any customer: 0 +positive control — the newest notification_log rows (the query reads the table): [(1181, 'demo-felhom', 'kernel_notice', '2026-10-08 07:00:40'), (1180, 'Tester-2', 'expected_backup_missed', '2026-10-08 03:00:01'), (1179, 'Tester-2', 'expected_dbdump_missed', '2026-10-08 03:00:00')] diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index e41b8e52..94bfd448 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -191,7 +191,7 @@ stopping line that lies. | **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC | | **R-352** | Storage & devices | P4 | **Four screens state something untrue about where an app's data goes, and the configured default is consulted by nothing that places data.** Measured on `demo-hp` 2026-08-21. **(1)** **40 of 53** catalogue templates declare no data path (`grep -rl 'env_var: HDD_PATH' --include='.felhom.yml'` → 13; total 53); those apps get **no storage field and no default** — their data lands in a named Docker volume on the system drive. **(2)** `GetDefaultStoragePath()` has exactly **three** non-test callers — the metrics collector (`cmd/controller/main.go:410`), the dashboard SystemInfo panel (`web/server.go:733`) and `.fab` import landing (`handler_export_upload.go:154`). **The deploy route never reads it.** Its field comment `// new apps use this by default` (`internal/settings/settings.go:453`) has never been true — an invariant with no test pinning it. **(3)** The first-tier backup follows the data onto the same disk (`backup/backup.go:324-334` → `systemDataPath`), so data and nearest copy share one device for a customer doing nothing wrong — the posture Tier 2 refuses outright at `tier2.go:329`. **(4)** „1 alkalmazás használja" on the Drives page counts only `Env["HDD_PATH"] == path` (`web/handlers.go:2118`), so it can never include the 40-class; it truthfully means *„1 of the apps that CAN use a drive does"*. | **NARROWED** — **PARTLY CLOSED 2026-08-21** — visibility shipped; **placement OPEN** | — | **Shipped tonight (visibility only, no placement change, nothing migrated):** the deploy page now states where the app's data will live before the button is pressed, naming the system drive for the 40-class and the selected drive for the 13. **⚠ RE-FRAMED 2026-08-22 — THE FOUR MEASUREMENTS STAND; TWO OF THE CONCLUSIONS DRAWN FROM THEM DO NOT.** (1) is a measurement and is correct, but "those apps get no storage field and no default" is not a deprivation: the architecture places **hot** data (DB/config/cache) on fast storage inside the guest and states that placement is **ENFORCED** (`documentation/architecture/01-topology-and-trust.md:150-152`). The 40 are all-hot apps; the 13 are the ones with **bulk** content, which belongs on an attached drive. There is no choice being denied. (3) **overstated one risk and understated a distinction.** Since R-165 the guest carries a small OS rootfs plus **ONE** data volume at `/var/lib/felhom`; `/var/lib/docker` and `/mnt/sys_drive` are two **binds of that same volume** (`felhom-agent/configs/build-golden.sh:29-40, 99`) — the `mp0`/`mp1` split assumed here was retired 2026-08-03. **Real risk:** a physical-disk failure loses the data and its first-tier copy together — which is what the off-site and whole-machine tiers exist for, and which is equally true of a drive-resident app whose unit sits beside its data by design. **Overstated risk:** a full data volume stopping the operating system — the OS rootfs is a separate volume and the capture floor refuses per app before exhaustion (`00-capability-map.md:94`), watched working 2026-08-21 with the volume at 99% and all 15 containers healthy. **The comparison to Tier 2's same-disk refusal (`tier2.go:329`) is withdrawn:** Tier 2 refuses a SECOND copy on the same disk; Tier 1's unit is meant to sit beside the data. **(2) and (4) are untouched and remain correct** — (2) is now filed on its own as **R-368** with its scope measured, and (4) needs no ruling: the count is honest and only easy to misread. **The specification for the rest is filed at `documentation/backlog/SPEC-app-data-placement-2026-08-21.md`** (corrected 2026-08-22, framing marked inline, measurements kept) and lists the five points a ruling must settle (compose-template vs controller, existing deployments, when the SSD is legitimately right, `IsDefault` must become true or go away *with a test*, and the Drives-page count). **An earlier recommendation to refuse deployment until a drive is registered was WITHDRAWN** — it assumed the customer had failed to choose; they had no choice to make. | **Viktor rules**, CC executes | -## Security & access — 14 rows (P2 1, P3 11, P4 2) +## Security & access — 15 rows (P2 1, P3 12, P4 2) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -207,6 +207,7 @@ stopping line that lies. | **R-782** | Security & access | P3 | **[P3-LOW] Two side observations of the R-753 sweep, inferred, not measured:** glance's seeded `glance.yml` has no `auth:` block (the dashboard is public to anyone with the address), and homepage's `/api/*` refuses a Host not in `HOMEPAGE_ALLOWED_HOSTS`, which the template does not set (widgets may 400). **Needs:** measure both on 9202; glance: decide whether a public link dashboard is intended (the setup gate does not cover it after setup). | **READY — rank P3-LOW; owner: CC (catalog)** **2026-10-06 night: homepage fixed on catalog branch `night-held-2026-10-06`** (`093e5ed`): `HOMEPAGE_ALLOWED_HOSTS=${SUBDOMAIN}.${DOMAIN}`; bench: `/api/services` 400 → 200. Glance measured: public (`GET /` 200 with no login, no auth block, `/login` 404) — whether it gets a login is the operator's question; the row stays open for it (`cat/R-782-red.txt`). | — | — | CC | | **R-831** | Security & access | P3 | **The Hetzner storage API token (`HETZNER_TOKEN`, the storage project's token in `Secret/storagebox`) was printed into the 2026-10-03 session transcript** — CC read the gitignored `manifests/storagebox.secret.yaml` and its redaction pattern missed the quoted value. It can create, reset and delete Storage Box sub-accounts. Not rotated by the operator's choice (decision 73). **Rotation, whenever chosen (3 steps):** create a new token in the storage project in the Hetzner console → patch `Secret/storagebox` key `HETZNER_TOKEN` in `felhom-system` and `kubectl rollout restart deployment/hub` → delete the old token in the console. Rule for sessions: never print a file that holds secrets — read the one field needed. | **WAITING-ON-OPERATOR — rotation is his call** **Not rotated by the operator's rulings (2026-10-04 „keep using the current one"; 2026-10-05 option B) — restated 2026-10-05 18:23; the steps stay here.** | — | rotate when chosen | operator | | **R-870** | Security & access | P3 | **Tester 1's two Cloudflare credentials — the zone API token (`infrastructure.cf_api_token`) and the tunnel token (`infrastructure.cf_tunnel_token`) of the hub's `customer_configs` row `tester-1` — were printed into the 2026-10-04 night session's transcript** (not into any file): a read-only query selected `substr(config_json,1,400)`, and both values sit in the first 400 characters. Tester 1 is CC's disposable test customer (`enkicsifelhom.hu`). **Not rotated, by the operator's ruling of 2026-10-05 06:49 (option B).** **Rotation, whenever chosen (3 steps):** in the Cloudflare dashboard create a new API token for the `enkicsifelhom.hu` zone with the same permissions and refresh the Tester 1 tunnel's token (Zero Trust → Networks → Tunnels → the tunnel → refresh token) → hub → Configs → `tester-1` → Edit → the two Cloudflare fields → Save, then confirm on the box that cloudflared reconnected (`docker ps` health `healthy`) → delete the old API token. Rule for sessions (as R-831): never select a whole config row — name the fields, and never `config_json` without `json_extract` of a non-secret field. | **WAITING-ON-OPERATOR — rotation is his call (ruled: not now)** **Not rotated by the operator's rulings (2026-10-04 „keep using the current one"; 2026-10-05 option B) — restated 2026-10-05 18:23; the steps stay here.** | — | rotate when chosen | operator | +| **R-908** | Security & access | P3 | **An old Resend API key is still in the history of the `homelab-manifests` repository; it was replaced on 2026-06-29, but whether it is also revoked at Resend is unknown.** FOUND 2026-10-08 by the contact-mailer source search (`audits/mailer-source-2026-10-08/SEARCH.md`, „A side finding"): commit c9648cd (2026-02-05, „added mailer pod") put a key literal in the old `felhom-system/contact-mailer.yaml` comment; the file was deleted in ee93b50 but the history keeps it. Compared by hash only, never printed: it is NOT today's key (`Secret/resend-api`, rotated 2026-06-29, felhom.eu feea0606). If the old key still works at Resend, anyone with read access to that repository can send mail as felhom.eu. | **OPEN — owner: operator** | — | In the Resend console, check that the old key is revoked (revoke it if not); rewriting the repository's history is NOT needed once it is revoked | operator | | **R-525** | Security & access | P4 | **[P3-LOW] FileBrowser has its own login; putting it behind the dashboard session (traefik forwardAuth or Quantum proxy auth) is a new mechanism nobody has measured.** Filed 2026-09-15 by the P1-fixes task (B.5). R-513 closed the default-password hole with a generated password; a household still has two logins. **What it needs:** a spike on a scratch guest — forwardAuth to the controller session, and what FileBrowser Quantum does with a trusted header. | **READY — rank P3-LOW; owner: CC (spike)** **Re-ranked 2026-10-03: P3->P4: comfort feature needing a new unmeasured mechanism; the default password hole is closed.** | — | — | CC | | **R-779** | Security & access | P4 | **[P3-LOW] Part A's "two outside addresses seen as two" is proven through the simulated tunnel only; on the REAL tunnel the second outside address (ep0, one request allowed) was refused by Cloudflare's edge with 403 and never reached the box.** Measured 2026-10-01 19:51 UTC (`audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt`): no log line on demo-hp; demo-hp's box has no geo restriction in its settings, so a Cloudflare ZONE rule (country or bot, not read) refused a German datacenter address. DooPlex's own address on the real tunnel was seen as itself. **Needs:** one sign-in from a second Hungarian address (the operator's phone off wifi) while DooPlex is locked out — 2 minutes; and say which Cloudflare rule refused ep0. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (a phone), CC reads the logs** **Re-ranked 2026-10-03: P3→P4: a proof gap on the real tunnel; operator-only follow-up.** | — | — | CC + operator | | **R-904** | Security & access | P4 | **Cloudflare can read every household's app traffic; replacing it with our own relay is a later item.** Facts (reviewer discussion 2026-10-08; `01`): app traffic and the dashboard reach the box through the Cloudflare Tunnel (`01` §5 trust table, rows end-user ↔ apps and customer ↔ controller UI; §7), and the tunnel's public end is Cloudflare's edge, where TLS ends — so Cloudflare can technically read that traffic (the FAQ says so since 2026-10-08, R-900; „TLS ends at the edge" is not written in `01` — add it there). What Cloudflare gives today, free: inbound reach with no router setup, the CGNAT answer (`01` §4, §7); certificates (`01` §7, the free tier covers one level below a zone); the geo-WAF the hub enforces (`01` §5 last row, §7); flood protection (not in `01`). The alternative named: our own EU relay over WireGuard with TLS passthrough by SNI, certificates on the box, the geo-block on the relay. Its costs: one more machine the operator keeps up, and a single point of reach for every box; weaker flood protection; about a week of work after a spike. Operator ruling 2026-10-08 09:07 (`09` §3 decision 184): a later item. | **DEFERRED — after the first customers (operator ruling 2026-10-08 09:07)** | — | A spike after the first customers (the relay's reach, cost and flood behaviour, measured) | operator | diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 3ca9d00b..0160105f 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,3 +1,10 @@ +## gates — site gate 20: the one picture viewer (2026-10-08) + +- Every `data-gallery` opener must be an `` to an existing file under `website/assets/`; a page with openers must + load `/assets/gallery.js?v=N`, and a page that loads it must have openers; „Screenshot Gallery / Lightbox" (the old inline + viewer) may appear on no page. Four decoys: `site/viewer-opener-without-href`, `viewer-opener-missing-file`, + `viewer-openers-no-script`, `viewer-inline-copy-back`, each with its failure line (`must=`). + ## gates — site gate 19: dashboard pictures in the page's own language (2026-10-08) - `site_gates.py` gate 19: every `/assets/dashboard-*-(hu|en).webp` on a page must match the page's language set. diff --git a/scripts/site_gates.py b/scripts/site_gates.py index 4932e69f..2808c995 100644 --- a/scripts/site_gates.py +++ b/scripts/site_gates.py @@ -29,6 +29,8 @@ Gates (all must pass; non-zero exit on any failure): 17. faq-ld — each FAQ page's FAQPage JSON-LD carries exactly its visible questions and answers 18. tail — nothing after (a stray file-dump summary sat visible below technologiak.html) 19. dash-lang — a page shows dashboard pictures only in its own language (`dashboard-*-hu.webp` / `-en.webp`) + 20. viewer — data-gallery openers are to existing assets; gallery.js (with ?v=) exactly where openers are; + no inline viewer copy """ import html as _html, io, json, os, re, sys, unicodedata @@ -452,8 +454,35 @@ for p, s in pages.items(): fail("%s: a %s dashboard picture (%s) on a %s page — use the page's own language" % (p, m.group(1), m.group(0), want)) print(" dashboard pictures: %d references, each in its page's language" % _dash_n) +# gate 20: the one picture viewer (2026-10-08). A set is declared in the page: every opener is an to a file +# that exists under website/assets/, carrying data-gallery. A page with openers loads assets/gallery.js with a ?v=; a +# page that loads it has openers; the old inline viewer script appears on no page. +_OPENER_RE = re.compile(r"<(\w+)\b[^>]*\bdata-gallery=\"[^\"]*\"[^>]*>") +_GJS_RE = re.compile(r'') +_open_n = 0 +for p, s in pages.items(): + openers = [m.group(0) for m in _OPENER_RE.finditer(s)] + _open_n += len(openers) + js = _GJS_RE.findall(s) + if openers and not js: + fail("%s: %d picture opener(s) (data-gallery) but no \n', ""), + must="en/technology.html: 2 picture opener(s) (data-gallery) but no") +decoy("site/viewer-inline-copy-back", "site_gates.py", + replace_in(os.path.join(_WEB, "alkalmazasok.html"), "", + " \n"), + must="alkalmazasok.html: an inline copy of the picture viewer") decoy("site/lang-attr", "site_gates.py", replace_in(_EN("backups.html"), '', ''), must="the English set needs lang='en'") diff --git a/website/404.html b/website/404.html index ae778ca4..639c4c9e 100644 --- a/website/404.html +++ b/website/404.html @@ -6,7 +6,7 @@ Az oldal nem található | Felhom.eu - + diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 1b75812d..0ee4e4da 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,3 +1,17 @@ +## 2026-10-08 (late afternoon) — one picture viewer for the whole site + +- **`assets/gallery.js`** — the viewer that lived twice inline on the two apps pages is one shared file now (`defer`, + `?v=1`), its markup built by the script. The dashboard pictures (home and technology, both languages) open in it too, + as one set: Launcher → Apps → an app → Backups. +- **A set is declared, not guessed:** every picture is a real ``; an app + card's further pictures are hidden links in the card. The old probing for `-screenshot-N.webp` files is gone (no + request for a picture that is not in the page). JavaScript off: the link opens the picture, as before. +- **New: a click or tap on the big picture closes the viewer** (the close button, Esc and a click beside it still do); + a swipe changes the picture and does not close. Labels follow ``; focus goes to the viewer and back to the + clicked picture; the page does not scroll behind it. +- `site.css`: the `.lightbox-…` rules are no longer scoped to the apps page; `?v=4` → `?v=5` on all sixteen pages. +- Gate 20 (`scripts/CHANGELOG.md`). + ## 2026-10-08 (afternoon) — the dashboard, shown - **A new section after „Mit tud a doboz ma?" / „What the box does today"**: „Így néz ki a vezérlőpult" / „What the diff --git a/website/alkalmazasok.html b/website/alkalmazasok.html index a5ce432f..13cf1811 100644 --- a/website/alkalmazasok.html +++ b/website/alkalmazasok.html @@ -43,8 +43,9 @@ } - + + @@ -111,7 +112,9 @@