Commit Graph

23 Commits

Author SHA1 Message Date
Bastian Wagner
13864a77f7 Auto-dismiss sync toasts after a few seconds
Fixes two issues found during manual verification:
- fragments/toast.html was missing class="toast-container" on the
  swapped-in element, so the container lost its fixed-position
  styling after the first swap.
- htmx:oobAfterSwap's event.detail.target is the *old* element that
  just got replaced (outerHTML oob-swaps detach it), so the dismiss
  timer must look up the live #toast-container by id instead of
  trusting that stale reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:32:03 +02:00
Bastian Wagner
eb97374578 Live-update the account page status block after sync
Mirrors the dashboard's row update: "Sync now" on the account page
refreshes the status block in place and shows a toast, instead of
navigating to a separate result page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:18:23 +02:00
Bastian Wagner
8d73dea7dd Live-update dashboard rows and show toasts after sync actions
"Sync now" and "Sync all now" now return the freshly reloaded rider
row(s) plus an out-of-band toast instead of navigating to a separate
result page. The "sync already running" case is a 200 + info toast
now instead of a 409 special case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:16:10 +02:00
Bastian Wagner
cc69b3ebb6 Extract dashboard row and sync-all form into reusable partials
These render the initial dashboard page today and will also be
rendered standalone by the sync routes in the next commits, so the
same markup drives both the full page and the post-sync response.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:14:08 +02:00
Bastian Wagner
59b39f10cb Vendor htmx and add toast/loading-state CSS
Self-hosted htmx v2.0.10 (no CDN) plus the toast container and CSS
this and the following tasks need for in-place sync updates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:12:49 +02:00
Bastian Wagner
a0890126bc Add dashboard summary tiles
Shows rider count, activities imported in the last 7 days, 7-day
sync success rate, and how many riders currently need attention,
right above the rider list where an admin looks first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 12:48:44 +02:00
Bastian Wagner
a9a46e949f Turn the next-sync indicator into a live ticking countdown
Replaces the one-time UTC-to-local formatting with a per-second
countdown (HH:MM:SS, or MM:SS under an hour) that fits the dark
cockpit theme's instrument-panel feel, falling back to "due now" once
the target passes instead of showing a negative duration.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:03:07 +02:00
Bastian Wagner
aa9e289185 Fix unstyled number inputs on the system scheduling form
input[type="number"] was missing from the form field selector added
in the redesign, so the scheduler settings fields stayed white-on-
white. Caught via manual browser verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:03:07 +02:00
Bastian Wagner
b6d4be97c1 Redesign UI with dark cockpit color and typography tokens
Replaces the light generic theme with a dark palette grounded in the
app's own subject matter (bike computer telemetry): electric-lime
accent, tabular monospace numerals for stats/timestamps, hairline
card borders instead of shadows, and visible focus rings. Every
template already shares the same classes, so no markup changes were
needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 10:59:58 +02:00
Bastian Wagner
a74ab95f3f Show next scheduled sync time on every page
Exposes the scheduler's next_tick in the topbar via a safe Jinja
helper (falls back to nothing if the scheduler isn't running yet),
and converts the server-rendered UTC timestamp to the visitor's local
time client-side so it reads correctly regardless of timezone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 10:58:53 +02:00
Bastian Wagner
722570c9d3 icon 2026-08-15 21:51:53 +02:00
Bastian Wagner
ff5dab6f0d intervall 2026-08-15 21:44:13 +02:00
Bastian Wagner
f7b04337ce auth für user 2026-08-15 21:34:40 +02:00
Bastian Wagner
420d089760 log 2026-08-15 20:54:35 +02:00
Bastian Wagner
2aba1265af mail notification 2026-08-15 20:47:02 +02:00
Bastian Wagner
7c9e19ba0b errors 2026-08-15 20:31:57 +02:00
Bastian Wagner
85b0d861b4 feat: restyle admin UI and add per-user sync run history
Adds a self-hosted stylesheet (no CDN dependencies) with a card-based
dashboard and color-coded status badges, and shows the last 10 sync
runs per user on the detail page.
2026-08-15 20:19:31 +02:00
Bastian Wagner
6657124983 feat: handle Garmin MFA and activity retries 2026-08-15 16:33:16 +02:00
Bastian Wagner
c2f13611b9 feat: add operational sync controls 2026-08-15 16:24:11 +02:00
Bastian Wagner
49aba8efb4 fix: address final review findings for foundation plan
- C1: drop module-level app singleton in app/main.py so importing the
  package no longer validates Settings or creates DATA_DIR; run uvicorn
  with --factory in the Dockerfile. pytest now collects and passes with
  no ambient env vars.
- I2: add missing app/auth, app/security, app/web __init__.py so
  setuptools discovers all five packages.
- I3: resolve the Jinja2 template directory relative to __file__ instead
  of the process CWD.
- I4: add .gitignore covering .env, data/, .venv/, caches and build
  artifacts so example deployment secrets cannot be committed.
- I5: assert UserRepository.list_enabled() excludes disabled users.
- M6: encode both operands before hmac.compare_digest in validate_csrf so
  a non-ASCII token yields 403 instead of an unhandled 500.
- M9: remove unused relationship / HealthState imports.
- M11: make session cookie https_only configurable via SESSION_HTTPS_ONLY
  (default unchanged: false).
- M13: dispose SQLAlchemy engines in the db_session and client fixtures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 10:14:26 +02:00
Bastian Wagner
d2ac079870 fix: reject empty credential fields with 400 instead of 500
Add an explicit non-empty check before encrypting user-submitted
email/password fields in the create and update user routes, so a
request that bypasses the HTML `required` attribute gets a clean
400 instead of an unhandled ValueError from CredentialCipher.encrypt
propagating as a 500. Applies to all four credential fields on
create, and to the two email fields on update (the password-blank-
means-keep-existing behavior on update is unchanged).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 09:50:05 +02:00
Bastian Wagner
b39842fe2c feat: add encrypted sync user management
Adds full CRUD for sync users (create/list/detail/edit) behind admin
auth and CSRF protection. Passwords are encrypted at rest and never
decrypted into a template context; only emails may be decrypted for
display on the edit form. Blank password fields on edit preserve the
existing encrypted password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 09:41:21 +02:00
Bastian Wagner
da6b94ca2f feat: add local admin authentication 2026-08-15 09:33:27 +02:00