diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css index 86474a57..d58efd1e 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css @@ -172,3 +172,13 @@ max-width: 380px; margin: 3.5rem auto 0; } + +/* --- App-specific rules not provided by ZB.MOM.WW.Theme (migrated during theme adoption) --- */ +/* OtOpcUa domain pages (Alerts, ScriptLog, Fleet, Hosts, AlarmsHistorian, + RoleGrants, ImportEquipment) use two extra status-chip variants on top of the + kit's .chip base + .chip-ok/.chip-warn/.chip-bad/.chip-idle/.chip-info set. + .chip-alert is the red/danger variant (mirrors the kit's .chip-bad); + .chip-caution is the amber variant (mirrors the kit's .chip-warn). Both reuse + the kit's status tokens so they stay on-palette. */ +.chip-alert { color: var(--bad); background: var(--bad-bg); border-color: var(--bad-border); } +.chip-caution { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-border); } diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css deleted file mode 100644 index 72513c49..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css +++ /dev/null @@ -1,379 +0,0 @@ -/* ============================================================================ - Technical-Light design system — portable theme layer - ---------------------------------------------------------------------------- - A refined technical-light aesthetic: warm-neutral paper, hairline rules, - IBM Plex type, monospace tabular numerics, status carried by colour. Built - to layer over Bootstrap 5 via --bs-* overrides, but every rule below works - standalone — Bootstrap is optional. - - HOW TO ADOPT - 1. Serve the three IBM Plex woff2 files (shipped in fonts/) and fix the - @font-face url() paths below to wherever you serve them. - 2. Include this file once, globally. Add view-specific rules in a separate - stylesheet — never edit the token block per-view. - 3. Status is colour, not iconography. Use the .s-* / .chip-* / .kv .v.* - helpers; do not hand-pick hex values in feature CSS. - ========================================================================= */ - -/* ── Vendored fonts (embedded woff2, no network/CDN fetch) ─────────────────── - Adjust these url()s to your asset route. If you cannot vendor the fonts the - --sans / --mono fallback stacks below degrade gracefully to system fonts. */ -@font-face { - font-family: 'IBM Plex Sans'; - font-style: normal; font-weight: 400; font-display: swap; - src: url('fonts/ibm-plex-sans-400.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Sans'; - font-style: normal; font-weight: 600; font-display: swap; - src: url('fonts/ibm-plex-sans-600.woff2') format('woff2'); -} -@font-face { - font-family: 'IBM Plex Mono'; - font-style: normal; font-weight: 500; font-display: swap; - src: url('fonts/ibm-plex-mono-500.woff2') format('woff2'); -} - -/* ── Design tokens ─────────────────────────────────────────────────────────── - The single source of truth. Re-theme by editing only this block. */ -:root { - /* Surfaces & ink */ - --paper: #f4f4f1; /* page background — warm off-white, never pure */ - --card: #ffffff; /* raised surfaces: cards, bars, table heads */ - --ink: #1b1d21; /* primary text */ - --ink-soft: #5a6066; /* secondary text, labels */ - --ink-faint: #8b9097; /* tertiary text, captions, units */ - --rule: #e4e4df; /* hairline borders / row dividers */ - --rule-strong: #d2d2cb; /* emphasised hairlines: bar underline, pills */ - - /* Accent */ - --accent: #2f5fd0; /* links, sort arrows, primary actions */ - --accent-deep: #1e3f99; /* hover / pressed accent, raw-value emphasis */ - - /* Status — foreground */ - --ok: #2f9e44; - --warn: #e8920c; - --bad: #e03131; - --idle: #868e96; - - /* Status — tinted backgrounds (pair with the matching foreground) */ - --ok-bg: #e9f6ec; - --warn-bg: #fdf1dd; - --bad-bg: #fceaea; - --idle-bg: #eef0f2; - - /* Type stacks — Plex first, graceful system fallback */ - --mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace; - --sans: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif; - - /* Bootstrap 5 overrides — harmless if Bootstrap is absent */ - --bs-body-bg: var(--paper); - --bs-body-color: var(--ink); - --bs-body-font-family: var(--sans); - --bs-body-font-size: 0.9rem; - --bs-primary: var(--accent); - --bs-border-color: var(--rule); - --bs-emphasis-color: var(--ink); -} - -/* ── Base ──────────────────────────────────────────────────────────────────── - The faint top-right radial is the one deliberate flourish — a soft sheen, - not a gradient wash. Keep it subtle. */ -body { - background: - radial-gradient(1200px 480px at 88% -8%, #ffffff 0%, rgba(255,255,255,0) 70%), - var(--paper); - color: var(--ink); - font-family: var(--sans); - font-size: 0.9rem; - -webkit-font-smoothing: antialiased; -} - -/* Any numeric / fixed-width text. Tabular figures so columns of digits align. */ -.numeric, -.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; } - -a { color: var(--accent); text-decoration: none; } -a:hover { color: var(--accent-deep); text-decoration: underline; } - -/* ── App chrome: top bar ───────────────────────────────────────────────────── - One bar across the top: brand, breadcrumb crumbs, a flex spacer, then meta - text and any status pill pushed hard right. */ -.app-bar { - display: flex; - align-items: baseline; - gap: 1rem; - padding: 0.85rem 1.25rem; - background: var(--card); - border-bottom: 1px solid var(--rule-strong); -} -.app-bar .brand { - font-weight: 600; - font-size: 1.05rem; - letter-spacing: 0.02em; -} -.app-bar .brand .mark { color: var(--accent); } /* the one accent glyph */ -.app-bar .crumb { color: var(--ink-faint); font-size: 0.85rem; } -.app-bar .spacer { flex: 1; } /* pushes meta/pill right */ -.app-bar .meta { - font-family: var(--mono); - font-size: 0.78rem; - color: var(--ink-soft); -} - -/* ── Connection / liveness pill ────────────────────────────────────────────── - A rounded pill with a dot, driven entirely by data-state. Use for any - live-link health indicator (websocket, SSE, polling). */ -.conn-pill { - display: inline-flex; - align-items: center; - gap: 0.4rem; - font-size: 0.74rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - padding: 0.2rem 0.6rem; - border-radius: 999px; - border: 1px solid var(--rule-strong); - color: var(--ink-soft); - background: var(--card); -} -.conn-pill .dot { - width: 7px; height: 7px; border-radius: 50%; - background: var(--idle); -} -.conn-pill[data-state="connected"] { color: var(--ok); border-color: #bfe3c6; background: var(--ok-bg); } -.conn-pill[data-state="connected"] .dot { background: var(--ok); } -.conn-pill[data-state="connecting"] { color: var(--warn); border-color: #f0d9ab; background: var(--warn-bg); } -.conn-pill[data-state="connecting"] .dot { background: var(--warn); animation: pulse 1.1s ease-in-out infinite; } -.conn-pill[data-state="disconnected"] { color: var(--bad); border-color: #f0c0c0; background: var(--bad-bg); } -.conn-pill[data-state="disconnected"] .dot { background: var(--bad); } - -@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.25; } } - -/* ── Status text helpers ───────────────────────────────────────────────────── - Recolour a value in place — counts, ratios, error totals. */ -.s-ok { color: var(--ok); } -.s-warn { color: var(--warn); } -.s-bad { color: var(--bad); } -.s-idle { color: var(--idle); } - -/* ── State chip ────────────────────────────────────────────────────────────── - Compact rectangular badge for an enumerated state (bound/recovering/…). - Squarer than the pill; use the pill for liveness, the chip for state. */ -.chip { - display: inline-block; - font-size: 0.72rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 0.15rem 0.5rem; - border-radius: 4px; - border: 1px solid transparent; -} -.chip-ok { color: var(--ok); background: var(--ok-bg); border-color: #c6e6cd; } -.chip-warn { color: #b56a00; background: var(--warn-bg); border-color: #efd6a6; } -.chip-bad { color: var(--bad); background: var(--bad-bg); border-color: #eec3c3; } -.chip-idle { color: var(--ink-soft); background: var(--idle-bg); border-color: var(--rule-strong); } - -/* ── Panel — the base raised surface ───────────────────────────────────────── - A white card with a hairline border and 8px radius. .panel-head is the - uppercase eyebrow label that sits on top. */ -.panel { - background: var(--card); - border: 1px solid var(--rule); - border-radius: 8px; -} -.panel-head { - font-size: 0.74rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.07em; - color: var(--ink-faint); - padding: 0.6rem 0.9rem; - border-bottom: 1px solid var(--rule); -} - -/* ── Page wrapper ──────────────────────────────────────────────────────────── - Centred, capped width, even gutter. */ -.page { padding: 1.25rem; max-width: 1680px; margin: 0 auto; } - -/* ── Reveal-on-paint ───────────────────────────────────────────────────────── - Add .rise to top-level sections; stagger with inline animation-delay - (.02s, .08s, .14s …) so panels settle in sequence, not all at once. */ -@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } } -.rise { animation: rise 0.4s ease both; } - -/* ════════════════════════════════════════════════════════════════════════════ - COMPONENT LIBRARY - Generic, reusable pieces. View-specific layout belongs in a separate sheet. - ════════════════════════════════════════════════════════════════════════════ */ - -/* ── KPI / aggregate cards ─────────────────────────────────────────────────── - A responsive strip of headline numbers. .agg-card.alert / .caution tint the - whole card when a watched metric goes non-zero. */ -.agg-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 0.75rem; - margin-bottom: 1rem; -} -@media (max-width: 1100px) { .agg-grid { grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 620px) { .agg-grid { grid-template-columns: repeat(2, 1fr); } } - -.agg-card { - background: var(--card); - border: 1px solid var(--rule); - border-radius: 8px; - padding: 0.7rem 0.9rem; -} -.agg-label { - font-size: 0.68rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.07em; - color: var(--ink-faint); -} -.agg-value { - margin-top: 0.25rem; - font-size: 1.5rem; - font-weight: 600; - line-height: 1.1; - display: flex; - align-items: baseline; - gap: 0.35rem; -} -.agg-sub { /* trailing "/ 54", "ms" etc. — quieter */ - font-size: 0.85rem; - font-weight: 400; - color: var(--ink-faint); -} -.agg-card.alert { border-color: #eec3c3; background: var(--bad-bg); } -.agg-card.alert .agg-value { color: var(--bad); } -.agg-card.caution { border-color: #efd6a6; background: var(--warn-bg); } -.agg-card.caution .agg-value { color: #b56a00; } - -/* ── Metric card + key/value rows ──────────────────────────────────────────── - A .panel-head over a stack of .kv rows: label left, monospace value right. - Zebra striping on even rows. .v.warn / .v.bad / .v.ok recolour a value. */ -.card-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); - gap: 0.85rem; - margin-bottom: 1rem; -} -.metric-card { - background: var(--card); - border: 1px solid var(--rule); - border-radius: 8px; - overflow: hidden; -} -.metric-card .panel-head { margin: 0; } - -.kv { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 1rem; - padding: 0.32rem 0.9rem; - font-size: 0.85rem; -} -.kv:nth-child(even) { background: #fbfbf9; } -.kv .k { color: var(--ink-soft); } -.kv .v { - font-family: var(--mono); - font-variant-numeric: tabular-nums; - text-align: right; -} -.kv .v.warn { color: var(--warn); } -.kv .v.bad { color: var(--bad); } -.kv .v.ok { color: var(--ok); } - -/* ── Toolbar ───────────────────────────────────────────────────────────────── - Filter/search row that sits inside a .panel above a table. */ -.toolbar { - display: flex; - align-items: center; - gap: 0.6rem; - padding: 0.6rem 0.9rem; - border-bottom: 1px solid var(--rule); -} -.toolbar .spacer { flex: 1; } -.tb-search { max-width: 280px; } -.tb-state { max-width: 150px; } -.tb-check { - display: flex; align-items: center; gap: 0.35rem; - font-size: 0.82rem; color: var(--ink-soft); white-space: nowrap; - user-select: none; -} -.tb-count { font-family: var(--mono); font-size: 0.78rem; color: var(--ink-faint); } - -/* ── Data table ────────────────────────────────────────────────────────────── - Dense, hairline-ruled table. Uppercase sticky head on a faint fill; numeric - columns get .num (right-aligned, monospace). Rows are clickable by default — - drop the cursor/hover rules if yours are not. */ -.table-wrap { overflow-x: auto; } - -.data-table { - width: 100%; - border-collapse: collapse; - font-size: 0.85rem; -} -.data-table th, -.data-table td { - padding: 0.45rem 0.8rem; - text-align: left; - white-space: nowrap; - border-bottom: 1px solid var(--rule); -} -.data-table th { - font-size: 0.7rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--ink-faint); - background: #fbfbf9; - position: sticky; - top: 0; -} -.data-table th.num, -.data-table td.num { text-align: right; font-family: var(--mono); } - -.data-table th.sortable { cursor: pointer; user-select: none; } -.data-table th.sortable:hover { color: var(--ink); } -.data-table th.sorted-asc::after { content: ' \2191'; color: var(--accent); } -.data-table th.sorted-desc::after { content: ' \2193'; color: var(--accent); } - -.data-table tbody tr { cursor: pointer; transition: background 0.08s; } -.data-table tbody tr:hover { background: #f3f6fd; } -.data-table tbody tr:last-child td { border-bottom: none; } - -.empty-row { - text-align: center !important; - color: var(--ink-faint); - padding: 1.6rem !important; - font-style: italic; -} - -/* ── Direction / category tag ──────────────────────────────────────────────── - Tiny inline tag for a per-row category (e.g. read vs write). */ -.dir-tag { - font-size: 0.68rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 0.1rem 0.4rem; - border-radius: 3px; -} -.dir-read { color: var(--accent-deep); background: #e7ecfb; } -.dir-write { color: #8a5a00; background: var(--warn-bg); } - -/* ── Inline notice ─────────────────────────────────────────────────────────── - A .panel with a warning tint — for "this thing is gone / degraded" banners. */ -.notice { - padding: 0.85rem 1.1rem; - margin-bottom: 1rem; - color: #b56a00; - background: var(--warn-bg); - border-color: #efd6a6; -} diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-mono-500.woff2 b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-mono-500.woff2 deleted file mode 100644 index 99c26103..00000000 Binary files a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-mono-500.woff2 and /dev/null differ diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-400.woff2 b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-400.woff2 deleted file mode 100644 index 93bcd643..00000000 Binary files a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-400.woff2 and /dev/null differ diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-600.woff2 b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-600.woff2 deleted file mode 100644 index 0ac91d60..00000000 Binary files a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/ibm-plex-sans-600.woff2 and /dev/null differ diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/nav-state.js b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/nav-state.js deleted file mode 100644 index 75c10adc..00000000 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/js/nav-state.js +++ /dev/null @@ -1,19 +0,0 @@ -// Sidebar nav collapse state — persisted in the `otopcua_nav` cookie so it -// survives full page reloads and reconnects. Invoked from MainLayout.razor via -// JS interop (window.navState.get / .set). Mirrors the ScadaLink pattern at -// /Users/dohertj2/Desktop/scadalink-design/src/ScadaLink.CentralUI/wwwroot/js/nav-state.js. -window.navState = { - // Returns the raw cookie value (comma-separated expanded section ids), or - // an empty string when the cookie is absent. - get: function () { - const match = document.cookie.match(/(?:^|;\s*)otopcua_nav=([^;]*)/); - return match ? decodeURIComponent(match[1]) : ""; - }, - // Writes the cookie with a one-year lifetime. SameSite=Lax; not HttpOnly - // (JS must write it) and not sensitive. - set: function (value) { - const oneYearSeconds = 60 * 60 * 24 * 365; - document.cookie = "otopcua_nav=" + encodeURIComponent(value) + - ";path=/;max-age=" + oneYearSeconds + ";samesite=lax"; - } -};