Commit Graph

2 Commits

Author SHA1 Message Date
Diseri Pearson
66660364ec Cost-feature followups: libgssapi fix, daily CA, dashboard cost
Three small polish items on top of the cost-compute feature.

(A) Dockerfile: install libgssapi-krb5-2 in the runtime stage.
Silences the "Cannot load library libgssapi_krb5.so.2" warning that
Npgsql logs when probing for Kerberos auth on Linux. We don't actually
use Kerberos; the lib is ~3 MB and removes confusing log noise.

(B) FleetTimescaleBootstrapper: add hierarchical fleet.daily_per_customer
continuous aggregate on top of fleet.hourly_per_device. Per-customer
daily totals (samples, kwh imported/exported, avg/max/min kW).
Realtime, materialized_only=false, 365-day start_offset, hourly refresh.
Available for long-range dashboards / billing summaries that don't need
device-level or hourly detail. Not yet consumed by any query — exists
as a primitive for the next dashboard / report that wants it.

(C) Fleet dashboard: include per-customer cost today.
- FleetQueryService.GetDashboardAsync invokes FleetCostService.ComputeAsync
  per customer for today's UTC window. Failures for one customer don't
  break the dashboard (leave their CostToday null and continue).
- FleetCustomerSummary gains nullable CostToday; FleetDashboardDto
  gains TotalCostToday (sum across customers).
- AdminFleetDashboardPage adds a 5th Statistic card "Cost today" in
  green, and a "Today (cost)" column in the customer summary table.
- Perf note added in service comment: per-load cost compute scales
  with N customers; revisit with a materialised daily_cost view when
  N grows beyond ~100.

Verified on the running stack
- Admin container logs no longer contain "libgssapi" anywhere.
- timescaledb_information.continuous_aggregates shows both
  hourly_per_device and daily_per_customer.
- /api/fleet/dashboard returns totalCostToday=161.00, the single
  customer (DEV0001) row shows costToday=161.00 matching the
  cost endpoint's total for the same UTC day.

Deliberately not in this commit: per-customer Postgres RLS for
multi-Admin-user setups. That needs design decisions (claim source,
fleet-admin bypass model, connection-pooling interaction with session
variables) — I'd rather pose those questions and ship it right than
sneak in a half-baked version. Wrap-up message has the design qs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:53:02 +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