diff --git a/configs/systemd/user/dossiers-ngi-branch-status.service b/configs/systemd/user/dossiers-ngi-branch-status.service index a6ad491..4a65aad 100644 --- a/configs/systemd/user/dossiers-ngi-branch-status.service +++ b/configs/systemd/user/dossiers-ngi-branch-status.service @@ -11,6 +11,8 @@ Environment=TZ=Europe/Zurich Environment=ADMIN_LOG=%h/src/koopa/koopa-admin-log # Override on koopa if the tdpd checkout path differs Environment=TDPD_ROOT=%h/git/src/ngi/tech-dossier-project-data +# Live site root (timer must refresh public JSON, not only the kal tree) +Environment=OUT=/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch/branch-status.json EnvironmentFile=-%h/.config/dossiers-ngi/branch-status.env ExecStart=/usr/bin/python3 %h/src/koopa/koopa-admin-log/scripts/dossiers-ngi/branch-status-hourly.py SuccessExitStatus=0 1 diff --git a/scripts/dossiers-ngi/README.md b/scripts/dossiers-ngi/README.md index 11da294..1b1131d 100644 --- a/scripts/dossiers-ngi/README.md +++ b/scripts/dossiers-ngi/README.md @@ -14,10 +14,12 @@ SoT for the blank cascade landing on **dossiers.ngi.hacktivism.ch** | Var | Default | Meaning | |-----|---------|---------| | `TDPD_ROOT` | `$HOME/git/src/ngi/tech-dossier-project-data` | tech-dossier-project-data checkout for tip SHAs | +| `OUT` | `sites/dossiers.ngi.hacktivism.ch/branch-status.json` under kal | write path for JSON (live www on koopa) | | `ADMIN_LOG` | (script derives from `__file__`) | koopa-admin-log root | On koopa the clone often lives at `%h/src/koopa/koopa-admin-log`; set -`TDPD_ROOT` in the service `Environment=` if the tdpd path differs. +`TDPD_ROOT` and `OUT` in `~/.config/dossiers-ngi/branch-status.env` when the +tdpd path or live www path differs from the laptop defaults. ## Manual run @@ -40,3 +42,47 @@ Hourly JSON can sit next to the HTML once the timer is installed on koopa Hourly builder belongs in **kal** (this tree). Do **not** put `scripts/branch-status-hourly.py` back into tdpd. + +## Sticky cascade bar + +Stock Caddy `templates` renders `{% include "_cascade/bar.html" . %}` on each +HTML page. The include path has no leading slash. The dot passes the request, +so `.Req.URL.Path` marks the active link with `class="is-active"` and +`aria-current="page"`. Delimiters are `{%` `%}`, so a later `{{ }}` in real +dossier HTML stays literal. + +The bar lists cascade `/` plus the five branch paths. Seeded tips match +`branch-status.json`. `/_cascade/bar.js` refreshes `[data-tip]` from that JSON +(`cache: no-store`). CSS is `/_cascade/bar.css` (`position: sticky`). + +Enable templates before rsync. A page without the include does not grow the +bar. `{%` without the templates directive is shown as raw text. + +Templates apply is **host-local on koopa** (`~/bin/apply-dossiers-ngi-templates.sh`), +not tracked in this repo. It edits `~/koopa-caddy/Caddyfile` only (this host; +keeps `header Alt-Svc "clear"` and any `basic_auth`; leaves `.2` / `.3` +untouched). Then reload with the usual home→`/etc` helper: + +```bash +bash ~/bin/apply-dossiers-ngi-templates.sh +sudo ~/bin/caddy-apply +``` + +The stanza body for hand-check / re-seed lives in +`scripts/dossiers-ngi/caddy-dossiers-ngi.snippet`. +`scripts/caddy/caddy-apply.sh --snippet` leaves an existing block unchanged. +Leave `scripts/caddy/apply-goa-wdGet-full-replace.sh` and +`scripts/caddy/apply-dossiers-ngi-basicauth.sh` unused. No response-body +replace plugin. + +After validate and reload succeed, rsync from the laptop (Freigabe): + +```bash +rsync -avz --delete --exclude .DS_Store \ + "$HOME/git/admin-logs/local/koopa-admin-log/sites/dossiers.ngi.hacktivism.ch/" \ + hernani@koopa:/var/www/monitoring-sites/dossiers.ngi.hacktivism.ch/ +``` + +`--delete` removes `_cascade/` on the server when the source tree is an older +copy. The bar does not finish the dossier site. This section does not install +the hourly timer. diff --git a/scripts/dossiers-ngi/branch-status-hourly.py b/scripts/dossiers-ngi/branch-status-hourly.py index e121273..4507313 100755 --- a/scripts/dossiers-ngi/branch-status-hourly.py +++ b/scripts/dossiers-ngi/branch-status-hourly.py @@ -21,9 +21,14 @@ from datetime import datetime, timezone from pathlib import Path ADMIN_LOG = Path(__file__).resolve().parents[2] -OUT = ADMIN_LOG / "sites" / "dossiers.ngi.hacktivism.ch" / "branch-status.json" +_DEFAULT_OUT = ADMIN_LOG / "sites" / "dossiers.ngi.hacktivism.ch" / "branch-status.json" HOST = "https://dossiers.ngi.hacktivism.ch" + +def out_path() -> Path: + raw = os.environ.get("OUT", "").strip() + return Path(raw).expanduser() if raw else _DEFAULT_OUT + # Default local clone; override with TDPD_ROOT (koopa may use a different path). DEFAULT_TDPD = Path.home() / "git" / "src" / "ngi" / "tech-dossier-project-data" @@ -76,9 +81,10 @@ def main() -> int: for b in branches ], } - OUT.parent.mkdir(parents=True, exist_ok=True) - OUT.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") - print(f"wrote {OUT}", file=sys.stderr) + dest = out_path() + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + print(f"wrote {dest}", file=sys.stderr) return 0 diff --git a/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet new file mode 100644 index 0000000..1418403 --- /dev/null +++ b/scripts/dossiers-ngi/caddy-dossiers-ngi.snippet @@ -0,0 +1,19 @@ +# Stanza body for dossiers.ngi.hacktivism.ch only (hand-check / re-seed). +# Templates apply is host-local on koopa (not in this repo): +# bash ~/bin/apply-dossiers-ngi-templates.sh +# sudo ~/bin/caddy-apply +# +# Keep the existing line: header Alt-Svc "clear" +# Keep any existing basic_auth in that same block. +# scripts/caddy/caddy-apply.sh --snippet reports an existing block and +# does not merge these lines. Leave that script unused for this change. +# Leave scripts/caddy/apply-goa-wdGet-full-replace.sh unused. +# Leave scripts/caddy/apply-dossiers-ngi-basicauth.sh unused. +# Stock Caddy templates only. No replace-response plugin. +# +# Tab-indented lines inserted after header Alt-Svc: + + templates { + mime text/html + between "{%" "%}" + } diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css new file mode 100644 index 0000000..0f9e1cc --- /dev/null +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.css @@ -0,0 +1,29 @@ +.cascade-bar { + position: sticky; + top: 0; + z-index: 10; + display: flex; + flex-wrap: wrap; + gap: 0.35rem 0.85rem; + align-items: baseline; + margin: -1.5rem -1.5rem 1rem; + padding: 0.55rem 1.5rem; + background: #10141a; + border-bottom: 1px solid #243040; +} +.cascade-bar a { + color: #7ec8ff; + text-decoration: none; +} +.cascade-bar a:hover { text-decoration: underline; } +.cascade-bar a.is-active { + color: #e8eef4; + text-decoration: none; + box-shadow: inset 0 -2px 0 #e8eef4; +} +.cascade-bar a.is-active:hover { text-decoration: none; } +.cascade-bar code { color: inherit; } +.cascade-bar .tip { + color: #8a96a3; + font-size: 12px; +} diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html new file mode 100644 index 0000000..d6ed145 --- /dev/null +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.html @@ -0,0 +1,11 @@ +{% $p := .Req.URL.Path %} + + + diff --git a/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js new file mode 100644 index 0000000..9371535 --- /dev/null +++ b/sites/dossiers.ngi.hacktivism.ch/_cascade/bar.js @@ -0,0 +1,24 @@ +(function () { + var nodes = document.querySelectorAll(".cascade-bar [data-tip]"); + if (!nodes.length) return; + fetch("/branch-status.json", { cache: "no-store" }) + .then(function (res) { + if (!res.ok) throw new Error("status"); + return res.json(); + }) + .then(function (data) { + var tips = {}; + var list = (data && data.branches) || []; + for (var i = 0; i < list.length; i++) { + var row = list[i]; + if (row && row.branch && row.tip) tips[row.branch] = row.tip; + } + for (var j = 0; j < nodes.length; j++) { + var el = nodes[j]; + var name = el.getAttribute("data-tip"); + var tip = tips[name]; + if (tip) el.textContent = name + " @ " + tip; + } + }) + .catch(function () {}); +})(); diff --git a/sites/dossiers.ngi.hacktivism.ch/feat-sm-content/index.html b/sites/dossiers.ngi.hacktivism.ch/feat-sm-content/index.html index bd25fd1..13f4285 100644 --- a/sites/dossiers.ngi.hacktivism.ch/feat-sm-content/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/feat-sm-content/index.html @@ -17,8 +17,6 @@ a { color: #7ec8ff; text-decoration: none; } a:hover { text-decoration: underline; } h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; color: #e8eef4; } .meta { color: #8a96a3; margin: 0 0 1rem; } -.nav { margin: 0.5rem 0 1rem; } -.nav a { margin-right: 0.85rem; } ul.plain { padding-left: 1.2rem; margin: 0; } li { margin: 0.25rem 0; } .dim { color: #6a7682; } @@ -26,15 +24,8 @@ li { margin: 0.25rem 0; } +{% include "_cascade/bar.html" . %}

/feat-sm-content/

last-built: 2026-09-30T02:30:50+02:00 · feat/sm-content @ 089ef1f

- diff --git a/sites/dossiers.ngi.hacktivism.ch/feat-website-design/index.html b/sites/dossiers.ngi.hacktivism.ch/feat-website-design/index.html index d4a7fae..142b80e 100644 --- a/sites/dossiers.ngi.hacktivism.ch/feat-website-design/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/feat-website-design/index.html @@ -17,8 +17,6 @@ a { color: #7ec8ff; text-decoration: none; } a:hover { text-decoration: underline; } h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; color: #e8eef4; } .meta { color: #8a96a3; margin: 0 0 1rem; } -.nav { margin: 0.5rem 0 1rem; } -.nav a { margin-right: 0.85rem; } ul.plain { padding-left: 1.2rem; margin: 0; } li { margin: 0.25rem 0; } .dim { color: #6a7682; } @@ -26,15 +24,8 @@ li { margin: 0.25rem 0; } +{% include "_cascade/bar.html" . %}

/feat-website-design/

last-built: 2026-09-30T02:30:50+02:00 · feat/website-design @ 2e26c80

- diff --git a/sites/dossiers.ngi.hacktivism.ch/feat-website-features/index.html b/sites/dossiers.ngi.hacktivism.ch/feat-website-features/index.html index ed90716..3271f8f 100644 --- a/sites/dossiers.ngi.hacktivism.ch/feat-website-features/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/feat-website-features/index.html @@ -17,8 +17,6 @@ a { color: #7ec8ff; text-decoration: none; } a:hover { text-decoration: underline; } h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; color: #e8eef4; } .meta { color: #8a96a3; margin: 0 0 1rem; } -.nav { margin: 0.5rem 0 1rem; } -.nav a { margin-right: 0.85rem; } ul.plain { padding-left: 1.2rem; margin: 0; } li { margin: 0.25rem 0; } .dim { color: #6a7682; } @@ -26,15 +24,8 @@ li { margin: 0.25rem 0; } +{% include "_cascade/bar.html" . %}

/feat-website-features/

last-built: 2026-09-30T02:30:50+02:00 · feat/website-features @ 53a3176

- diff --git a/sites/dossiers.ngi.hacktivism.ch/feat-website-views/index.html b/sites/dossiers.ngi.hacktivism.ch/feat-website-views/index.html index 5bc14c7..5896890 100644 --- a/sites/dossiers.ngi.hacktivism.ch/feat-website-views/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/feat-website-views/index.html @@ -17,8 +17,6 @@ a { color: #7ec8ff; text-decoration: none; } a:hover { text-decoration: underline; } h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; color: #e8eef4; } .meta { color: #8a96a3; margin: 0 0 1rem; } -.nav { margin: 0.5rem 0 1rem; } -.nav a { margin-right: 0.85rem; } ul.plain { padding-left: 1.2rem; margin: 0; } li { margin: 0.25rem 0; } .dim { color: #6a7682; } @@ -26,15 +24,8 @@ li { margin: 0.25rem 0; } +{% include "_cascade/bar.html" . %}

/feat-website-views/

last-built: 2026-09-30T02:30:50+02:00 · feat/website-views @ bbafc38

- diff --git a/sites/dossiers.ngi.hacktivism.ch/index.html b/sites/dossiers.ngi.hacktivism.ch/index.html index 7126152..4d40f7f 100644 --- a/sites/dossiers.ngi.hacktivism.ch/index.html +++ b/sites/dossiers.ngi.hacktivism.ch/index.html @@ -17,8 +17,6 @@ a { color: #7ec8ff; text-decoration: none; } a:hover { text-decoration: underline; } h1 { font-size: 1.15rem; font-weight: 600; margin: 0 0 0.5rem; color: #e8eef4; } .meta { color: #8a96a3; margin: 0 0 1rem; } -.nav { margin: 0.5rem 0 1rem; } -.nav a { margin-right: 0.85rem; } ul.plain { padding-left: 1.2rem; margin: 0; } li { margin: 0.25rem 0; } .dim { color: #6a7682; } @@ -26,16 +24,9 @@ li { margin: 0.25rem 0; } +{% include "_cascade/bar.html" . %}

cascade

last-built: 2026-09-30T02:30:50+02:00

-