feat(adminui): F15 Phase A — shell + auth + fleet + hosts pages
Some checks failed
v2-ci / build (push) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (push) Has been skipped

Implements Phase A of the F15 rebuild plan: minimum-viable Admin surface
with a working sign-in path and a fleet-state landing page. Decisions Q1–Q5
of docs/v2/AdminUI-rebuild-plan.md were taken as recommended.

- App.razor (moved into AdminUI library from the Host stub; vendored
  Bootstrap from RCL wwwroot — no public CDN, air-gap safe)
- Routes.razor (AuthorizeRouteView enforces page-level [Authorize])
- RedirectToLogin.razor (preserves returnUrl through the auth hop)
- Login.razor (static SSR, posts to /auth/login; Q5 wording about
  generic-vs-specific LDAP errors)
- Account.razor (identity + fleet roles + raw LDAP groups; Q4 — no
  per-cluster grants; fleet-wide LDAP-group → role mapping only)
- Fleet.razor (per-node deployment status: reads NodeDeploymentState
  + unions with IClusterRoleInfo.MembersWithRole("driver") so freshly-
  joined nodes appear as "waiting"; 10s auto-refresh)
- Hosts.razor (Akka cluster topology: members, status, roles, role-
  leader; 5s auto-refresh)

Host's stub App.razor deleted; Program.cs now points at
AdminUI.Components.App via an added using.

All 104 v2 tests remain green.
This commit is contained in:
Joseph Doherty
2026-05-26 07:49:35 -04:00
parent 5c754ecffd
commit 850d6774ea
9 changed files with 590 additions and 19 deletions

View File

@@ -0,0 +1,53 @@
@page "/login"
@* Login MUST stay anonymously reachable — otherwise the fallback authorization policy
would lock operators out of the only way in (Admin-001). Static-rendered on purpose:
the form POSTs to /auth/login while ASP.NET still owns an unstarted HTTP response.
Calling SignInAsync from an interactive circuit would be too late. *@
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
<div class="login-wrap rise" style="animation-delay:.02s">
<section class="panel">
<div class="panel-head">OtOpcUa Admin &mdash; sign in</div>
<div style="padding:1.1rem 1.1rem 1.25rem">
<form method="post" action="/auth/login" data-enhance="false">
@if (ReturnUrl is not null)
{
<input type="hidden" name="returnUrl" value="@ReturnUrl"/>
}
<div class="mb-3">
<label class="form-label" for="username">Username</label>
<input id="username" name="username" type="text"
class="form-control form-control-sm" autocomplete="username"/>
</div>
<div class="mb-3">
<label class="form-label" for="password">Password</label>
<input id="password" name="password" type="password"
class="form-control form-control-sm" autocomplete="current-password"/>
</div>
@if (!string.IsNullOrWhiteSpace(Error))
{
<div class="panel notice" style="margin-bottom:.85rem">@Error</div>
}
<button class="btn btn-primary w-100" type="submit">Sign in</button>
</form>
<div style="margin-top:1rem;padding-top:.85rem;border-top:1px solid var(--rule);
font-size:.78rem;color:var(--ink-faint)">
LDAP bind against the configured directory (per Q5 of the AdminUI rebuild plan:
generic error in production; specific reason when <span class="mono">Authentication:Ldap:AllowInsecureLdap=true</span>).
</div>
</div>
</section>
</div>
@code {
/// <summary>Error message surfaced by /auth/login after a failed bind.</summary>
[SupplyParameterFromQuery]
private string? Error { get; set; }
/// <summary>Original protected URL the operator was bounced from; round-tripped to the endpoint.</summary>
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
}