Commit Graph
101 Commits
Author SHA1 Message Date
CatubbaandGitHub ff4ba3e7bc Merge pull request #14 from Joulenap/re-review-remediation-0.4.2
release: 0.4.2 — re-review remediation (robustness, session handling, setup banner)
v0.4.2
2026-07-12 00:09:04 +02:00
Catubba 5a958c0539 release: 0.4.2 2026-07-12 00:05:01 +02:00
Catubba 88d97b9959 feat: frontend resilience — session/stale handling, apply feedback, setup banner
Re-review remediation (FE-H2, FE-H3) plus a requested not-configured banner,
all patch-level:

- FE-H3: session/401 and stale-data handling. A central setUnauthorizedHandler
  in the api client fires on any 401 (exempting /login and /account, whose 401s
  mean wrong credentials, not a dead session); AuthProvider resets auth
  client-side so the app returns to Login with a "session expired" notice.
  useStatus flags data stale after 3 failed polls, driving a "can't reach
  Joulenap" banner that clears on recovery. ApiError's parameter property was
  rewritten to a plain field so node --test can import the client module.

- FE-H2: the Scheduler "Apply changes" action gained busy/saved/error feedback
  matching the settings tabs (disabled while saving, green saved note, the
  backend's error surfaced).

- Not-configured banner: an amber dashboard banner shown when PVE/PBS aren't
  wired up (reusing the wizard's isConfigured helper), with a CTA that
  deep-links to Settings -> Setup via a new initialTab prop.

New i18n keys (en/it, parity 306/306). tsc + 23 tests + build clean.
2026-07-12 00:02:00 +02:00
Catubba 895c7ac9f8 fix: backend robustness — missed/interrupted-run alerts, MAC validation
Re-review remediation (BE-B8, BE-R1, BE-R2, BE-C2), all patch-level:

- BE-R1: detect a scheduled backup missed while the process was down
  (in-memory jobstore has no cross-restart memory). New
  Scheduler.missed_backup_since() queries the armed job's own trigger;
  core/catchup.py anchors on the last finished cycle and, on a hit, logs
  and notifies (via the new NotificationService.send_alert, on_failure
  routed). Wired into lifespan on a daemon thread. Notify-only, no auto-run.

- BE-R2: alert when a run was interrupted by a restart. sweep_orphaned_runs
  now returns the swept runs; lifespan builds a localized interrupted-run
  message (reusing the wake/power-off check so it flags "PBS left on" only
  when the box had actually woken) and dispatches it off-thread.

- BE-B8.4: failure/abort-after-wake notifications now include the
  "PBS left powered on" line. _pbs_left_on rewritten to the accurate rule
  (WAIT succeeded AND no POWEROFF succeeded). B8.2: dashboard pbs_state
  doc-comment clarifying "backing_up" covers any active run.

- BE-C2: reject a malformed pbs.mac at save time (PUT /api/config, 422,
  changed-only) reusing wol.normalize_mac — not a load-time pydantic
  validator, which would brick startup on a legacy bad value.

New backend i18n (missed + interrupted events, en/it). 331 tests pass.
2026-07-12 00:01:44 +02:00
CatubbaandGitHub 3f94413e60 Merge pull request #13 from Joulenap/backend-robustness-0.4.1
0.4.1 — backend robustness + config secret-file permissions
2026-07-10 23:04:06 +02:00
Catubba c87b1e1c49 release: 0.4.1
Patch release: backend robustness fixes (SQLite WAL/busy_timeout/foreign_keys,
manual power-off race, worker-thread lock leak, prune-job timezone) and a
security hardening (config.yaml written owner-only). No feature or API changes.
2026-07-10 23:01:35 +02:00
Catubba 8074043a7f fix: backend robustness + config secret-file permissions
- BE-B3: open SQLite with WAL + busy_timeout + foreign_keys via a connect
  listener, so a running cycle's frequent commits don't risk "database is
  locked" against dashboard polling, and the CASCADE FKs actually enforce.
- BE-S2: write config.yaml owner-only (0600) — it holds API tokens, the
  session key and notification secrets, so it must not be world-readable on
  the host/bind mount (matches the SSH key). Best-effort; re-asserted on save.
- BE-B5: hold the single-run lock across a manual power-off (JobService
  .exclusive()) so a scheduled cycle can't start in the check-then-act gap
  and get its PBS shut down mid-backup.
- BE-B6: if the worker thread fails to start, fail the run and release the
  single-run lock instead of leaking it (which would 409 every later run).
- BE-B7: re-arm the daily history-prune job on rearm so a runtime timezone
  change moves it into the new zone instead of the boot-time zone.
2026-07-10 23:01:28 +02:00
CatubbaandGitHub 340646b477 Merge pull request #12 from Joulenap/re-review-fixes-0.4.0
0.4.0 — keep-PBS-on manual jobs + re-review fixes
2026-07-10 22:16:02 +02:00
Catubba 3c3d8d99ac release: 0.4.0
Bump every version reference to 0.4.0 and add the 0.4.0 CHANGELOG entry
(keep-PBS-on manual jobs + manual GC cycle; account current-password,
cron-validation, wizard-secret, and exclude-mode fixes).

Minor bump: new user-facing feature and an additive, backward-compatible
API field (keep_on defaults to false = prior behavior).
2026-07-10 21:58:48 +02:00
Catubba 0cd59dcb40 fix: address re-review findings + keep-PBS-on manual jobs
Feature — manual jobs can keep the PBS awake:
- Run backup / Run GC are now available while the PBS is asleep; they wake
  it themselves and only require that no other run is in progress.
- Manual GC runs as a full wake -> GC -> power-off cycle (run_gc_cycle),
  mirroring the verify cycle, instead of failing on a sleeping PBS.
- A "Keep PBS powered on after the job" toggle on the confirm dialog leaves
  the box on instead of powering it off, defaulted to its current power
  state (already-on stays on; asleep goes back to sleep). POST
  /api/backup/run and /api/gc/run accept an optional {keep_on} body.
  Scheduled runs always power off, unchanged.

Fixes:
- BE-S9: PUT /api/account now requires the current password, so a stolen
  session alone can't rotate the admin credentials.
- BE-B1: an invalid backup cron is rejected on save (422) and, if already
  on disk, is skipped with a warning instead of crashing the scheduler on
  every restart.
- FE-C1: the setup wizard no longer clears the stored PVE token secret on
  re-save (mirrors the PBS redaction guard).
- FE-H1: exclude guest mode is shown read-only and preserved on save,
  instead of being displayed as an include set and inverted on Apply.
2026-07-10 21:58:35 +02:00
Catubba 1cf452217e release: align every version reference on 0.3.1
frontend/package.json had drifted at 0.2.0 since the 0.2.0 release, and the
README status heading was never updated for 0.3.0. The lockfile pins the root
version too, so it is synced here to keep `npm ci` happy.

The Docker references all use `:latest` and need no change.
v0.3.1
2026-07-10 00:09:47 +02:00
Catubba 5949d6cc43 fix(dev): make the stub guard un-bypassable in any build
`vite build --mode stub` loads .env.stub, which sets VITE_STUB_API=1, so the
guard admitted the stub into a production bundle — one that patches fetch and
Date and answers /auth/status with an authenticated admin. No npm script does
this, but the file must not be able to reach a build at all.

`import.meta.env.DEV` is false for `vite build` under every mode, so the stub
now cannot ship. Verified: `vite build --mode stub` no longer bundles it, and
the dev server still serves it.

Also bump the stub's fake /health version to match the release.
2026-07-09 23:59:18 +02:00
Catubba 22f7063f2e docs: add the 0.3.1 changelog links and mention the wizard fixtures
The `## [0.3.1]` heading is a reference link with no definition, so it
rendered as literal text; `[Unreleased]` still compared from v0.3.0.
2026-07-09 23:48:07 +02:00
Catubba c4629dbe1d release: 0.3.1 2026-07-09 23:47:11 +02:00
Catubba 9691db586f docs: explain how to run the frontend against the dev stub 2026-07-09 23:45:54 +02:00
Catubba a0cfa0feb4 feat(dev): add wizard route fixtures to the API stub
The dev-only stub answered every /wizard/* route with a generic
{ ok: true }, so the setup wizard could never advance past its first
card without a real backend. Add typed fixtures for pve/connect,
pbs/check, pbs/provision, interfaces, wol/detect-mac, ssh/keygen,
ssh/hostkey, ssh/trust, ssh/install and storage/derive so the wizard
can be clicked all the way through (manual or quick mode) at any
viewport with no backend running.
2026-07-09 23:41:21 +02:00
Catubba e80930b684 feat(ui): stack the settings nav into a 2-column grid below 900px 2026-07-09 23:35:56 +02:00
Catubba be7e53b415 fix(ui): let the dashboard panels shrink below their min-content width
Grid items default to `min-width: auto`, so a `1fr` track cannot shrink below
its item's min-content. The guests panel's min-content is 378px, because the
guest-name span is `white-space: nowrap` and a nowrap span's min-content is the
whole string. That floored the collapsed track above the viewport and left the
page 1px wider than a 393px phone.

Set `min-width: 0` on the children of both dashboard rows, inside the mobile
block only. The ellipsis already on that span engages, as it was always meant
to. Desktop is unaffected: there the guests panel sits in a fixed 400px track
and the activity log's min-content is 151px, well under its share.
2026-07-09 23:10:29 +02:00
Catubba 6958e4758a refactor(ui): split header controls into status/actions for mobile wrap
Header now renders brand / status / actions as three flex children
instead of brand / controls, using .jn-header-brand's margin-right:auto
plus .jn-header's gap in place of justify-content:space-between. This
lets the mobile stylesheet wrap actions onto row one (with brand) and
push status to a full-width second row via order + flex-basis, fixing
header overflow on narrow viewports without changing desktop layout.
2026-07-09 23:02:28 +02:00
Catubba ecb9d6c6e0 fix(ui): make weekday selector responsive on mobile
- Class dividers with .jn-sched-divider to hide on mobile
- Class days container with .jn-days to grid-wrap on mobile
- Class day buttons with .jn-day-btn and remove flex layout
- Keeps state-dependent styles (background, color, etc) inline
- Resolves overflow on ~331px mobile panel
2026-07-09 22:58:27 +02:00
Catubba 2db8055dbb refactor(responsive): use jn-log-head and jn-log-row classes for mobile reflow
Remove inline display and gridTemplateColumns from header and log row divs,
delegating layout control to responsive CSS classes. Add minWidth: 0 and
overflowWrap: 'anywhere' to message span to allow text wrapping on mobile.
2026-07-09 22:55:18 +02:00
Catubba 46240b9105 feat(dashboard): replace inline grid styles with responsive classes
Replace two inline grid styles in Dashboard.tsx with className references
to .jn-row-actions and .jn-row-guests from responsive.css. This enables
the existing media queries to control layout on mobile devices without
the inline styles overriding them.

- Line 193: gridTemplateColumns '360px 1fr' → .jn-row-actions
- Line 213: gridTemplateColumns '400px 1fr' → .jn-row-guests
2026-07-09 22:52:18 +02:00
Catubba 9dbb140956 feat(ui): add responsive layout stylesheet and shell class
Introduce responsive.css with the jn- layout classes (shell, header,
dashboard rows, scheduler days, activity log) and a single 900px
max-width breakpoint. Base rules are verbatim transcriptions of the
inline styles they replace, keeping desktop layout unchanged. Apply
the first class, .jn-shell, to AppShell's outer container.
2026-07-09 22:45:03 +02:00
Catubba 3ce7e71217 feat(dev): add stub API harness for UI-only preview
VITE_STUB_API=1 npm run dev now renders the full dashboard against
in-memory fixtures (config/status/guests/logs) with no backend, and
freezes Date to 2026-07-09T21:30:00Z for reproducible screenshots.
Statically guarded in main.tsx so Vite drops it from production
builds entirely.
2026-07-09 22:34:09 +02:00
Catubba e70202339f release: 0.3.0
Per-channel notification report. POST /api/notify/test changes shape and
no longer returns 400/502, hence the minor bump rather than a patch.
v0.3.0
2026-07-09 20:55:00 +02:00
Catubba 2bbe5716a5 fix(notify): scrub an exception raised by engine.add()
add() is the call handed the Apprise URL, so an exception from it is the
one most likely to quote a secret back. It sat outside the try, so it
escaped unscrubbed into the cycle's exception log.
2026-07-09 20:35:30 +02:00
Catubba 0699bbbc64 fix(ui): the test button no longer reports a save failure
When the backend is unreachable, fetch throws before an ApiError is built
and the fallback copy said 'Couldn't save changes' for an action that
saved nothing. Give the test its own key.
2026-07-09 20:22:19 +02:00
Catubba 4654ad8bf6 feat(ui): show the notification test outcome per channel
One row per channel with the failure reason, replacing the single
'Test failed' string that could not say which channel broke.
2026-07-09 20:06:15 +02:00
Catubba b9b99d8d28 feat(ui): add channelLabel helper for the notification report 2026-07-09 20:02:53 +02:00
Catubba 992f9e8afe feat(api): return a per-channel report from POST /api/notify/test
Delivery failure is a result, not a transport error: the request itself
succeeded. Always answer 200 with one entry per channel, so the UI can
show which channel failed and why. Drops the 400 (no channels) and 502
(delivery failed) responses.
2026-07-09 19:58:40 +02:00
Catubba 51ceb9177d docs(notify): tie the thread filter to the one-URL-per-engine invariant
The capture only sees the real failure reason because Apprise dispatches a
single server in the calling thread. Nothing enforced that link; a future
change batching URLs into one engine would silently blank every reason.
2026-07-09 19:55:27 +02:00
Catubba dfb4ca068b fix(notify): isolate log capture per thread, scrub channel URLs and encoded secrets
Concurrent send_test()/send_run_result() calls on different threads could
attribute one channel's failure reason to another, since the apprise log
capture handler was not thread-scoped. Also close two secret-leak gaps:
scrub each channel's own URL before anything else (covers ntfy, which has
no credential field) and try the colon-safe percent-encoded form of a
secret so tokens containing '/' are fully redacted.
2026-07-09 19:47:47 +02:00
Catubba 3e6153565f feat(notify): report delivery outcome per channel
Apprise collapses every channel into one bool, so a failed notification
could only ever say 'delivery failed'. Drive one engine per channel and
capture Apprise's log records for that send, so the reason (DNS failure,
connection refused, 401) is attributed to the channel that produced it.
Secrets are scrubbed from the reason before it leaves the service.

Run notifications log a warning per failed channel; previously a channel
that stopped working was silent.
2026-07-09 19:32:31 +02:00
CatubbaandGitHub 7aefc66324 fix(docker): build the SPA on node 24 to match CI (#11)
* fix(docker): build the SPA on node 24 to match CI

The frontend CI job runs on node 24 (npm 11) while the image built the SPA
on node:22-slim (npm 10). The two npm majors disagree on whether a violated
*optional* peer dependency is fatal: npm 11 warns, npm 10 fails with ERESOLVE.

So `npm ci` could pass every CI job and still fail `docker build` on the very
same lockfile — which is exactly what happened on the typescript 7 PR, where
i18next declares `peerOptional typescript@"^5 || ^6"`. CI was green; the image
build was not.

Align the build stage to node:24-slim so CI validates the same toolchain the
shipped image uses. Vite 8 requires node >=22.12, so 24 is in range.

Note this makes the image build as lenient as CI: a violated optional peer no
longer stops the build. That is the correct trade — the two environments agreeing
matters more than one of them tripping on type-only metadata — but it does remove
the tripwire that surfaced the typescript 7 conflict.

* ci: fail on node major drift between Dockerfile and ci.yml

The Dockerfile now carries a "keep this in sync with ci.yml" comment, and
comments drift. Assert it instead: parse the node major from the SPA build
stage and from node-version, and fail the build when they disagree.

Verified the guard actually fails, not just passes: drift (22 vs 24) errors,
a ci.yml declaring two different majors errors, and an unparseable Dockerfile
errors with an explanation rather than dying silently under `set -e`.
2026-07-09 11:07:26 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f6de808547 chore(deps): bump the i18n group across 1 directory with 2 updates (#7)
Bumps the i18n group with 2 updates in the /frontend directory: [i18next](https://github.com/i18next/i18next) and [react-i18next](https://github.com/i18next/react-i18next).


Updates `i18next` from 23.16.8 to 26.3.5
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.16.8...v26.3.5)

Updates `react-i18next` from 14.1.3 to 17.0.8
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v14.1.3...v17.0.8)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.3.5
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: i18n
- dependency-name: react-i18next
  dependency-version: 17.0.8
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: i18n
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 09:53:09 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
949686a95c chore(deps): bump the react group across 1 directory with 4 updates (#8)
Bumps the react group with 4 updates in the /frontend directory: [react](https://github.com/facebook/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).


Updates `react` from 18.3.1 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

Updates `@types/react` from 18.3.31 to 19.2.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 18.3.1 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `@types/react-dom` from 18.3.7 to 19.2.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@types/react` from 18.3.31 to 19.2.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 18.3.7 to 19.2.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: react
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 09:45:46 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2f6b0205e9 chore(deps-dev): bump the vite group in /frontend with 2 updates (#9)
Bumps the vite group in /frontend with 2 updates: [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `@vitejs/plugin-react` from 4.7.0 to 6.0.3
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react)

Updates `vite` from 6.4.3 to 8.1.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.1.4/packages/vite)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: vite
- dependency-name: vite
  dependency-version: 8.1.4
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: vite
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 09:33:54 +02:00
CatubbaandGitHub 781822f84c chore(deps): typescript 6 + group peer-coupled Dependabot updates (#6)
* chore(deps-dev): bump typescript from 5.9.3 to 6.0.3

Redone on current main rather than merging the Dependabot branch, which
was 62 commits behind and predated the tsconfig change and the frontend
test harness — its CI run proved nothing about today's tree.

Verified locally: tsc --noEmit clean, vite build succeeds, 18/18 tests
pass, npm audit --omit=dev --audit-level=high reports 0 vulnerabilities.

* ci(dependabot): group peer-coupled packages so majors can go green

react/react-dom, vite/@vitejs/*, and i18next/react-i18next are coupled by
peer dependencies. Bumping one half across a major without the other fails
npm ci with ERESOLVE, so the solo PRs Dependabot opened for each half could
never pass CI — four of five open PRs were blocked this way.

Group each family ahead of the catch-all with no update-types filter, so it
moves as one unit at every version level. The catch-all keeps its minor+patch
scope, preserving the original intent that a security patch never rides in
with an untested framework major.

Raise the npm PR limit 5 -> 10: the cap was exactly hit, which silently stops
Dependabot opening the companion half of a pair.

i18n is listed before react so that react-i18next cannot be claimed by the
react group regardless of how patterns are matched.
2026-07-09 09:25:10 +02:00
Catubba 6572d5a950 release: v0.2.0 — dashboard integration + persistent datastore usage v0.2.0 2026-07-09 02:49:34 +02:00
Catubba 9dc6879feb test(api): cover datastore write-on-GET when PBS online + clarify fallback 2026-07-09 02:34:50 +02:00
Catubba a07523d529 feat(api): serve cached datastore usage when the PBS is offline 2026-07-09 02:25:27 +02:00
Catubba c3c912e182 feat(cycle): cache datastore usage while the PBS is awake 2026-07-09 02:20:04 +02:00
Catubba 4c06f245f5 feat(db): add DatastoreStat cache table + upsert/get helpers 2026-07-09 02:15:45 +02:00
Catubba de2c27b402 fix(frontend): copy works over HTTP (non-secure context) + snippet copy button 2026-07-09 01:23:36 +02:00
Catubba 26a3e875ef feat(dashboard): last_run_status reflects the last completed cycle, not an in-progress one 2026-07-09 00:48:12 +02:00
Catubba 256dfecbad fix(dashboard): 401 on non-ASCII API key + correct snippet icon path
secrets.compare_digest() rejects non-ASCII str with TypeError, turning
the unauthenticated /api/dashboard endpoint's 401 path into a 500 when
an attacker sends a non-ASCII ?key= or X-API-Key. Compare as UTF-8
bytes instead, and add a regression test.

Also fix the Homepage integration snippet (UI + docs) referencing the
non-existent /favicon.svg; the app serves its icon at
/assets/joulenap-icon.svg.
2026-07-09 00:33:49 +02:00
Catubba c85fd2183a fix(frontend): correct Homarr/Dashy integration snippets to current syntax 2026-07-09 00:25:36 +02:00
Catubba 61cdf7268e docs: add dashboard integration guide 2026-07-09 00:20:40 +02:00
Catubba 8c2bcc654c feat(frontend): add Settings > Integrations with dashboard snippet picker 2026-07-09 00:13:46 +02:00
Catubba 259032f6a6 feat(frontend): add api_key config field and key-management client methods 2026-07-09 00:09:22 +02:00