Port the ScadaLink CentralUI sidebar pattern into the OtOpcUa AdminUI: - Drop the top app-bar. Brand moves into the side rail's header — same visual rhythm as ScadaLink's NavMenu. - New NavSection.razor: collapsible eyebrow toggle (rail-eyebrow-toggle CSS) with a chevron + label. Mirrors ScadaLink/Components/Layout/NavSection. - New NavSidebar.razor: interactive island carrying the three section groups (Navigation / Scripting / Live) + session block. Marked @rendermode InteractiveServer; MainLayout itself stays static-rendered because layouts can't take a RenderFragment Body across an interactive boundary. - New wwwroot/js/nav-state.js: window.navState.get/.set persists the expanded-section list to the otopcua_nav cookie (one-year lifetime, SameSite=Lax). Same shape as ScadaLink's scadabridge_nav. - New LoginLayout.razor + @layout LoginLayout on Login.razor: the login page now renders without the side rail — clean centred card. - MainLayout.razor: slimmed down to the d-flex shell + hamburger toggle + <NavSidebar/> + @Body. - Login.razor: also drops the trailing "LDAP bind against the configured directory..." footer that the user asked to remove. - site.css: adds .side-rail .brand styles (mirrored from ScadaLink) and the .rail-eyebrow-toggle / .rail-eyebrow-chevron / .rail-section-body styles for the new collapsible UI. Auto-expand on page load: NavSidebar seeds the expanded set from the current URL's first path segment (in OnInitialized so it works even on the very first server render) and from the cookie (in OnAfterRenderAsync once JS interop is available). LocationChanged hooks keep the expanded state in sync as the user navigates between sections.
51 lines
2.2 KiB
Plaintext
51 lines
2.2 KiB
Plaintext
@page "/login"
|
|
@layout LoginLayout
|
|
@* 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.
|
|
|
|
Uses LoginLayout (no side rail) so the page renders as a clean centred card. *@
|
|
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
|
|
|
<div class="login-wrap rise" style="animation-delay:.02s">
|
|
<section class="panel">
|
|
<div class="panel-head">OtOpcUa Admin — 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>
|
|
</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; }
|
|
}
|