Files
pxe-server/webapp/static/pxe-ui.css
cproudlock 0b86255711 Webapp redesign phase 2: Overview and imaging floor
Imaging floor (/imaging, /imaging/tiles):
- One line per PC: status chip, serial with machine number, hostname
  and IP, stage text with a segmented stage track, one action plus a
  '...' menu. Everything else (description, error, timestamps, Intune
  ID + QR, Set category / ARTS links, LAPS QR, log tail) is in the row's
  expandable body.
- Order: failed, needs action (stage 7), in progress, done; log-only
  bays fold into one collapsed group. imaging_status.floor_state() and
  floor_order() compute this.
- Status filter buttons with live counts next to the text filter. SSE
  refresh, expanded-row memory and LAPS handling are unchanged.
- Stage text is never truncated; BIOS updates and erasing are bold.

Overview (/):
- Services popover, technician to-do line, the six most urgent floor
  rows (?limit=6 partial, refreshed every 15 s), and image cards with
  lint warnings, map errors and missing zips as separate chips. New,
  clone, delete and unattend stay on the cards' menus.

Copy buttons are now handled once in static/app.js, with a fallback for
plain-http pages where navigator.clipboard is unavailable.
2026-10-14 13:44:07 -04:00

280 lines
19 KiB
CSS

/* ==========================================================================
pxe-ui.css - PXE Manager redesign: tokens, shell and components.
Loads AFTER bootstrap.min.css and pxe-theme.css. The legacy tokens
(--bg-card, --border, --text-light, ...) are pointed at the new ones at
the bottom of the token block, so pages not yet redesigned blend in.
Air-gap safe: no external fonts or URLs.
========================================================================== */
/* --------------------------------------------------------------------------
Tokens. Light on :root, dark under [data-theme="dark"] (set by base.html
before first paint) and under prefers-color-scheme as a no-JS fallback.
-------------------------------------------------------------------------- */
:root {
--bg: #EEF1F4; --surface: #FFFFFF; --surface-2: #F6F7F9; --surface-3: #F1F3F6;
--text: #12161C; --text-2: #3A424E; --muted: #4A5361; --line: #E6EAEF; --line-2: #D5DBE2; --control: #7D8899;
--accent: #1F5FBF; --accent-hover: #174A94; --on-accent: #FFFFFF;
--fail: #B42318; --on-fail: #FFFFFF; --fail-soft: #FDECEA; --fail-text: #8F1C13;
--act: #F2A93B; --on-act: #151B24; --act-edge: #9A6200; --act-track: #C27C0E;
--run: #1F5FBF; --on-run: #FFFFFF;
--ok: #2E7D4F; --ok-soft: #E3F2E9; --ok-text: #14532D;
--warn-soft: #FFF0D6; --warn-text: #6B4000;
--neutral-soft: #E8EDF3; --neutral-text: #151B24;
--w11-soft: #E1ECFA; --w11-text: #174A94;
--w10-soft: #E8EBEF; --w10-text: #3A424E;
--off-soft: #FFFFFF; --off-text: #3A424E; --off-edge: #7D8899;
--edit-soft: #F2F7FF; --remove-soft: #F4F5F7;
--track-empty: #C9D1DC; --track-done: #7D8899;
--tip-bg: #151B24; --tip-text: #FFFFFF;
--del-bg: #FDECEA; --del-text: #8F1C13; --add-bg: #E3F2E9; --add-text: #14532D;
--backdrop: rgba(18, 22, 28, .55); --shadow: rgba(18, 22, 28, .08);
/* GE brand sidebar (theme-invariant colours, own tokens so dark can deepen it) */
--side-bg: #00003D; --side-hi: rgba(255, 255, 255, .12); --side-text: #E3E6F5; --side-head: #A4ABD6;
--side-mark: #4181FF; --badge-bg: #E8EDF3; --badge-text: #151B24;
--savebar-bg: #151B24; --savebar-edge: #151B24; --savebar-text: #FFFFFF; --savebar-sub: #C9D1DC;
--savebar-pri: #78A9F0; --on-savebar-pri: #0F141A;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
--font-mono: Consolas, 'SF Mono', ui-monospace, monospace;
}
[data-theme="dark"] {
--bg: #0F141A; --surface: #182029; --surface-2: #1E2731; --surface-3: #232D38;
--text: #E6EAF0; --text-2: #C2CAD4; --muted: #A3ADB9; --line: #26303B; --line-2: #34404D; --control: #7B8796;
--accent: #78A9F0; --accent-hover: #9BC0F5; --on-accent: #0F141A;
--fail: #F0705F; --on-fail: #1A0B09; --fail-soft: #3A1A17; --fail-text: #FFB4A9;
--act: #F2A93B; --on-act: #151B24; --act-edge: #F2A93B; --act-track: #F2A93B;
--run: #78A9F0; --on-run: #0F141A;
--ok: #4CAF78; --ok-soft: #173323; --ok-text: #9AD8B0;
--warn-soft: #3A2C10; --warn-text: #F6CD7E;
--neutral-soft: #2A3440; --neutral-text: #E6EAF0;
--w11-soft: #1C2F4A; --w11-text: #A9C8F5;
--w10-soft: #2A3038; --w10-text: #C7CED7;
--off-soft: #182029; --off-text: #C2CAD4; --off-edge: #7B8796;
--edit-soft: #17243A; --remove-soft: #1B222B;
--track-empty: #34404D; --track-done: #8592A3;
--tip-bg: #E6EAF0; --tip-text: #0F141A;
--del-bg: #3A1A17; --del-text: #FFB4A9; --add-bg: #173323; --add-text: #9AD8B0;
--backdrop: rgba(0, 0, 0, .6); --shadow: rgba(0, 0, 0, .35);
--side-bg: #00002A; --badge-bg: #2A3440; --badge-text: #E6EAF0;
--savebar-bg: #2B3542; --savebar-edge: #4A5666;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--bg: #0F141A; --surface: #182029; --surface-2: #1E2731; --surface-3: #232D38;
--text: #E6EAF0; --text-2: #C2CAD4; --muted: #A3ADB9; --line: #26303B; --line-2: #34404D; --control: #7B8796;
--accent: #78A9F0; --accent-hover: #9BC0F5; --on-accent: #0F141A;
}
}
/* Legacy tokens (pxe-theme.css and Bootstrap pages) follow the new ones. */
:root, [data-theme="dark"] {
--bg-card: var(--surface); --bg-card-solid: var(--surface); --border: var(--line-2);
--text-light: var(--muted); --link: var(--accent); --primary: var(--accent); --primary-dark: var(--accent-hover);
--sidebar-bg: var(--side-bg); --sidebar-width: 236px;
--bs-body-bg: var(--bg); --bs-body-color: var(--text); --bs-border-color: var(--line-2);
}
/* --------------------------------------------------------------------------
Base
-------------------------------------------------------------------------- */
body { font-family: var(--font); font-size: 14px; letter-spacing: 0; line-height: 1.5; background: var(--bg); color: var(--text); }
a { color: var(--accent); }
a:hover { color: var(--accent-hover); }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible, [tabindex]:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.mono { font-family: var(--font-mono); }
.muted { color: var(--muted); }
.sub { font-size: 12px; color: var(--muted); }
.vh { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip-link { position: absolute; left: 8px; top: -60px; background: var(--surface); color: var(--text); padding: 10px 14px; border-radius: 6px; z-index: 2000; }
.skip-link:focus { top: 8px; }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 4px; flex: none; }
/* --------------------------------------------------------------------------
Shell: GE sidebar + main
-------------------------------------------------------------------------- */
.ui-shell { display: flex; min-height: 100vh; }
.ui-side {
position: sticky; top: 0; height: 100vh; overflow-y: auto;
flex: 0 0 236px; max-width: 236px; box-sizing: border-box;
background: var(--side-bg); color: var(--side-text);
padding: 16px 12px; display: flex; flex-direction: column; gap: 18px; z-index: 1000;
}
.ui-brand { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 2px 8px; }
.ui-brand .logo { width: 150px; max-width: 100%; filter: brightness(0) invert(1); display: block; }
.ui-brand .app { font-weight: 600; font-size: 14px; color: #FFFFFF; margin-top: 6px; }
.ui-brand .host { font-family: var(--font-mono); font-size: 11px; color: var(--side-head); }
.ui-menu-btn { display: none; width: 44px; height: 44px; align-items: center; justify-content: center; border: 1px solid rgba(255,255,255,.35); border-radius: 8px; background: transparent; color: #FFFFFF; }
.ui-groups { display: flex; flex-direction: column; gap: 16px; }
.ui-group { display: flex; flex-direction: column; gap: 2px; }
.ui-navhead { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--side-head); padding: 0 12px 4px; }
.ui-navlink {
display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 0 12px;
border-radius: 7px; color: var(--side-text); text-decoration: none; font-size: 14px;
}
.ui-navlink:hover { background: var(--side-hi); color: #FFFFFF; }
.ui-navlink[aria-current] { background: var(--side-hi); color: #FFFFFF; font-weight: 600; box-shadow: inset 3px 0 0 var(--side-mark); }
.ui-navlink .count { font-family: var(--font-mono); font-size: 12px; background: var(--badge-bg); color: var(--badge-text); border-radius: 10px; padding: 1px 8px; }
.ui-side a:focus-visible, .ui-side button:focus-visible { outline-color: #FFFFFF; }
.ui-theme {
display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 12px; margin-top: auto;
border: 1px solid rgba(255,255,255,.35); border-radius: 7px; background: transparent; color: var(--side-text); font: inherit; font-size: 13px;
}
.ui-main { flex: 1 1 560px; min-width: 0; padding: 28px clamp(16px, 4vw, 48px) 56px; box-sizing: border-box; }
/* Legacy pages set .main-content; inside the new shell it needs no offset. */
.ui-main.main-content { margin-left: 0; }
@media (max-width: 991px) {
.ui-shell { flex-direction: column; }
.ui-side { position: static; height: auto; flex: none; max-width: none; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; }
.ui-side .ui-groups, .ui-side .ui-theme { display: none; }
.ui-side.open .ui-groups { display: flex; flex-basis: 100%; }
.ui-side.open .ui-theme { display: flex; flex-basis: 100%; }
.ui-menu-btn { display: inline-flex; }
}
/* Page header ------------------------------------------------------------ */
.ui-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 14px; margin-bottom: 22px; }
.ui-head .eyebrow-t { font-size: 13px; color: var(--muted); }
.ui-head h1 { margin: 2px 0 0; font-size: 28px; font-weight: 600; letter-spacing: 0; }
.ui-head .actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ui-stack { display: flex; flex-direction: column; gap: 22px; }
h2.sec { margin: 0; font-size: 18px; font-weight: 600; }
/* Global to-do strip (one line) ------------------------------------------- */
.ui-todo { display: flex; align-items: center; gap: 12px; padding: 10px 14px; margin-bottom: 18px; border-radius: 10px; background: var(--surface); box-shadow: 0 1px 2px var(--shadow); color: var(--text); text-decoration: none; }
.ui-todo:hover { color: var(--text); box-shadow: 0 2px 8px var(--shadow); }
/* --------------------------------------------------------------------------
Components
-------------------------------------------------------------------------- */
.panel { background: var(--surface); border-radius: 14px; box-shadow: 0 1px 2px var(--shadow); }
.chip { display: inline-block; white-space: nowrap; font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: 5px; line-height: 1.4; letter-spacing: 0; }
.c-fail { background: var(--fail); color: var(--on-fail); }
.c-err { background: var(--fail-soft); color: var(--fail-text); box-shadow: inset 0 0 0 1px var(--fail); }
.c-act { background: var(--act); color: var(--on-act); box-shadow: inset 0 0 0 1px var(--act-edge); }
.c-run { background: var(--run); color: var(--on-run); }
.c-ok { background: var(--ok-soft); color: var(--ok-text); }
.c-warn { background: var(--warn-soft); color: var(--warn-text); }
.c-neutral { background: var(--neutral-soft); color: var(--neutral-text); }
.c-w11 { background: var(--w11-soft); color: var(--w11-text); }
.c-w10 { background: var(--w10-soft); color: var(--w10-text); }
.c-off { background: var(--off-soft); color: var(--off-text); box-shadow: inset 0 0 0 1px var(--off-edge); }
.c-edit { background: var(--accent); color: var(--on-accent); }
.c-remove { background: var(--text-2); color: var(--surface); }
/* Truncation: full text stays in the DOM (screen readers read it); hover
shows it. Status, errors, safety warnings and reasons are never hover-only. */
.tip { position: relative; min-width: 0; display: block; }
.tip.inl { display: inline-block; }
.trunc { display: block; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tip[data-tip]:hover::after {
content: attr(data-tip); position: absolute; left: 0; top: calc(100% + 6px); z-index: 20;
width: max-content; max-width: min(440px, 80vw); white-space: normal;
background: var(--tip-bg); color: var(--tip-text); font: 400 12px/1.45 var(--font);
padding: 7px 10px; border-radius: 6px; box-shadow: 0 4px 14px rgba(0,0,0,.25); text-align: left;
}
.tip.right[data-tip]:hover::after { left: auto; right: 0; }
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }
.chev { color: var(--muted); font-size: 11px; transition: transform .15s; flex: none; }
details[open] > summary .chev { transform: rotate(180deg); }
.disc { display: flex; align-items: center; gap: 8px; min-height: 36px; font-size: 13px; color: var(--accent); font-weight: 600; }
.ui-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 36px; min-width: 36px; box-sizing: border-box;
padding: 0 12px; border: 1px solid var(--control); border-radius: 7px; background: var(--surface); color: var(--text);
text-decoration: none; font: inherit; font-size: 13px; cursor: pointer; white-space: nowrap; letter-spacing: 0;
}
.ui-btn:hover { color: var(--text); background: var(--surface-3); }
.ui-btn.pri { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.ui-btn.pri:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.ui-btn.danger { border-color: var(--fail); color: var(--fail-text); }
.ui-btn.attest { border-color: var(--text-2); font-weight: 600; }
.ui-btn.ghost { border-color: transparent; background: transparent; }
.ui-btn.ghost:hover { background: var(--surface-3); }
.ui-btn[disabled] { opacity: .55; cursor: not-allowed; }
.ui-btn.sm { min-height: 28px; padding: 0 8px; font-size: 12px; }
.fbtn { white-space: nowrap; padding: 0 12px; min-height: 30px; border-radius: 15px; border: 1px solid var(--line-2); background: var(--surface-3); color: var(--text-2); font: inherit; font-size: 13px; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 6px; }
.fbtn[aria-pressed="true"], .fbtn[aria-current] { background: var(--text); color: var(--surface); border-color: var(--text); }
details.more { display: inline-block; position: relative; vertical-align: middle; }
details.more .menu {
position: absolute; right: 0; top: calc(100% + 4px); z-index: 30; box-sizing: border-box;
min-width: min(180px, calc(100vw - 32px)); max-width: calc(100vw - 32px);
background: var(--surface); border: 1px solid var(--line-2); border-radius: 8px; box-shadow: 0 6px 18px var(--shadow);
padding: 4px; display: flex; flex-direction: column; text-align: left;
}
details.more .menu a, details.more .menu button, details.more .menu form > button {
display: block; width: 100%; padding: 9px 12px; border: 0; background: none; font: inherit; font-size: 13px; color: var(--text);
text-decoration: none; text-align: left; border-radius: 5px; cursor: pointer; white-space: nowrap;
}
details.more .menu a:hover, details.more .menu button:hover { background: var(--surface-3); }
details.more .menu .sep { height: 1px; background: var(--line); margin: 4px 0; }
details.more .menu .del { color: var(--fail-text); }
details.more .menu [disabled] { color: var(--muted); cursor: not-allowed; }
details.more .menu form { margin: 0; }
@media (max-width: 600px) { details.more.lft .menu { left: 0; right: auto; } }
.field { height: 36px; box-sizing: border-box; border: 1px solid var(--control); border-radius: 8px; padding: 0 12px; font: inherit; font-size: 14px; background: var(--surface); color: var(--text); }
.notice { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: 8px; background: var(--warn-soft); color: var(--warn-text); font-size: 13px; line-height: 1.5; }
.notice.info { background: var(--neutral-soft); color: var(--neutral-text); }
.notice.bad { background: var(--fail-soft); color: var(--fail-text); }
.errbox { padding: 10px 12px; border-radius: 8px; background: var(--fail-soft); color: var(--fail-text); font-size: 13px; line-height: 1.5; }
.errbox pre { margin: 8px 0 0; font-size: 12px; white-space: pre-wrap; color: var(--text-2); }
.track { display: flex; gap: 3px; margin-top: 8px; }
.track i { flex: 1; height: 6px; border-radius: 2px; background: var(--track-empty); }
.track i.d { background: var(--track-done); }
.track i.fail { background: var(--fail); } .track i.act { background: var(--act-track); }
.track i.run { background: var(--run); } .track i.ok { background: var(--ok); }
/* Expandable list rows: one line, details below, actions outside <summary>. */
.ui-list { container-type: inline-size; }
.ui-row { position: relative; border-top: 1px solid var(--line); }
.ui-row > .act { position: absolute; right: 16px; top: 7px; display: flex; gap: 6px; align-items: center; }
.ui-rowhead { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.hidden-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 10px 24px; border-top: 1px solid var(--line); font-size: 13px; color: var(--text-2); background: var(--surface-2); }
.panel > .hidden-bar:last-child { border-radius: 0 0 14px 14px; }
/* Flash messages restyled to the token palette (Bootstrap markup kept). */
.ui-main > .alert { border-radius: 10px; border: 0; box-shadow: 0 1px 2px var(--shadow); }
/* --------------------------------------------------------------------------
Imaging floor rows (_imaging_tiles.html), used by /imaging and Overview.
-------------------------------------------------------------------------- */
.floor { container-type: inline-size; }
.frow, .fhead {
display: grid; grid-template-columns: 118px minmax(160px, 270px) minmax(0, 1fr);
grid-template-areas: "st pc stage"; align-items: center; gap: 0 20px; padding: 14px 230px 14px 24px;
}
.fhead { padding-top: 6px; padding-bottom: 6px; font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.g-st { grid-area: st; } .g-pc { grid-area: pc; min-width: 0; display: flex; flex-direction: column; } .g-stage { grid-area: stage; min-width: 0; }
.floor-row .g-act { position: absolute; right: 20px; top: 16px; width: 190px; justify-content: flex-end; }
.floor-row .serial { font-size: 15px; }
.stageline { display: flex; align-items: center; gap: 12px; font-size: 13px; min-height: 24px; }
.stageline .txt { flex: 1 1 auto; min-width: 0; white-space: normal; }
.stageline .warnline { font-weight: 600; }
.stageline .kind { flex: none; font-size: 12px; color: var(--muted); max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stageline .step { flex: none; color: var(--muted); font-size: 12px; }
.fbody { display: flex; gap: 24px; align-items: flex-start; padding: 0 24px 16px 162px; font-size: 13px; }
.fbody-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.fbody-qr { flex: none; line-height: 0; background: #FFFFFF; padding: 6px; border-radius: 6px; }
.fmeta { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.fline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.imaging-log-pre { max-height: 14rem; overflow-y: auto; margin: 6px 0 0; padding: 8px 10px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px; font-size: 12px; white-space: pre-wrap; }
.floor-logs { border-top: 1px solid var(--line); }
.floor-logs > summary { padding: 0 24px; min-height: 44px; color: var(--text-2); font-weight: 400; }
.floor-empty { padding: 28px 24px; text-align: center; border-top: 1px solid var(--line); }
@container (max-width: 820px) {
.fhead { display: none; }
.frow { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "st pc" "stage stage"; gap: 10px 14px; padding: 14px 18px; }
.floor-row .g-act { position: static; width: auto; display: flex; justify-content: flex-end; padding: 0 18px 12px; }
.fbody { padding: 0 18px 14px; flex-direction: column; }
}
/* Overview image cards */
.img-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); gap: 16px; }
.img-card { padding: 16px 18px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.img-top { display: flex; align-items: center; gap: 8px; }
.img-name { font-weight: 600; font-size: 16px; color: var(--text); text-decoration: none; margin-right: auto; }
.img-name:hover { color: var(--accent); }