Resolve audit findings: correct WorkerEnvelope proto/route/metric/session facts; rewrite auth (ZB.MOM.WW.Auth migration), dashboard (ZB.MOM.WW.Theme), and StyleGuide (foreign-project copy-paste); document alarm subsystem, Ldap options, and gateway alarm broker; fix client CLI flags and package paths.
13 KiB
Dashboard Interface Design
This guide describes the visual and interaction patterns used by the MXAccess Gateway dashboard so the same interface style can be reused in other operations-focused projects.
Design Goal
The dashboard is an operational interface, not a landing page. It prioritizes
fast scanning, low visual noise, and stable layouts while live data changes.
The layout chrome, status presentation, and design tokens come from the shared
ZB.MOM.WW.Theme kit (the technical-light design system). Bootstrap supplies
common widget behavior, and a small local stylesheet (wwwroot/css/site.css)
wires the dashboard's own class names and Bootstrap widgets onto the kit's
tokens. The local sheet contains no hard-coded colors; every color, font, and
surface resolves to a theme token.
Use this style for applications where users repeatedly check system state, compare rows, inspect details, and diagnose faults. Avoid promotional layouts, large hero areas, decorative imagery, or oversized cards that reduce data density.
Visual Language
The interface uses a quiet, work-focused visual system:
- A light gray page background separates the application shell from white data surfaces.
- White cards and sections carry the actual operational content.
- Borders define structure more often than shadows.
- Accent color is reserved for metric values and important numeric signals.
- The kit's
StatusPillprovides state color without custom status art. - Tables remain compact and responsive so long identifiers and timestamps stay readable.
The resulting page should look like a control surface: restrained, predictable, and dense enough for repeated use.
Layout Structure
The application chassis is the kit's ThemeShell component (a vertical side
rail plus a content area), not a horizontal top navbar. MainLayout.razor is a
thin wrapper that delegates the rail chassis — brand block, hamburger toggle,
responsive collapse — to <ThemeShell> and supplies only the navigation items
and a rail footer:
<ThemeShell Product="MXAccess Gateway" Accent="#2f5fd0">
<Nav>
<NavRailItem Href="/" Text="Dashboard" Match="NavLinkMatch.All" />
<NavRailSection Title="Runtime" Key="runtime">
<NavRailItem Href="/sessions" Text="Sessions" />
<NavRailItem Href="/workers" Text="Workers" />
</NavRailSection>
</Nav>
<RailFooter><!-- user name + sign-out --></RailFooter>
<ChildContent>@Body</ChildContent>
</ThemeShell>
Within the content area, every page follows the same structure:
- A page header with the page title, short context text, and optional status pill.
- Metric cards when a page has top-level numeric state.
- Bordered content sections for tables, details, faults, or empty states.
The login page uses LoginLayout.razor instead — a minimal layout with no rail
and no brand block, because the page renders its own centered <LoginCard>.
Color Tokens
Colors come from the ZB.MOM.WW.Theme kit's theme.css. The local
site.css defines no :root custom properties of its own; it references kit
tokens by name. The dashboard does not define a --mxgw-* token set.
| Token | Purpose |
|---|---|
var(--card) |
Background of cards, sections, and data tables. |
var(--rule), var(--rule-strong) |
Hairline and stronger borders. |
var(--ink), var(--ink-soft), var(--ink-faint) |
Primary, secondary, and muted text. |
var(--accent), var(--accent-deep) |
Metric values, links, primary buttons, focus rings. |
var(--mono) |
Monospace family for values, identifiers, and code. |
var(--ok)/--ok-bg, var(--warn)/--warn-bg, var(--bad)/--bad-bg, var(--idle)/--idle-bg |
State colors for chips, alerts, and alarm-state labels. |
Keep the palette small and let the kit own it. Add new colors only when they
encode state or improve readability, and resolve them to a kit token rather than
a literal hex value. Use the kit's StatusPill for states such as ready,
closing, idle, and faulted.
Typography
Typography stays compact and consistent:
- Page headings (
.dashboard-page-header h1) use1.15rem, weight600, and a slight letter spacing. - Section headings (
.section-heading h2) use a small uppercase eyebrow:.74rem, weight600, muted ink. - Metric labels (
.agg-label) use uppercase text at.68remand weight600, muted ink. - Metric values (
.agg-value) use1.5rem, weight600, the monospace family, tabular numerics, and primary ink (var(--ink)). - Body and table text inherit Bootstrap defaults for readability.
Do not scale text with viewport width. Long values use overflow-wrap: break-word (numbers and date tokens stay whole, wrapping only at spaces); a few
free-form fields such as .agg-sub use overflow-wrap: anywhere so session
IDs, paths, and fault messages do not break the layout.
Spacing And Shape
The dashboard uses modest spacing:
- The kit owns the rail and content padding; the local small-screen rule sets
.pagepadding to.85rem. - Metric grids use
.75remgaps. - Content sections (
.dashboard-section) and metric cards (.agg-card) are fully bordered cards:var(--card)fill, a1px solid var(--rule)hairline, and0.9rempadding for sections. - Cards, sections, and modals use an
8pxradius; smaller widgets such as the empty state use6px. - Metric cards have no shadow (
box-shadow: none); borders define structure.
This keeps information grouped without turning each section into a decorative panel. Use cards for repeated metric summaries, login forms, and individual items. Use bordered sections for page-level groups.
Navigation
Navigation lives in the ThemeShell side rail. It is built from the kit's
NavRailSection and NavRailItem components: a single home item plus eight
page items grouped into three labeled sections.
| Section | Items |
|---|---|
| (home) | Dashboard (route /, NavLinkMatch.All) |
| Runtime | Sessions, Workers, Events, Alarms |
| Galaxy | Repository, Browse |
| Admin | API Keys, Settings |
Section expand/collapse state is owned by the kit (a <details> element plus
ThemeScripts); the layout does not run JS interop for it. The rail footer
shows the signed-in user name and a sign-out form (or a sign-in link when
unauthenticated).
Keep navigation labels short and group related pages. Operational users should be able to predict what each page contains without reading explanatory copy.
Page Headers
Each page starts with a dashboard-page-header:
- The title is the primary anchor.
- A single secondary line gives timestamp, row count, or configuration context.
- A status pill appears on the right when the page has an overall state.
On narrow screens, the header stacks vertically. This prevents long context text or status pills from overlapping the title.
<div class="dashboard-page-header">
<div>
<h1>Dashboard</h1>
<div class="text-secondary">Generated 2026-04-27 17:30:00</div>
</div>
<!-- <StatusBadge Text="Healthy" /> -> kit <StatusPill State="Ok"> -->
</div>
Metric Cards
Metric cards summarize numeric state at the top of the home and diagnostic
pages. The MetricCard component renders an .agg-card with label, value, and
optional sub-line:
- Label (
.agg-label): uppercase eyebrow, muted, compact. - Value (
.agg-value): large monospace number in primary ink, wraps safely. - Sub (
.agg-sub): optional muted text for version, rate context, or state.
Cards lay out in a .metric-grid. Use auto-fill CSS grid tracks so they fill
available width without custom breakpoints:
.metric-grid {
display: grid;
gap: .75rem;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.metric-grid.compact {
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
Metrics should be formatted before rendering. Counts use thousands separators,
durations use stable units, and missing values render as -.
Tables
Tables are the main information surface. Use Bootstrap table table-sm with a
local dashboard-table class:
table-smkeeps rows dense.align-middleimproves status badge alignment.table-responsivewraps every table that can exceed the viewport.- Header cells use weight
650and no wrapping. - Body cells allow wrapping so identifiers, paths, and messages stay visible.
- Detail tables reserve a fixed header width.
Use code formatting for machine identifiers such as session IDs, file paths, and protocol values. Link rows only where navigation is useful; avoid making entire rows clickable when a single identifier link is clearer.
Status Badges
StatusBadge is a thin adapter over the kit's StatusPill. Call sites pass the
literal domain state text (<StatusBadge Text="Ready" />); the adapter maps
that text to one of the kit's four StatusState values, and StatusPill
renders the chip. There are no Bootstrap text-bg-* classes in this layer.
| Domain state text | StatusState |
|---|---|
Ready, Healthy, Active |
Ok |
Creating, StartingWorker, WaitingForPipe, InitializingWorker, Closing, Stale, Degraded |
Warn |
Faulted, Unavailable |
Bad |
Any other text (including Closed, Revoked, Unknown) |
Idle |
Note the mapping changes from earlier revisions: Closed now falls through to
Idle (rather than its own neutral badge), and Active, Stale, Degraded,
and Unavailable are explicit cases. The kit owns the chip rendering; only this
domain text-to-state vocabulary lives in the app.
Keep status text literal. Operators benefit from seeing the same state names that appear in logs and APIs.
Empty And Loading States
Empty states are explicit and quiet. They use a white background, dashed border, small radius, muted text, and one sentence:
<div class="empty-state">No worker processes are attached.</div>
Loading states use the same component shape. Avoid spinners for snapshot pages that update on a timer; a stable text placeholder is less distracting.
Detail Pages
Detail pages use stacked sections instead of nested cards:
- The page header identifies the selected entity.
- The first section shows entity metadata in a two-column details table.
- Additional sections show related runtime state, such as worker metadata.
- Missing entities render a single section with a concise not-found message.
This structure keeps details comparable across pages and avoids card nesting.
Responsive Behavior
The dashboard uses one small-screen breakpoint:
@media (max-width: 700px) {
.page {
padding: .85rem;
}
.dashboard-page-header {
align-items: flex-start;
flex-direction: column;
}
.details-table th {
width: 9rem;
}
}
A second breakpoint (max-width: 960px) collapses the Browse two-pane layout
(.browse-layout) to a single column.
Do not hide important columns by default. Use horizontal table scrolling for dense operational data, and reserve column hiding for data that is clearly duplicative.
Data Formatting
Use a small display helper instead of formatting inline in every component. The helper should provide consistent rendering for:
- empty text as
-, - counts with thousands separators,
- dates and times in a consistent local or configured format,
- durations in stable units,
- metric lookup by name and dimension.
Centralizing formatting prevents visual drift between overview cards, tables, and detail pages.
Security And Redaction
The interface is read-only unless an explicit administrative action is designed. It should not display secrets or raw credential-bearing values.
Apply redaction before values reach Razor components. The UI treats redacted values as normal display text; it does not need to know why a value is hidden. This keeps security policy in the dashboard projection layer rather than in markup.
Replication Checklist
Use this checklist when applying the design to another project:
- Take colors, fonts, and surfaces from the
ZB.MOM.WW.Themekit tokens; do not define a local color token set. - Use the kit's
ThemeShellside rail withNavRailSection/NavRailItemand short route labels grouped into sections. - Start every page with the same header structure.
- Put primary numeric state in
metric-grid/agg-cardcards. - Put detailed runtime state in compact responsive tables.
- Use
StatusBadge(kitStatusPill) mapped from real domain states. - Use dashed bordered empty states for loading and no-data cases.
- Use top-bordered sections for page groups instead of nested cards.
- Centralize formatting and redaction outside Razor markup.
- Hide every destructive admin affordance from viewers; render it only for
the
Administratorrole and re-check the role server-side on every invocation. - Route every destructive action (Close session, Kill worker, Rotate /
Revoke / Delete API key) through the shared
ConfirmDialogcomponent so the operator always gets one explicit confirmation step before the call reaches the service.