Commit Graph
133 Commits
Author SHA1 Message Date
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
Catubba 16465b632f feat(api): add POST/DELETE /api/config/api-key management 2026-07-09 00:05:52 +02:00
Catubba 2d954f53fd feat(api): add API-key-protected GET /api/dashboard endpoint 2026-07-09 00:00:37 +02:00
Catubba fd82af46c1 refactor(api): extract shared PBS status probe helper 2026-07-08 23:55:04 +02:00
Catubba 61a9dee79a feat(config): add server-managed app.api_key for dashboard integration 2026-07-08 23:50:17 +02:00
Catubba 48e4bc9b42 release: v0.1.1 — version bump + doc corrections v0.1.1 2026-07-08 22:48:57 +02:00
Catubba 1c6df48152 fix(dashboard): preserve advanced cron schedules verbatim on save (JN-006) 2026-07-08 18:52:38 +02:00
Catubba 4451f4919c test(provision): pin create_token re-raise when delete fails (JN-028) 2026-07-08 18:41:51 +02:00
Catubba 1a7aef9dc7 test(cycle): cover datastore best-effort, failure notification, per-step logs (JN-028) 2026-07-08 18:38:17 +02:00
Catubba 07f9883edd fix(dashboard): lock the weekday UI for advanced cron schedules + note (JN-006) 2026-07-08 18:28:53 +02:00
Catubba 657ef3b8d8 fix(cron): carry day-of-month/month through the round-trip (JN-006) 2026-07-08 18:23:30 +02:00
Catubba ace6f52474 fix(format): revert rel() to Math.round, correct test to characterize it (JN-030) 2026-07-08 18:20:52 +02:00
Catubba 7d0676c3fe test(frontend): add node:test harness + format.ts tests, bump CI to Node 24 (JN-030) 2026-07-08 18:15:20 +02:00
Catubba 02ac5da168 fix(wizard): keep the pinned PBS fingerprint on plain backtrack (JN-002) 2026-07-08 17:22:52 +02:00
Catubba d117d11144 fix(auth): keep the acting session on self password-change, revoke others (JN-011) 2026-07-08 17:22:48 +02:00
Catubba 0045fa6fe0 fix(wizard): reset host-key confirmation + pinned fingerprint on PBS host change (JN-002/003) 2026-07-08 17:01:54 +02:00
Catubba 37bfbbff16 docs: auth-hardening settings + first-run guidance (JN-009/010/011) 2026-07-08 16:57:56 +02:00
Catubba 95eaa85fa1 feat(auth): surface login lockout message on the sign-in screen (JN-009) 2026-07-08 16:53:48 +02:00
Catubba 951d19fca7 feat(auth): revoke sessions on password change via hash-derived token (JN-011)
Also fix test_notify's no-channels test, which replaced the whole config
(including auth) and tripped the new session/password-hash check; it now
preserves the auth section while resetting notifications.
2026-07-08 16:49:30 +02:00