Commit Graph

2 Commits

Author SHA1 Message Date
Diseri Pearson
b5ceedc097 Portal frontend: tests, code splitting, 401 interceptor, lint/format tooling
Items 1-5 of the agreed frontend upgrades. The one-time Prettier
reformatting of files not touched here is in the FOLLOW-UP commit so this
one stays reviewable.

Testing (vitest + @testing-library)
- vitest 3 + @testing-library/react + jsdom. test / test:watch / test:ui
  scripts; setup.ts stubs matchMedia + ResizeObserver for AntD under jsdom.
- 15 tests, 4 files: RequireRole, LoginPage, measurements API client,
  extractApiError.

Route-level code splitting
- Every page converted to React.lazy() behind a Suspense boundary in
  App.tsx. Main bundle 1490 KB -> 714 KB; ECharts isolated in the
  DashboardPage chunk and only fetched when a dashboard is opened.

Global 401 handling
- api/client.ts response interceptor redirects any 401 from a non-/auth
  path to /login?returnTo=<current>; LoginPage honours the param.
- Shared extractApiError replaces four near-identical per-page copies
  (UsersPage, AdminCustomersPage, MyProfilePage, TariffDrawer).

Query devtools
- @tanstack/react-query-devtools, lazy + import.meta.env.DEV-gated so a
  production build dead-code-eliminates it. Verified: no devtools code in
  any shipped .js chunk.

ESLint + Prettier
- ESLint 9 flat config (js + typescript-eslint + react-hooks +
  react-refresh, eslint-config-prettier last). Prettier config tuned to
  the existing style. lint / lint:fix / format / format:check scripts +
  lint-staged config.
- Fixed one real react-hooks/exhaustive-deps warning (useMemo on
  DashboardsPage's `dashboards`).
- End state: eslint 0 problems, tsc clean, 15/15 tests pass.

Pre-commit hook (lint-staged) is installed locally in .git/hooks/ -- not
version-controlled, see follow-up note. Husky was deliberately not used:
it needs a core.hooksPath change and the monorepo layout makes it awkward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 23:46:54 +02:00
Diseri Pearson
e17921a122 Add portal: customer-facing white-labeled monitoring stack
New top-level portal/ project, peer to console/ and firmware/. Delivers a
.NET 10 + React 18 + TimescaleDB + Grafana stack, one container set per
customer behind Traefik. Built in 12 phases per FrontEndPrompt spec; no
changes to existing console or firmware.

Backend (src/Tau.Acuvim.Portal/):
- .NET 10 minimal API, Serilog, ASP.NET Identity (cookie auth, lockout).
- Single AppDbContext with identity / app / monitoring schemas.
- MigrateAsync + TimescaleBootstrapper (idempotent hypertable creation)
  + IdentityBootstrapper (seeded admin + branding) on startup.
- Pure CostCalculator + DB-backed RateService for tariffs (effective-dated,
  TOU periods, VAT, fixed charges, per-municipality timezone).
- BrandingService with logo upload to mounted volume.
- Time-series ingest + bucketed query services (time_bucket aggregates,
  ON CONFLICT for idempotent re-delivery).
- ConfigOverviewService with redaction-by-construction (passwords never in
  payload).
- DataProtection keys persisted to /data/keys volume for cookie survival
  across container restarts.

Frontend (frontend/):
- React 18 + TypeScript + Vite + Ant Design 5 + TanStack Query.
- BrandingProvider + ThemedRoot for live re-themed white-labelling.
- RequireAuth / RequireRole guards.
- Pages: Login, Dashboard, Dashboards (embedded Grafana), Sites (admin),
  Settings tabs (Branding / Rates / Users / Grafana / App config).

Infra:
- Dev (docker-compose.yml) and prod (docker-compose.prod.yml) compose
  files. Three services per customer; Traefik subdomain + same-origin
  /grafana path-prefix routing wired with labels.
- Grafana 11 with provisioned timescaledb datasource (uid pinned) and
  starter power-overview.json dashboard with device template variable.
- Compose project name documented as lowercase (Compose v2 requirement).

Tests (tests/Tau.Acuvim.Portal.Tests/):
- xUnit, 40 tests. Covers CostCalculator (period match, TZ, overlap,
  VAT, fixed), ConnectionStringResolver (all 4 precedence branches incl.
  Production refusal), TariffValidator, DayOfWeekFlag.
- All passing locally against .NET 10.

Docs:
- README.md (onboarding + 11 spec sections), OPERATIONS.md (per-customer
  provisioning, secret rotation, backup, troubleshooting), TESTING.md
  (manual integration scenarios, frontend test scaffolding recipe).

Production safety guards:
- Refuses to start if Authentication:DefaultAdminPassword is unchanged
  default in Production.
- Refuses to start if Database:AutoProvisionLocalTimescaleDb=true in
  Production.
- Prod Grafana ships with anonymous off and auth mode unset (three
  options documented in README Security) so iframe refuses to load
  until a deliberate prod auth choice is made.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:30:30 +02:00