09cd435c2b
ci / build-test (push) Successful in 1m19s
Three requested phases.
1) Admin section (SDD §8.7) — MudBlazor inline-dialog CRUD, consistent pattern,
delete guards, snackbar feedback, shared Confirm helper:
- Energy types: create/edit/delete (blocks delete when meters reference it).
- Meters: create/edit/delete; recomputes consumption when mode/baseline
changes (NormalizationService over a fresh factory context, in a tx);
delete cascades data (consumption+readings are Restrict → removed first).
- A meter's ingest sources: manage on the meter-detail Sources tab
(add/edit/delete MQTT/Tasmota/HA sources with typed config).
- Tariffs: full CRUD (scope/component/value/validity).
- Cost categories: CRUD + member management (meter or energy-type members).
- Connectors: ingestion_endpoint CRUD (MQTT broker + Home Assistant);
secrets referenced by env-var name only, never stored.
- Settings: read-only effective-config view (settings are env-driven and
reproducible, so an editable form would change nothing — kept honest).
PV role is now editable on meters (MeterMeta.SetRole can clear a role).
2) Read Home Assistant — extracted a shared public HaEndpointConfig (was a
private record in the worker), added HaConnectionTester (powers the connector
"Test connection": checks base URL + env-resolved token, optionally reads one
entity). Configuring an HA connector + an HA source on a meter drives the
existing REST-poll worker end to end. (WebSocket push stays a future
optimization; REST poll already reads HA.)
3) Wiring/placeholder audit — swept every OnClick/Href: all handlers are real,
all internal links resolve to real routes, no TODO/stub/placeholder code.
Fixed one genuine gap: MainLayout had no drawer toggle, so the nav was
unreachable on narrow screens — added a hamburger button.
Tests: +6 (MeterMeta.SetRole role-removal; HaConnectionTester fail-closed
guard branches with a throwing HttpClientFactory proving no network on bad
config); render test now covers all admin routes. 69 Core + 45 Integration =
114 green. Live-verified in Docker: all admin pages 200, drawer toggle present,
Settings shows real effective config.
Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
43 lines
1.5 KiB
Plaintext
43 lines
1.5 KiB
Plaintext
@inherits LayoutComponentBase
|
|
@using MeterVault.App.Theme
|
|
|
|
<MudThemeProvider Theme="MeterVaultTheme.Instance" @bind-IsDarkMode="_darkMode" />
|
|
<MudPopoverProvider />
|
|
<MudDialogProvider />
|
|
<MudSnackbarProvider />
|
|
|
|
<MudLayout>
|
|
<MudAppBar Elevation="1" Dense="true">
|
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start"
|
|
OnClick="@(() => _drawerOpen = !_drawerOpen)" aria-label="Toggle navigation" />
|
|
<MudIcon Icon="@Icons.Material.Filled.Bolt" Class="mr-2" />
|
|
<MudText Typo="Typo.h6">MeterVault</MudText>
|
|
<MudSpacer />
|
|
<MudTooltip Text="@(_darkMode ? "Light mode" : "Dark mode")">
|
|
<MudIconButton Icon="@(_darkMode ? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)"
|
|
Color="Color.Inherit" OnClick="@(() => _darkMode = !_darkMode)" />
|
|
</MudTooltip>
|
|
</MudAppBar>
|
|
|
|
<MudDrawer @bind-Open="_drawerOpen" Elevation="1" ClipMode="DrawerClipMode.Always" Variant="DrawerVariant.Responsive">
|
|
<NavMenu />
|
|
</MudDrawer>
|
|
|
|
<MudMainContent>
|
|
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4 mv-main">
|
|
@Body
|
|
</MudContainer>
|
|
</MudMainContent>
|
|
</MudLayout>
|
|
|
|
<div id="blazor-error-ui" data-nosnippet>
|
|
An unhandled error has occurred.
|
|
<a href="." class="reload">Reload</a>
|
|
<span class="dismiss">🗙</span>
|
|
</div>
|
|
|
|
@code {
|
|
private bool _drawerOpen = true;
|
|
private bool _darkMode = true;
|
|
}
|