docs(ui-theme): current-state ×3 + GAPS adoption backlog
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# UI Theme — gaps & adoption backlog
|
||||
|
||||
Divergence of each project from [`spec/SPEC.md`](spec/SPEC.md), and the ordered backlog to
|
||||
reach adoption of the `ZB.MOM.WW.Theme` shared RCL. Status legend: ⛔ gap · 🟡 partial · ✅ matches.
|
||||
|
||||
---
|
||||
|
||||
## Divergence vs spec
|
||||
|
||||
### §1 Design tokens — `theme.css`
|
||||
|
||||
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|
||||
|---|---|---|---|
|
||||
| Tokens identical to canonical | ✅ identical | ✅ identical | ✅ identical |
|
||||
| File maintained in one place (RCL) | ⛔ own copy | ⛔ own copy | ⛔ own copy |
|
||||
| Font path `url('../fonts/…')` | ⛔ `url('fonts/…')` — **latent 404** | 🟡 `url('/fonts/…')` — absolute, not portable | ✅ `url('../fonts/…')` — correct |
|
||||
| IBM Plex fonts in one place | ⛔ own `wwwroot/fonts/` | ⛔ own `wwwroot/fonts/` | ⛔ own `wwwroot/fonts/` |
|
||||
|
||||
→ **Gap T1:** All three apps maintain a copy of `theme.css` — the single-source guarantee
|
||||
is broken today. Any token change must be applied in four places (three apps + the RCL)
|
||||
once the RCL exists.
|
||||
→ **Gap T2:** OtOpcUa `url('fonts/…')` is a latent 404 masked by system-font fallback.
|
||||
Adoption fixes it automatically.
|
||||
→ **Gap T3:** Each app vendors fonts — 3× duplication. The RCL eliminates it.
|
||||
|
||||
### §2 Typography
|
||||
|
||||
All three apps reference IBM Plex via the token stacks. No typography divergence — the
|
||||
token values are identical. Gap is delivery (T3 above).
|
||||
|
||||
### §3 Canonical side-rail layout
|
||||
|
||||
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|
||||
|---|---|---|---|
|
||||
| `.app-shell` root element | ✅ `div.app-shell` | ⛔ `div.d-flex …` (no `.app-shell`) | ⛔ `div.d-flex …` (no `.app-shell`) |
|
||||
| Rail CSS class | ✅ `.side-rail` | ⛔ `.sidebar` | ⛔ `.sidebar` |
|
||||
| Nav item CSS class | ✅ `.rail-link` | ⛔ `.nav-link` | ⛔ `.nav-link` |
|
||||
| Nav item element | ✅ `<a>` (NavLink) | ⛔ `<li><NavLink>` inside `<ul>` | ⛔ `<li><NavLink>` inside `<ul>` |
|
||||
| Shell component | ⛔ bespoke `MainLayout` + `NavSidebar` | ⛔ combined `MainLayout` (210 lines) | ⛔ `MainLayout` + `NavMenu` |
|
||||
| Thin-MainLayout pattern | ⛔ not yet | ⛔ not yet | ⛔ not yet |
|
||||
|
||||
→ **Gap L1:** OtOpcUa already uses the right CSS classes but the component structure
|
||||
doesn't use `ThemeShell`. Low-risk migration.
|
||||
→ **Gap L2:** MxAccessGateway and ScadaBridge use `.sidebar` / `.nav-link` / `<ul><li>`.
|
||||
Migration requires class name changes throughout their nav markup and `site.css` sidebar
|
||||
blocks. Medium (ScadaBridge) to high (MxGateway combined layout) risk.
|
||||
|
||||
### §4 Component contract
|
||||
|
||||
| Component | OtOpcUa | MxAccessGateway | ScadaBridge |
|
||||
|---|---|---|---|
|
||||
| `StatusPill` (vs bespoke `StatusBadge`) | ⛔ `StatusBadge` (string CSS class) | ⛔ `StatusBadge` (string text → class) | ⛔ raw `.chip-*` classes inline |
|
||||
| `LoginCard` | ⛔ inline markup in `Login.razor` | ⛔ no Blazor login page | ⛔ Bootstrap `.card` markup in `Login.razor` |
|
||||
| `NavRailItem` / `NavRailSection` | ⛔ `NavLink` + `NavSection` (interactive) | ⛔ `NavLink`+`<li>` + `NavSection` | ⛔ `NavLink`+`<li>` + `NavSection` |
|
||||
| `ThemeShell` / thin `MainLayout` | ⛔ not yet | ⛔ not yet | ⛔ not yet |
|
||||
| `ThemeHead` | ⛔ manual `<link>` tags | ⛔ manual `<link>` tags | ⛔ manual `<link>` tags |
|
||||
|
||||
### §5 Delivery
|
||||
|
||||
| Item | OtOpcUa | MxAccessGateway | ScadaBridge |
|
||||
|---|---|---|---|
|
||||
| Asset via `_content/ZB.MOM.WW.Theme/…` | ⛔ `_content/…AdminUI/css/…` | ⛔ root-relative `/css/…` | ⛔ `_content/…CentralUI/css/…` |
|
||||
| `<ThemeHead />` in `<head>` | ⛔ manual `<link>` tags | ⛔ manual `<link>` tags | ⛔ manual `<link>` tags |
|
||||
|
||||
---
|
||||
|
||||
## Adoption backlog (ordered)
|
||||
|
||||
| # | Item | Projects | Priority | Effort | Risk | Notes |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Build `ZB.MOM.WW.Theme` RCL | scadaproj | High | M | Low | **DONE** — `0.1.0` built + tested in this repo |
|
||||
| 2 | Adopt in OtOpcUa AdminUI | OtOpcUa | High | S | Low | Already rail; fix latent font 404; cookie nav-state optional retain |
|
||||
| 3 | Adopt in ScadaBridge CentralUI + Host | ScadaBridge | Med | M | Med | Sidebar class migration + `MainLayout` replace; scoped `.razor.css` unchanged |
|
||||
| 4 | Adopt in MxAccessGateway Dashboard | MxAccessGateway | Low | L | High | Combined `MainLayout` migration; sidebar idiom change; largest UX-visible change — verify visually |
|
||||
|
||||
**Sequencing:** #2 first (lowest risk, validates the adoption pattern); #3 next (medium
|
||||
effort, no design change); #4 last (highest risk — verify dashboard UX thoroughly before
|
||||
merging). Each adoption is a per-repo PR, independent.
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
- **MxGateway login:** No Blazor login page today. If one is added during adoption (#4),
|
||||
use `<LoginCard>`. If the server-redirect pattern is kept, `<LoginCard>` is not needed.
|
||||
- **OtOpcUa cookie nav state:** Decide whether to retain `otopcua_nav` cookie persistence
|
||||
(keep bespoke interactive `NavSection` alongside `ThemeShell`'s `Nav` slot) or drop it
|
||||
(CSS-only `NavRailSection` replaces it, losing expand-state persistence across page loads).
|
||||
- **ScadaBridge `AuthorizeView` policy gating in nav:** Verify `<NavRailSection>` inside
|
||||
`<AuthorizeView>` renders + hides correctly with the canonical SSR rendering model.
|
||||
@@ -0,0 +1,161 @@
|
||||
# UI Theme — current state: MxAccessGateway
|
||||
|
||||
Repo: `~/Desktop/MxAccessGateway` (Gitea `mxaccessgw`). Stack: .NET 10, Blazor SSR
|
||||
(gateway x64) — UI in `src/ZB.MOM.WW.MxGateway.Server/`.
|
||||
All paths below are relative to the repo root. Verified against source on 2026-06-01.
|
||||
|
||||
**Summary:** MxAccessGateway uses a sidebar nav layout and the Technical-Light tokens, but
|
||||
the sidebar uses Bootstrap `.sidebar` / `.nav-link` classes rather than the canonical
|
||||
`.side-rail` / `.rail-link` classes, and the overall structure diverges from the spec
|
||||
target. Adoption has the **highest effort and risk** of the three apps — the shell
|
||||
requires migration from its current sidebar idiom to the canonical `ThemeShell` pattern.
|
||||
There is no dedicated login page (authentication gate is integrated into the Dashboard).
|
||||
|
||||
---
|
||||
|
||||
## 1. CSS / design tokens
|
||||
|
||||
**`theme.css`** — 379-line hand copy of the Technical-Light design system. Identical in
|
||||
content to OtOpcUa's and ScadaBridge's copies except for the font-path prefix.
|
||||
- Path: `src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/theme.css`
|
||||
- Font path: `url('/fonts/ibm-plex-sans-400.woff2')` (lines 24, 29, 34)
|
||||
- Absolute path (`/fonts/…`) is technically correct (resolves from root of the app), but
|
||||
differs from the canonical `url('../fonts/…')` in the RCL — a deployment path difference,
|
||||
not a loading bug.
|
||||
- Wired in `App.razor` line 6: `<link rel="stylesheet" href="/css/theme.css" />`.
|
||||
|
||||
**`site.css`** — 592 lines of per-app page layout and Dashboard component styling.
|
||||
- Path: `src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css`
|
||||
- Wired in `App.razor` line 7: `<link rel="stylesheet" href="/css/site.css" />`.
|
||||
- Contains: `.sidebar` layout (lines ~24–95), `.dashboard-body`, `.agg-card`, table
|
||||
styles, metric cards, event/alarm grids, and other domain-specific rules.
|
||||
- After adoption: the `.sidebar` layout section is superseded by RCL `layout.css`.
|
||||
The domain-specific table/card/grid rules stay in `site.css`.
|
||||
|
||||
Note: MxGateway's `App.razor` loads assets from `/css/…` and `/fonts/…` (root-relative
|
||||
paths to `wwwroot/`), not via `_content/…` static-web-asset paths — contrast with
|
||||
OtOpcUa and ScadaBridge which use the RCL `_content/` mechanism.
|
||||
|
||||
---
|
||||
|
||||
## 2. IBM Plex fonts
|
||||
|
||||
Three `.woff2` files vendored into:
|
||||
`src/ZB.MOM.WW.MxGateway.Server/wwwroot/fonts/`
|
||||
- `ibm-plex-sans-400.woff2`
|
||||
- `ibm-plex-sans-600.woff2`
|
||||
- `ibm-plex-mono-500.woff2`
|
||||
|
||||
After adoption: delete all three; the RCL serves them from
|
||||
`_content/ZB.MOM.WW.Theme/fonts/`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Layout shell
|
||||
|
||||
**`MainLayout.razor`** — 210-line combined layout + nav component. `@implements
|
||||
IDisposable`; `@inject NavigationManager`, `@inject IJSRuntime`. Interactive
|
||||
(`@rendermode InteractiveServer` inherited from `Routes`).
|
||||
- Path: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Layout/MainLayout.razor`
|
||||
- Root element: `<div class="d-flex flex-column flex-lg-row" style="min-height: 100vh;">`.
|
||||
Note: **no `.app-shell` class** (unlike OtOpcUa and the spec target).
|
||||
- Brand: `<a class="brand" href="/"><span class="mark">▮</span> MXAccess Gateway</a>`
|
||||
(line 24).
|
||||
- Nav structure: `<nav class="sidebar d-flex flex-column">` with `<ul class="nav flex-column">`
|
||||
and `<NavSection>` groups ("Runtime", "Galaxy", "Admin", "Configuration") with
|
||||
`<NavLink class="nav-link">` children (not `.rail-link`).
|
||||
- No dedicated `RailFooter` / session block (auth state shown elsewhere or via API keys).
|
||||
- Nav state persisted via JS (`nav-state.js`), same pattern as OtOpcUa.
|
||||
|
||||
**`NavSection.razor`** — 40-line component using `EventCallback OnToggle` + JS collapse.
|
||||
- Path: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Layout/NavSection.razor`
|
||||
- Structure: `<li class="nav-item"><button class="nav-section-toggle" @onclick="OnToggle">`.
|
||||
Uses Bootstrap-style `<ul>/<li>` nav items, not `.rail-link` anchor style.
|
||||
|
||||
---
|
||||
|
||||
## 4. Login / auth surface
|
||||
|
||||
MxAccessGateway has **no dedicated Blazor login page**. There is no `Login.razor`. The
|
||||
dashboard is protected by ASP.NET Core cookie authentication; login is handled via an
|
||||
ASP.NET Minimal API auth endpoint (outside the Blazor component tree). The `MainLayout`
|
||||
includes a "Sign In" link (`<a href="/login" class="btn …">Sign In</a>` line 87) that
|
||||
redirects to the server endpoint. The `<LoginCard>` component is not applicable until
|
||||
a Blazor login page is added.
|
||||
|
||||
---
|
||||
|
||||
## 5. StatusBadge component
|
||||
|
||||
**`StatusBadge.razor`** — string-match–based chip component.
|
||||
- Path: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/Components/Shared/StatusBadge.razor`
|
||||
- Parameters: `string? Text`. Maps known strings ("Ready", "Healthy", "Active", "Faulted",
|
||||
etc.) to `chip-ok` / `chip-warn` / `chip-bad` / `chip-idle` CSS classes.
|
||||
- After adoption: the string-matching logic is app-specific (based on gateway session
|
||||
state strings). Migration to `StatusPill` requires the caller to map gateway state
|
||||
strings to `StatusState` values, then pass `State` instead of `Text`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Divergences from spec
|
||||
|
||||
| Item | Current state | Spec |
|
||||
|---|---|---|
|
||||
| `theme.css` | Hand copy, 379 lines | Single canonical copy in RCL |
|
||||
| Font-path `url()` | `url('/fonts/…')` (absolute, not a bug but non-canonical) | `url('../fonts/…')` |
|
||||
| IBM Plex fonts | Vendored 3× in `wwwroot/fonts/` | Single copy in RCL `wwwroot/fonts/` |
|
||||
| Asset wiring | Root-relative `/css/…`, `/fonts/…` | `_content/ZB.MOM.WW.Theme/…` |
|
||||
| Shell class | `d-flex …` (no `.app-shell`) | `ThemeShell` + `.app-shell` |
|
||||
| Nav class idiom | `.sidebar` + `.nav-link` + `<ul>/<li>` | `.side-rail` + `.rail-link` + `<a>` |
|
||||
| Nav items | `<NavLink class="nav-link">` inside `<li>` | `<NavRailItem>` |
|
||||
| Nav sections | `NavSection` (button `OnToggle` + JS) | `NavRailSection` (`<details>`, CSS-only) |
|
||||
| Status chip | `StatusBadge` (string text → CSS class) | `StatusPill` (`StatusState` enum) |
|
||||
| Login page | None — server endpoint redirect only | `<LoginCard>` (if a Blazor login page is added) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Adoption plan
|
||||
|
||||
**Effort: High. Risk: High.** The sidebar idiom (`nav.sidebar` + `.nav-link` + `<ul><li>`)
|
||||
differs from the canonical rail idiom (`.side-rail` + `.rail-link` + `<a>`), requiring a
|
||||
CSS and markup migration. No layout redesign (it already uses a side-panel pattern), but
|
||||
class names, element structure, and the `site.css` sidebar block all change.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Delete copies.** Remove `wwwroot/css/theme.css` and `wwwroot/fonts/ibm-plex-*.woff2`
|
||||
from `src/ZB.MOM.WW.MxGateway.Server/`.
|
||||
|
||||
2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
|
||||
`ZB.MOM.WW.MxGateway.Server.csproj`. Add `@using ZB.MOM.WW.Theme` to `_Imports.razor`.
|
||||
|
||||
3. **Wire `ThemeHead`.** In `App.razor` replace `/css/theme.css` link with `<ThemeHead />`.
|
||||
Keep `/css/site.css` for domain-specific rules. Also change static asset paths from
|
||||
root-relative to `_content/ZB.MOM.WW.Theme/…` for fonts if any remain in `site.css`.
|
||||
|
||||
4. **Replace `MainLayout`.** Replace the 210-line `MainLayout.razor` with a thin wrapper
|
||||
around `<ThemeShell Product="MXAccess Gateway">`. Carry the nav sections and the sign-in
|
||||
link into the `Nav` and `RailFooter` slots respectively.
|
||||
|
||||
5. **Port nav items.** Migrate from `<NavLink class="nav-link">` inside `<li class="nav-item">`
|
||||
to `<NavRailItem Href="…" Text="…">`. The four section groups ("Runtime", "Galaxy",
|
||||
"Admin", "Configuration") map to `<NavRailSection Title="…">` children.
|
||||
|
||||
6. **Clean `site.css`.** Remove the `.sidebar` layout block (lines ~24–95) — superseded by
|
||||
`layout.css`. Keep all dashboard/domain-specific rules (`.agg-card`, tables, metric
|
||||
cards, etc.).
|
||||
|
||||
7. **Replace `StatusBadge`.** Add a helper that maps gateway session-state strings to
|
||||
`StatusState` values; replace `<StatusBadge Text="…">` call sites with
|
||||
`<StatusPill State="…">`. Delete `StatusBadge.razor`.
|
||||
|
||||
8. **Login card (optional).** No Blazor login page exists today. If one is added,
|
||||
`<LoginCard>` is the canonical implementation.
|
||||
|
||||
9. **Keep:** domain-specific `site.css` rules, scoped `.razor.css` files (none currently),
|
||||
API-key authentication, all page components.
|
||||
|
||||
**Flagged risk:** This is the largest UX-visible change across the three apps. The sidebar
|
||||
class migration (`.sidebar` → `.side-rail`, `.nav-link` → `.rail-link`) will visually
|
||||
change the nav styling. Verify visually in the dashboard before merging. The nav expand
|
||||
state persistence (JS-based) must be verified or replaced with CSS-only `<details>`.
|
||||
@@ -0,0 +1,163 @@
|
||||
# UI Theme — current state: OtOpcUa
|
||||
|
||||
Repo: `~/Desktop/OtOpcUa` (Gitea `lmxopcua`). Stack: .NET 10, Blazor SSR.
|
||||
UI surface: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/` (Razor Class Library).
|
||||
All paths below are relative to the repo root. Verified against source on 2026-06-01.
|
||||
|
||||
**Summary:** OtOpcUa already uses a side-rail layout and the full Technical-Light token
|
||||
set. Adoption is **lowest effort** of the three apps — the shell shape already matches the
|
||||
canonical target. The one bug fixed by adoption: a latent font-path 404 that silently
|
||||
falls back to system fonts today.
|
||||
|
||||
---
|
||||
|
||||
## 1. CSS / design tokens
|
||||
|
||||
**`theme.css`** — 379-line hand copy of the Technical-Light design system.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css`
|
||||
- Font path: `url('fonts/ibm-plex-sans-400.woff2')` (lines 24, 29, 34)
|
||||
- **Bug:** the path is relative to the CSS file location (`wwwroot/css/`), so it resolves
|
||||
as `wwwroot/css/fonts/…` — a 404. The browser silently falls back to system fonts. The
|
||||
canonical RCL path `url('../fonts/…')` fixes this permanently.
|
||||
- Wired in `App.razor` line 17:
|
||||
`<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/>`.
|
||||
|
||||
**`site.css`** — 174 lines of per-app page layout (side-rail shell, login card layout,
|
||||
page body padding, miscellaneous overrides).
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css`
|
||||
- Wired in `App.razor` line 18:
|
||||
`<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>`.
|
||||
- After adoption: the `.side-rail`, `.rail-*`, `.login-wrap`, `.login-title` rules are
|
||||
superseded by the RCL's `layout.css`. The page-layout residuals (body padding, page-
|
||||
specific overrides) stay in `site.css`.
|
||||
|
||||
---
|
||||
|
||||
## 2. IBM Plex fonts
|
||||
|
||||
Three `.woff2` files vendored into:
|
||||
`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/`
|
||||
- `ibm-plex-sans-400.woff2`
|
||||
- `ibm-plex-sans-600.woff2`
|
||||
- `ibm-plex-mono-500.woff2`
|
||||
|
||||
After adoption: delete all three; the RCL serves them from
|
||||
`_content/ZB.MOM.WW.Theme/fonts/`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Layout shell
|
||||
|
||||
**`MainLayout.razor`** — 28-line static layout (no `@rendermode`). Renders `.app-shell`
|
||||
flex row, hamburger toggle, `<NavSidebar/>` inside a Bootstrap collapse div, and
|
||||
`<main class="page">`.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor`
|
||||
- Structure: `.app-shell d-flex flex-column flex-lg-row` (line 8), hamburger (lines 11–18),
|
||||
`<div class="collapse d-lg-block" id="sidebar-collapse">` (line 21), `<NavSidebar />` (line 22),
|
||||
`<main class="page">@Body</main>` (lines 25–27).
|
||||
|
||||
**`NavSidebar.razor`** — 160-line interactive (`@rendermode InteractiveServer`) sidebar.
|
||||
Hosts the collapsible `NavSection` groups and cookie-persisted expand state.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor`
|
||||
- Brand: `<div class="brand"><span class="mark">▮</span> OtOpcUa</div>` (lines 14–14).
|
||||
- Nav sections: two `NavSection` groups ("Navigation", "Scripting", "Live", "Config")
|
||||
with `<NavLink class="rail-link">` children.
|
||||
- Rail foot (lines 44–62): `<div class="rail-foot"><AuthorizeView>` — session info + sign-out
|
||||
`<form method="post" action="/auth/logout">`.
|
||||
- Nav expand state persisted in `otopcua_nav` cookie via
|
||||
`wwwroot/js/nav-state.js` (cookie: `otopcua_nav=<comma-separated ids>`).
|
||||
|
||||
**`NavSection.razor`** — 36-line `NavSection` component (interactive; uses `EventCallback`
|
||||
`OnToggle` for expand/collapse, not CSS `<details>`).
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor`
|
||||
|
||||
**`LoginLayout.razor`** — plain layout (no sidebar) used by the login page.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/LoginLayout.razor`
|
||||
|
||||
---
|
||||
|
||||
## 4. Login page
|
||||
|
||||
**`Login.razor`** — 50-line static login page. Uses `@layout LoginLayout`,
|
||||
`@attribute [AllowAnonymous]`.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Login.razor`
|
||||
- Form: `<form method="post" action="/auth/login" data-enhance="false">` (line 21).
|
||||
- Hidden `returnUrl` input (line 22–25), username/password inputs, error notice panel.
|
||||
- The form structure exactly matches what `<LoginCard>` emits; migration is direct.
|
||||
|
||||
---
|
||||
|
||||
## 5. StatusBadge component
|
||||
|
||||
**`StatusBadge.razor`** — thin wrapper over `.chip` classes.
|
||||
- Path: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/StatusBadge.razor`
|
||||
- Parameters: `string Text`, `string CssClass` (default `chip-idle`).
|
||||
- After adoption: replaced by `<StatusPill State="…">` — caller maps state to `StatusState`
|
||||
enum rather than passing CSS class strings directly.
|
||||
|
||||
---
|
||||
|
||||
## 6. Divergences from spec
|
||||
|
||||
| Item | Current state | Spec |
|
||||
|---|---|---|
|
||||
| `theme.css` | Hand copy, 379 lines | Single canonical copy in RCL |
|
||||
| Font-path `url()` | `url('fonts/…')` — **latent 404** | `url('../fonts/…')` — correct |
|
||||
| IBM Plex fonts | Vendored 3× in `wwwroot/fonts/` | Single copy in RCL `wwwroot/fonts/` |
|
||||
| Shell layout | `.app-shell` + `NavSidebar` component (matches target shape) | `ThemeShell` + thin `MainLayout` |
|
||||
| Nav items | `<NavLink class="rail-link">` inside interactive `NavSidebar` | `NavRailItem` inside `NavRailSection` |
|
||||
| Nav expand state | Cookie-persisted via `otopcua_nav` + JS | CSS-only `<details>` in `NavRailSection` |
|
||||
| Status chip | `StatusBadge` (string CSS class param) | `StatusPill` (`StatusState` enum param) |
|
||||
| Login card | Inline markup in `Login.razor` | `<LoginCard>` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Adoption plan
|
||||
|
||||
**Effort: Low.** The shell shape already matches the target. No layout migration needed.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Delete copies.** Remove `wwwroot/css/theme.css` and `wwwroot/fonts/ibm-plex-*.woff2`
|
||||
from `ZB.MOM.WW.OtOpcUa.AdminUI`. This also fixes the latent font-path 404.
|
||||
|
||||
2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
|
||||
`ZB.MOM.WW.OtOpcUa.AdminUI.csproj`. Add `@using ZB.MOM.WW.Theme` to `_Imports.razor`.
|
||||
|
||||
3. **Wire `ThemeHead`.** In `App.razor` replace lines 17–18:
|
||||
```diff
|
||||
- <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/>
|
||||
- <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>
|
||||
+ <ThemeHead />
|
||||
+ <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>
|
||||
```
|
||||
(Keep `site.css` for the page-layout residuals.)
|
||||
|
||||
4. **Replace `MainLayout`.** Delete the current 28-line `MainLayout.razor`. Create a new
|
||||
thin `MainLayout.razor` that delegates to `<ThemeShell Product="OtOpcUa Admin">` with
|
||||
`Nav` and `RailFooter` slots (carry the session/sign-out block from `NavSidebar`'s
|
||||
`.rail-foot` into `RailFooter`).
|
||||
|
||||
5. **Port nav.** Rebuild the `Nav` slot using `<NavRailSection>` + `<NavRailItem>`. The
|
||||
four section groups ("Navigation", "Scripting", "Live", "Config") map directly to
|
||||
`NavRailSection Title="…"` with `NavRailItem` children.
|
||||
|
||||
**Cookie nav state:** OtOpcUa's `otopcua_nav` cookie persistence requires JS and an
|
||||
`InteractiveServer` component. If this feature is retained, keep a bespoke interactive
|
||||
`NavSection` (the current `NavSection.razor` or a refactored version) alongside — it
|
||||
is compatible with `ThemeShell`'s `Nav` slot. If cookie persistence is acceptable to
|
||||
drop, `NavRailSection` (CSS-only `<details>`) is a drop-in replacement.
|
||||
|
||||
6. **Replace `StatusBadge`.** Find all usages of `<StatusBadge CssClass="chip-*">` and
|
||||
replace with `<StatusPill State="StatusState.*">`. Delete `StatusBadge.razor`.
|
||||
|
||||
7. **Replace login card.** In `Login.razor`, replace the inline `<div class="login-wrap">
|
||||
… </div>` block with `<LoginCard Product="OtOpcUa Admin" Action="/auth/login"
|
||||
ReturnUrl="@ReturnUrl" Error="@Error"><AntiforgeryToken /></LoginCard>`. The code-behind
|
||||
(`Error` / `ReturnUrl` supply-from-query properties) stays unchanged.
|
||||
|
||||
8. **Keep:** `site.css` page-layout residuals; scoped `.razor.css` files (none currently in
|
||||
AdminUI); `LoginLayout.razor`; auth endpoints; all page components.
|
||||
|
||||
**Risk: Low** — layout shape already matches, no top-bar migration. Cookie nav state is
|
||||
the only optional complexity (decide retain vs drop).
|
||||
@@ -0,0 +1,165 @@
|
||||
# UI Theme — current state: ScadaBridge
|
||||
|
||||
Repo: `~/Desktop/ScadaBridge`. Stack: .NET 10, Blazor SSR (Akka.NET cluster + central UI).
|
||||
UI surfaces: `src/ZB.MOM.WW.ScadaBridge.CentralUI/` (RCL) and
|
||||
`src/ZB.MOM.WW.ScadaBridge.Host/` (the Blazor host that references it).
|
||||
All paths below are relative to the repo root. Verified against source on 2026-06-01.
|
||||
|
||||
**Summary:** ScadaBridge uses a sidebar nav layout and the Technical-Light tokens, with the
|
||||
correct font-path prefix. The sidebar uses `.sidebar` / `.nav-link` classes (same idiom as
|
||||
MxGateway), not `.side-rail` / `.rail-link`. Adoption is **medium effort** — sidebar-class
|
||||
migration + `MainLayout` replacement, no layout redesign. ScadaBridge has several
|
||||
scoped `.razor.css` files that stay per-project.
|
||||
|
||||
---
|
||||
|
||||
## 1. CSS / design tokens
|
||||
|
||||
**`theme.css`** — 379-line hand copy of the Technical-Light design system.
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.css`
|
||||
- Font path: `url('../fonts/ibm-plex-sans-400.woff2')` (lines 24, 29, 34)
|
||||
- **Correct path** — resolves from `wwwroot/css/` to `wwwroot/fonts/` without 404. This is
|
||||
the canonical `url('../fonts/…')` that the RCL also uses.
|
||||
- Wired in the Host's `App.razor` line 9:
|
||||
`<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/theme.css" rel="stylesheet" />`.
|
||||
|
||||
**`site.css`** — 128 lines of per-app page layout (sidebar shell, nav overrides).
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/site.css`
|
||||
- Wired in the Host's `App.razor` line 11:
|
||||
`<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/site.css" rel="stylesheet" />`.
|
||||
- Contains: `.sidebar` layout block (~4–95), Bootstrap-icons integration for nav items.
|
||||
- After adoption: the `.sidebar` layout section is superseded by RCL `layout.css`. The
|
||||
remaining rules (Bootstrap-icons, misc overrides) stay in `site.css`.
|
||||
|
||||
Note: ScadaBridge uses the `_content/ZB.MOM.WW.ScadaBridge.CentralUI/…` static-web-asset
|
||||
path for its own CentralUI RCL assets — the same mechanism `ZB.MOM.WW.Theme` will use.
|
||||
|
||||
---
|
||||
|
||||
## 2. IBM Plex fonts
|
||||
|
||||
Three `.woff2` files vendored into:
|
||||
`src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/fonts/`
|
||||
- `ibm-plex-sans-400.woff2`
|
||||
- `ibm-plex-sans-600.woff2`
|
||||
- `ibm-plex-mono-500.woff2`
|
||||
|
||||
After adoption: delete all three from `CentralUI/wwwroot/fonts/`; the RCL serves them
|
||||
from `_content/ZB.MOM.WW.Theme/fonts/`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Layout shell
|
||||
|
||||
**`MainLayout.razor`** — 29-line static layout. `@inherits LayoutComponentBase`. No
|
||||
`@rendermode` directive (static SSR).
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/MainLayout.razor`
|
||||
- Root element: `<div class="d-flex flex-column flex-lg-row" style="min-height: 100vh;">`.
|
||||
No `.app-shell` class.
|
||||
- Renders `<NavMenu />` inside a Bootstrap collapse div, `<main class="flex-grow-1 p-3">`,
|
||||
plus `<DialogHost />` and `<SessionExpiry />` at the bottom.
|
||||
|
||||
**`NavMenu.razor`** — 200+ line interactive sidebar component. `@implements IDisposable`.
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor`
|
||||
- Brand: `<div class="brand"><span class="mark">▮</span> ScadaBridge</div>` (lines ~9–9).
|
||||
- Nav structure: `<nav class="sidebar d-flex flex-column">` with `<ul class="nav flex-column">`
|
||||
and `<NavSection>` groups ("Admin", "Data", "Audit", etc.) with `<NavLink class="nav-link">`
|
||||
children. Uses `AuthorizeView` + `AuthorizeView Policy="…"` to gate admin sections.
|
||||
- Nav state: JS-based expand-state persistence (same pattern as OtOpcUa and MxGateway).
|
||||
|
||||
**`NavSection.razor`** (same name as OtOpcUa/MxGateway, independent per-project copy).
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavSection.razor`
|
||||
|
||||
---
|
||||
|
||||
## 4. Login page
|
||||
|
||||
**`Login.razor`** — 36-line static login page. `@layout LoginLayout`, `@attribute [AllowAnonymous]`.
|
||||
- Path: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Login.razor`
|
||||
- Form: `<form method="post" action="/auth/login" data-enhance="false">` (line 16).
|
||||
- Uses Bootstrap `.card` / `.card-body` markup — **not** the Technical-Light `.panel` /
|
||||
`.login-wrap` idiom used in OtOpcUa. Does not use a `<LoginCard>` yet.
|
||||
- Error notice: Bootstrap `.alert alert-danger` (line 12) rather than `.panel.notice`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Scoped `.razor.css` files (stays per-project)
|
||||
|
||||
ScadaBridge ships several component-scoped CSS files. These are **not shared** and stay
|
||||
in the CentralUI RCL after adoption:
|
||||
|
||||
| File | Path |
|
||||
|---|---|
|
||||
| `MultiSelectDropdown.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/` |
|
||||
| `TreeView.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/` |
|
||||
| `AuditDrilldownDrawer.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
|
||||
| `AuditEventDetail.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
|
||||
| `ExecutionDetailModal.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
|
||||
| `ExecutionTree.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
|
||||
| `AuditResultsGrid.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/` |
|
||||
| `NodeBrowserDialog.razor.css` | `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/` |
|
||||
|
||||
These scoped styles are component-specific overrides and are unaffected by theme adoption.
|
||||
|
||||
---
|
||||
|
||||
## 6. Divergences from spec
|
||||
|
||||
| Item | Current state | Spec |
|
||||
|---|---|---|
|
||||
| `theme.css` | Hand copy, 379 lines | Single canonical copy in RCL |
|
||||
| Font-path `url()` | `url('../fonts/…')` — **correct** | `url('../fonts/…')` — same |
|
||||
| IBM Plex fonts | Vendored 3× in `wwwroot/fonts/` | Single copy in RCL `wwwroot/fonts/` |
|
||||
| Shell class | `d-flex …` (no `.app-shell`) | `ThemeShell` + `.app-shell` |
|
||||
| Nav class idiom | `.sidebar` + `.nav-link` + `<ul>/<li>` | `.side-rail` + `.rail-link` + `<a>` |
|
||||
| Nav items | `<NavLink class="nav-link">` inside `<li>` | `<NavRailItem>` |
|
||||
| Nav sections | `NavSection` (`EventCallback OnToggle` + JS) | `NavRailSection` (`<details>`, CSS-only) |
|
||||
| Status chip | None (uses raw `.chip-*` classes inline) | `StatusPill` (`StatusState` enum) |
|
||||
| Login card | Bootstrap `.card` markup (not Technical-Light `.panel`) | `<LoginCard>` |
|
||||
| Scoped `.razor.css` | 8 component-scoped files | Stays per-project (no change) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Adoption plan
|
||||
|
||||
**Effort: Medium. Risk: Medium.** The font path is already correct so no 404 fix needed.
|
||||
The sidebar idiom migration (`.sidebar`→`.side-rail`, `.nav-link`→`.rail-link`) and the
|
||||
`MainLayout` replacement are the main work. The scoped `.razor.css` files are unaffected.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Delete copies.** Remove `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.css`
|
||||
and `wwwroot/fonts/ibm-plex-*.woff2` from `CentralUI`.
|
||||
|
||||
2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
|
||||
`ZB.MOM.WW.ScadaBridge.CentralUI.csproj`. Add `@using ZB.MOM.WW.Theme` to
|
||||
`CentralUI`'s `_Imports.razor`.
|
||||
|
||||
3. **Wire `ThemeHead`.** In `Host`'s `App.razor`, replace line 9
|
||||
(`<link href="_content/…/css/theme.css">`) with `<ThemeHead />` (which now resolves
|
||||
via `ZB.MOM.WW.Theme`). Keep the `site.css` link on line 11.
|
||||
|
||||
4. **Replace `MainLayout`.** Replace the 29-line `MainLayout.razor` with a thin wrapper
|
||||
around `<ThemeShell Product="ScadaBridge">`. Carry `<NavMenu />` into the `Nav` slot
|
||||
(or replace it — see step 5). Keep `<DialogHost />` and `<SessionExpiry />` below the
|
||||
`ThemeShell` or inside `ChildContent` as needed.
|
||||
|
||||
5. **Port nav.** Migrate `NavMenu.razor` from `nav.sidebar` + `<ul>/<li>` + `.nav-link`
|
||||
to `<NavRailSection>` + `<NavRailItem>`. The `AuthorizeView` policy gating on admin
|
||||
sections stays — wrap `<NavRailSection>` inside the appropriate `<AuthorizeView>` just
|
||||
as today.
|
||||
|
||||
6. **Clean `site.css`.** Remove the `.sidebar` layout block. Keep Bootstrap-icons
|
||||
integration and any domain-specific overrides.
|
||||
|
||||
7. **Replace login card.** In `Login.razor`, replace the Bootstrap `.card`/`.card-body`
|
||||
markup with `<LoginCard Product="ScadaBridge" Action="/auth/login" ReturnUrl="@ReturnUrl"
|
||||
Error="@ErrorMessage"><AntiforgeryToken /></LoginCard>`. Align error display with the
|
||||
Technical-Light `.panel.notice` style from `LoginCard`.
|
||||
|
||||
8. **Keep:** all scoped `.razor.css` files (8 files listed above); `site.css` domain rules;
|
||||
auth and session endpoints; all page components.
|
||||
|
||||
**Risk note:** ScadaBridge's `AuthorizeView` policy-gated nav sections require careful
|
||||
testing — verify that `<NavRailSection>` inside `<AuthorizeView>` renders correctly and
|
||||
that the section is fully hidden when the policy fails (not just collapsed).
|
||||
Reference in New Issue
Block a user