From 18db07747578ac500ad96aceac95d98b869071ac Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 23 Jul 2026 10:05:09 -0400 Subject: [PATCH] webapp: reskin to shopdb-flask design system Adopt the shopdb-flask visual language across the PXE webapp (presentation only, Flask/Jinja logic unchanged): - New static/pxe-theme.css: GE Aerospace palette (atmosphere-blue sidebar, sky-blue primary, avionics-green), Inter font stack, light/dark theming via data-theme + localStorage (key pxe-theme) with system-pref fallback, and card/button/table/form/badge/alert component styles layered over Bootstrap. - base.html: shopdb-style sidebar (logo + title, nav sections, footer light/dark toggle) + theme boot script. - All 13 content templates restyled to the new page-header + card/table/badge vocabulary; unattend_editor grouped per unattend-UX research. - Fixed a pre-existing CRITICAL bug found during review: nested
s in image_config.html made Adopt submit the delete form and Delete-selected post every orphan filename regardless of checkboxes; split into standalone forms wired via the form= attribute. Built by a Fable-orchestrated Opus workflow (17 agents). All 14 templates parse clean under Jinja2. --- webapp/static/pxe-theme.css | 928 +++++++++++++++++++++ webapp/templates/_imaging_tiles.html | 73 +- webapp/templates/audit.html | 121 ++- webapp/templates/backups.html | 272 ++++--- webapp/templates/base.html | 406 +++++----- webapp/templates/dashboard.html | 519 ++++++------ webapp/templates/enrollment.html | 273 +++---- webapp/templates/image_config.html | 950 +++++++++++----------- webapp/templates/imaging.html | 47 +- webapp/templates/imaging_detail.html | 160 ++-- webapp/templates/import.html | 372 +++++---- webapp/templates/report_view.html | 165 ++-- webapp/templates/reports.html | 227 +++--- webapp/templates/startnet_editor.html | 280 ++++--- webapp/templates/unattend_editor.html | 1065 +++++++++++++------------ 15 files changed, 3446 insertions(+), 2412 deletions(-) create mode 100644 webapp/static/pxe-theme.css diff --git a/webapp/static/pxe-theme.css b/webapp/static/pxe-theme.css new file mode 100644 index 0000000..279a10b --- /dev/null +++ b/webapp/static/pxe-theme.css @@ -0,0 +1,928 @@ +/* ========================================================================== + pxe-theme.css - GE Aerospace shopdb-flask design port for the PXE webapp + Load AFTER bootstrap.min.css so these token+component rules win. + Hand-written, dependency-free, air-gap safe (no CDN, no external fonts). + ========================================================================== */ + +/* -------------------------------------------------------------------------- + Tokens - LIGHT is the default on :root. Dark overrides live in the + [data-theme="dark"] block. Brand tokens are theme-invariant. + -------------------------------------------------------------------------- */ +:root { + /* GE brand (theme-invariant) */ + --ge-atmosphere-blue: #00003d; + --ge-sky-blue: #4181ff; + --ge-avionics-green: #0ad64f; + --ge-tungsten: #eaeaea; + + /* accents (same in both themes unless noted) */ + --primary: #4181ff; + --primary-dark: #2d6ce0; + --secondary: #6c757d; + --secondary-dark: #82503f; + --success: #0ad64f; + --success-dark: #019e4c; + --warning: #ff9500; + --warning-dark: #e67c02; + --danger: #dc3545; + --danger-dark: #e62c51; + --info: #4181ff; + --info-dark: #039ce0; + --purple: #9c27b0; + --link: #4181ff; + + /* surfaces + text (LIGHT) */ + --bg: #f5f5f5; + --bg-card: #ffffff; + --bg-card-solid: #ffffff; + --bg-popover: #111111; + --text: #1a1a2e; + --text-light: #6c757d; + --border: #dee2e6; + + /* sidebar (same in both themes) */ + --sidebar-bg: #00003d; + --sidebar-text: #ffffff; + --sidebar-width: 250px; + + /* custom caret for selects (light) */ + --select-caret: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); +} + +[data-theme="dark"] { + --danger: #f5365c; + --bg: #0a0a1a; + --bg-card: rgba(0, 0, 61, 0.4); + --bg-card-solid: #0d0d2b; + --text: rgba(255, 255, 255, 0.9); + --text-light: rgba(255, 255, 255, 0.6); + --border: rgba(65, 129, 255, 0.2); + --select-caret: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); +} + +/* Belt-and-suspenders: honour OS dark before JS stamps the attribute, + to avoid an unstyled flash on dark-OS machines. */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --danger: #f5365c; + --bg: #0a0a1a; + --bg-card: rgba(0, 0, 61, 0.4); + --bg-card-solid: #0d0d2b; + --text: rgba(255, 255, 255, 0.9); + --text-light: rgba(255, 255, 255, 0.6); + --border: rgba(65, 129, 255, 0.2); + --select-caret: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + } +} + +/* -------------------------------------------------------------------------- + Base / reset + -------------------------------------------------------------------------- */ +* { box-sizing: border-box; } + +body { + font-family: 'Inter Variable', 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif; + font-size: 15px; + line-height: 1.6; + letter-spacing: 0.5px; + background-color: var(--bg); + color: var(--text); + min-height: 100vh; +} + +a { color: var(--link); } + +.mono, +.mac, .ip, .id-cell { + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 13px; +} + +/* Bootstrap .text-light is near-white always - remap to muted token so it + reads in BOTH themes (templates use it as "muted text") */ +.text-light { color: var(--text-light) !important; } + +/* Signature type treatment helper */ +.eyebrow { + text-transform: uppercase; + letter-spacing: 1px; + font-size: 11px; + font-weight: 600; + color: var(--text-light); +} + +/* -------------------------------------------------------------------------- + Layout: sidebar rail + main content + -------------------------------------------------------------------------- */ +.sidebar { + width: var(--sidebar-width); + height: 100vh; + background: var(--sidebar-bg); + color: var(--sidebar-text); + position: fixed; + top: 0; + left: 0; + overflow-y: auto; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + z-index: 1000; + padding: 0; +} + +.main-content { + margin-left: var(--sidebar-width); + padding: 20px 10px 70px 10px; +} + +/* Sidebar header ---------------------------------------------------------- */ +.sidebar-header { + padding: 1rem; + text-align: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); +} +.sidebar-logo { + width: 180px; + max-width: 100%; + filter: brightness(0) invert(1); +} +.sidebar-header h1 { + font-size: 14px; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 1px; + margin: 0.5rem 0 0; + color: #fff; +} + +/* Sidebar search ---------------------------------------------------------- */ +.sidebar-search { + padding: 10px 15px; +} +.sidebar-search input { + width: 100%; + height: 34px; + border: 0; + border-radius: 0.25rem; + background: rgba(255, 255, 255, 0.2); + color: #fff; + padding: 0 0.75rem; + font-size: 13px; +} +.sidebar-search input::placeholder { + color: rgba(255, 255, 255, 0.5); + font-size: 13px; +} +.sidebar-search input:focus { + outline: none; + background: rgba(0, 0, 0, 0.2); + box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.45); +} + +/* Sidebar nav ------------------------------------------------------------- */ +.sidebar-nav { list-style: none; margin: 0; padding: 0; } + +.sidebar-nav .nav-link, +.sidebar .nav-link { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.7rem 1.25rem; + color: rgba(255, 255, 255, 0.85); + font-size: 14px; + text-decoration: none; + transition: all 0.2s; + border-radius: 0; +} +.sidebar-nav .nav-link i, +.sidebar .nav-link i { + font-size: 16px; + margin-right: 0.5rem; +} +.sidebar-nav .nav-link:hover, +.sidebar-nav .nav-link.active, +.sidebar .nav-link:hover, +.sidebar .nav-link.active { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} +.sidebar-nav .nav-link.active, +.sidebar .nav-link.active { + border-left: 3px solid var(--primary); +} + +/* sub-link (Configuration under an image) */ +.sidebar-nav .nav-sublink { + padding-left: 2.5rem; + font-size: 0.82rem; +} + +.nav-section, +.sidebar-heading { + padding: 0.75rem 1.25rem 0.5rem; + color: rgba(255, 255, 255, 0.5); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 0.5rem; +} + +.sidebar-nav .nav-link.external::after { + content: '\2197'; + margin-left: auto; + opacity: 0.7; +} + +/* Sidebar footer ---------------------------------------------------------- */ +.sidebar-footer { + margin-top: auto; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} +.theme-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.75rem 1.25rem; + background: transparent; + border: none; + color: rgba(255, 255, 255, 0.7); + font-size: 13px; + cursor: pointer; + transition: all 0.2s; +} +.theme-toggle:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} +.user-menu { + padding: 1rem 1.25rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} +.user-menu .avatar { + width: 34px; + height: 34px; + border-radius: 50%; + background: var(--primary); + color: #fff; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 600; +} +.user-menu .username { + font-size: 14px; + color: rgba(255, 255, 255, 0.85); +} +.user-menu .user-sub { + font-size: 0.72rem; + color: rgba(255, 255, 255, 0.5); +} +.user-menu .icon-btn { + flex: 1; + padding: 0.4rem 0.5rem; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + color: rgba(255, 255, 255, 0.8); + font-size: 12px; + cursor: pointer; + text-align: center; + text-decoration: none; +} +.user-menu .icon-btn:hover { + background: rgba(255, 255, 255, 0.16); + color: #fff; +} + +/* -------------------------------------------------------------------------- + Page header + -------------------------------------------------------------------------- */ +.page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} +.page-header h1, +.page-header h2 { + font-size: 20px; + font-weight: 500; + line-height: 20px; + margin: 0; +} +.header-actions { + display: flex; + gap: 0.5rem; +} + +/* -------------------------------------------------------------------------- + Card (also overrides Bootstrap .card) + -------------------------------------------------------------------------- */ +.pxe-card, +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 0.25rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + padding: 0; + margin-bottom: 25px; + color: var(--text); +} +/* pxe-card with padded body baked in */ +.pxe-card { padding: 1.25rem; } + +[data-theme="light"] .pxe-card, +[data-theme="light"] .card { border: 1px solid var(--border); } +[data-theme="dark"] .pxe-card, +[data-theme="dark"] .card { border: none; } + +.pxe-card-header, +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1.25rem; + border-bottom: 1px solid var(--border); + font-weight: 600; + font-size: 14px; + background: transparent; +} +.pxe-card-header h3, +.card-header h3 { font-size: 16px; font-weight: 600; margin: 0; } + +.pxe-card-body, +.card-body { padding: 1.25rem; } + +.pxe-card-footer, +.card-footer { + padding: 0.75rem 1.25rem; + border-top: 1px solid var(--border); + background: transparent; +} +.card-title, +.pxe-card-title { font-size: 16px; font-weight: 600; margin-bottom: 0.75rem; } + +/* Section-card variant */ +.section-card { + background: var(--bg-card); + border-radius: 0.25rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + padding: 1.25rem; + margin-bottom: 25px; +} +[data-theme="light"] .section-card { border: 1px solid var(--border); } +[data-theme="dark"] .section-card { border: none; } +.section-title { + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + border-bottom: 1px solid var(--border); + padding-bottom: 0.75rem; + margin-bottom: 1rem; +} + +/* -------------------------------------------------------------------------- + Buttons (overrides Bootstrap .btn + variants) + -------------------------------------------------------------------------- */ +.pxe-btn, +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 9px 19px; + border: none; + border-radius: 0.25rem; + font-size: 11px; + font-weight: 500; + letter-spacing: 1px; + text-transform: uppercase; + cursor: pointer; + transition: all 0.2s; + line-height: 1; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + text-decoration: none; +} +.pxe-btn:focus, +.btn:focus { box-shadow: none; outline: none; } + +.pxe-btn-primary, .btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); } +.pxe-btn-primary:hover, .btn-primary:hover { background: var(--primary-dark); color: #fff; } +.pxe-btn-secondary, .btn-secondary { background: var(--secondary); color: #fff; border-color: var(--secondary); } +.pxe-btn-secondary:hover, .btn-secondary:hover { background: var(--secondary-dark); color: #fff; } +.pxe-btn-success, .btn-success { background: var(--success); color: #fff; border-color: var(--success); } +.pxe-btn-success:hover, .btn-success:hover { background: var(--success-dark); color: #fff; } +.pxe-btn-info, .btn-info { background: var(--info); color: #fff; border-color: var(--info); } +.pxe-btn-info:hover, .btn-info:hover { background: var(--info-dark); color: #fff; } +.pxe-btn-warning, .btn-warning { background: var(--warning); color: #fff; border-color: var(--warning); } +.pxe-btn-warning:hover, .btn-warning:hover { background: var(--warning-dark); color: #fff; } +.pxe-btn-danger, .btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); } +.pxe-btn-danger:hover, .btn-danger:hover { background: var(--danger-dark); color: #fff; } + +/* Ghost / outline buttons keep the brand shape but read as secondary chrome */ +.pxe-btn-ghost, +.btn-outline-primary, +.btn-outline-secondary, +.btn-outline-danger, +.btn-outline-success, +.btn-outline-info { + background: transparent; + box-shadow: none; + border: 1px solid var(--border); + color: var(--text); +} +.pxe-btn-ghost:hover { background: var(--bg); color: var(--text); } +.btn-outline-primary { color: var(--primary); border-color: var(--primary); } +.btn-outline-primary:hover { background: var(--primary); color: #fff; } +.btn-outline-danger { color: var(--danger); border-color: var(--danger); } +.btn-outline-danger:hover { background: var(--danger); color: #fff; } +.btn-outline-secondary:hover { background: var(--secondary); color: #fff; } +.btn-outline-success { color: var(--success); border-color: var(--success); } +.btn-outline-success:hover { background: var(--success); color: #fff; } +.btn-outline-info { color: var(--info); border-color: var(--info); } +.btn-outline-info:hover { background: var(--info); color: #fff; } + +.pxe-btn-link, +.btn-link { + background: transparent; + border: none; + box-shadow: none; + color: var(--info); + text-transform: none; + letter-spacing: normal; +} + +/* Sizes */ +.pxe-btn-sm, .btn-sm { font-size: 10px; padding: 6px 15px; } +.pxe-btn-lg, .btn-lg { font-size: 14px; padding: 12px 38px; } + +/* small icon-only row action buttons in tables keep a compact footprint */ +.btn-row-action { padding: 6px 10px; font-size: 10px; } + +/* -------------------------------------------------------------------------- + Tables (overrides Bootstrap .table) + -------------------------------------------------------------------------- */ +.table-container { overflow-x: auto; white-space: nowrap; } + +.data-table, +table.table, +.table { + width: 100%; + border-collapse: collapse; + color: var(--text); + font-variant-numeric: tabular-nums; + background: transparent; +} +.data-table th, +.table th { + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1px; + text-align: left; + padding: 0.75rem; + border-bottom: 1px solid var(--border); + border-top: none; + color: var(--text); +} +.data-table td, +.table td { + padding: 0.75rem; + font-size: 13px; + vertical-align: middle; + white-space: nowrap; + border-top: 1px solid var(--border); + color: var(--text); +} +.data-table tbody tr:hover, +.table tbody tr:hover { background: rgba(65, 129, 255, 0.1); } + +/* sortable header */ +.data-table th.sortable, +.table th.sortable { cursor: pointer; } +.data-table th.sortable:hover, +.table th.sortable:hover { color: var(--primary); } +.sort-arrow { font-size: 10px; } + +/* truncating cell */ +.cell-truncate td { + max-width: 32rem; + overflow: hidden; + text-overflow: ellipsis; +} + +/* action column: keep it a real table cell, space the buttons */ +td.actions { display: table-cell; } +td.actions .btn + .btn, +.actions .pxe-btn + .pxe-btn { margin-left: 0.25rem; } + +.clickable-row { cursor: pointer; } +.clickable-row:hover td { background: var(--bg); } + +/* drag handle used by app.js command tables */ +.drag-handle { cursor: grab; color: var(--text-light); } +.drag-handle:active { cursor: grabbing; } + +/* -------------------------------------------------------------------------- + Forms (overrides Bootstrap .form-control / .form-select) + -------------------------------------------------------------------------- */ +.form-field, +.form-group { margin-bottom: 1rem; } + +.form-field label, +.form-group label, +.form-label { + display: block; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; + color: var(--text); +} + +.form-control { + width: 100%; + padding: 0.625rem 0.875rem; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 0.9375rem; + background: var(--bg-card); + color: var(--text); + transition: border-color 0.15s, box-shadow 0.15s; +} +.form-control:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15); +} +.form-control::placeholder { color: var(--text-light); opacity: 0.7; } + +.form-control-sm { padding: 0.375rem 0.6rem; font-size: 0.85rem; } + +[data-theme="dark"] .form-control { + background-color: var(--bg); +} +[data-theme="dark"] .form-control:focus { + box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); +} + +/* select */ +select.form-control, +.form-select { + width: 100%; + padding: 0.625rem 0.875rem; + padding-right: 2.25rem; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 0.9375rem; + background: var(--bg-card); + color: var(--text); + appearance: none; + -webkit-appearance: none; + cursor: pointer; + background-image: var(--select-caret); + background-repeat: no-repeat; + background-position: right 0.75rem center; +} +select.form-control:focus, +.form-select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15); +} +[data-theme="dark"] select.form-control, +[data-theme="dark"] .form-select { + background-color: var(--bg); +} +select.form-control option, +.form-select option { background: var(--bg-card); } +[data-theme="dark"] select.form-control option, +[data-theme="dark"] .form-select option { background: var(--bg-card-solid); } + +/* textarea */ +textarea.form-control { + min-height: 100px; + resize: vertical; + line-height: 1.5; +} + +/* checkboxes / radios */ +.form-check-input, +input[type="checkbox"], +input[type="radio"] { + width: 1.125rem; + height: 1.125rem; + accent-color: var(--primary); +} + +/* -------------------------------------------------------------------------- + Badges (overrides Bootstrap .badge + bg-* utilities) + -------------------------------------------------------------------------- */ +.badge { + display: inline-block; + padding: 0.25em 0.5em; + border-radius: 0.25rem; + font-size: 11px; + font-weight: 500; + line-height: 1; + text-align: center; + white-space: nowrap; + color: #fff; +} +.badge-success, .badge.bg-success { background: var(--success); color: #fff; } +.badge-warning, .badge.bg-warning { background: var(--warning); color: #fff; } +.badge-danger, .badge.bg-danger { background: var(--danger); color: #fff; } +.badge-info, .badge.bg-info { background: var(--info); color: #fff; } +.badge-primary, .badge.bg-primary { background: var(--primary); color: #fff; } +.badge-secondary,.badge.bg-secondary { background: var(--secondary); color: #fff; } +.badge-printer, .badge.bg-purple { background: var(--purple); color: #fff; } +.badge-lg { padding: 0.35rem 0.75rem; font-size: 12px; } + +/* -------------------------------------------------------------------------- + Status pill / dot / toggle + -------------------------------------------------------------------------- */ +.status-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--secondary); +} +.status-dot.inactive { background: var(--secondary); } +.status-dot.warning, +.status-dot.unknown { background: var(--warning); } +.status-dot.pending { background: var(--primary); } +.status-dot.success, +.status-dot.active { background: var(--success); } + +.status-indicator { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background: var(--bg); + border-radius: 4px; + color: var(--text-light); +} + +.toggle-btn { + position: relative; + width: 50px; + height: 26px; + border-radius: 13px; + background: var(--secondary); + border: none; + cursor: pointer; + transition: background 0.2s; +} +.toggle-btn.active { background: var(--success); } +.toggle-slider { + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + border-radius: 50%; + background: #fff; + transition: transform 0.2s; +} +.toggle-btn.active .toggle-slider { transform: translateX(24px); } + +/* -------------------------------------------------------------------------- + Alerts (overrides Bootstrap .alert) + notification banners + -------------------------------------------------------------------------- */ +.alert { + padding: 0.75rem 1rem; + border-radius: 0.25rem; + font-size: 14px; + border: 1px solid transparent; + margin-bottom: 1rem; +} +.error-message, +.alert-danger, +.alert-error { + background: rgba(245, 54, 92, 0.2); + color: var(--danger); + border: 1px solid rgba(245, 54, 92, 0.3); +} +.settings-success, +.alert-success { + background: var(--success); + color: #fff; + border-color: var(--success); +} +.alert-warning { + background: rgba(255, 136, 0, 0.15); + color: var(--warning-dark); + border: 1px solid rgba(255, 136, 0, 0.3); +} +.alert-info, +.alert-primary { + background: rgba(65, 129, 255, 0.12); + color: var(--primary); + border: 1px solid rgba(65, 129, 255, 0.3); +} +/* Flask flashes commonly use "message" category */ +.alert-message { + background: rgba(65, 129, 255, 0.12); + color: var(--primary); + border: 1px solid rgba(65, 129, 255, 0.3); +} + +.notification-item { + flex: 1; + min-width: 250px; + padding: 0.625rem 0.875rem; + border-radius: 6px; + border-left: 4px solid var(--border); + background: var(--bg-card); +} +.notification-item.type-incident { border-left-color: var(--danger); background: rgba(245, 54, 92, 0.06); } +.notification-item.type-change { border-left-color: var(--warning); background: rgba(255, 136, 0, 0.06); } +.notification-item.type-awareness { border-left-color: var(--success); background: rgba(4, 185, 98, 0.06); } +[data-theme="dark"] .notification-item.type-incident { background: rgba(245, 54, 92, 0.1); } +[data-theme="dark"] .notification-item.type-change { background: rgba(255, 136, 0, 0.1); } +[data-theme="dark"] .notification-item.type-awareness { background: rgba(4, 185, 98, 0.1); } + +/* -------------------------------------------------------------------------- + Toasts + -------------------------------------------------------------------------- */ +.toast-stack { + position: fixed; + bottom: 1.25rem; + right: 1.25rem; + z-index: 3000; + display: flex; + flex-direction: column; + gap: 0.6rem; + max-width: min(420px, 100vw - 2.5rem); +} +.toast { + display: flex; + gap: 0.75rem; + padding: 0.75rem 0.9rem; + border-radius: 8px; + background: var(--bg-card); + color: var(--text); + border-left: 4px solid var(--secondary); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); + font-size: 0.9rem; + transition: opacity 0.2s, transform 0.2s; +} +.toast-success { border-left-color: var(--success); } +.toast-error { border-left-color: var(--danger); } +.toast-info { border-left-color: var(--primary); } +.toast .toast-close { + background: transparent; + border: none; + color: var(--text-light); + font-size: 1.1rem; + cursor: pointer; + margin-left: auto; +} + +/* -------------------------------------------------------------------------- + Modal (overrides Bootstrap-manual markup; use .modal-overlay wrapper) + -------------------------------------------------------------------------- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1050; +} +.modal-overlay .modal, +.pxe-modal { + background: var(--bg-card-solid); + border-radius: 0.25rem; + max-width: 500px; + width: 100%; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 16px 38px -12px rgba(0, 0, 0, 0.56), + 0 4px 25px rgba(0, 0, 0, 0.12), + 0 8px 10px -5px rgba(0, 0, 0, 0.2); +} +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border); +} +.modal-header h3 { font-size: 16px; font-weight: 600; margin: 0; } +.modal-body { padding: 1.25rem; } +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + padding: 1rem 1.25rem; + border-top: 1px solid var(--border); +} + +/* -------------------------------------------------------------------------- + Pagination + -------------------------------------------------------------------------- */ +.pagination-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 1rem; + padding-top: 0.75rem; + border-top: 1px solid var(--border); +} +.pagination { display: flex; gap: 0.25rem; list-style: none; margin: 0; padding: 0; } +.pagination button, +.pagination .page-btn { + padding: 0.5rem 0.75rem; + border: 1px solid var(--border); + background: transparent; + border-radius: 0.25rem; + font-size: 13px; + color: var(--text); + cursor: pointer; +} +.pagination button:hover:not(:disabled), +.pagination .page-btn:hover:not(.disabled) { + background: var(--bg); + border-color: var(--primary); +} +.pagination .active { background: var(--primary); color: #fff; border-color: var(--primary); } +.pagination button:disabled, +.pagination .disabled { opacity: 0.4; cursor: default; } +.perpage-select { padding: 0.375rem 0.5rem; font-size: 13px; } + +/* -------------------------------------------------------------------------- + Stat cards / dashboard grid + -------------------------------------------------------------------------- */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 25px; +} +.stat-card { + background: var(--bg-card); + border-radius: 0.25rem; + padding: 1.25rem; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} +[data-theme="light"] .stat-card { border: 1px solid var(--border); } +[data-theme="dark"] .stat-card { border: none; } +.stat-card .label { font-size: 14px; color: var(--text-light); margin-bottom: 0.5rem; } +.stat-card .value { font-size: 2rem; font-weight: 600; color: var(--text); } +.stat-card.success .value { color: var(--success); } +.stat-card.warning .value { color: var(--warning); } +.stat-card.danger .value { color: var(--danger); } +.stat-card.info .value { color: var(--info); } + +/* -------------------------------------------------------------------------- + Tabs (editor-tabs used by app.js; overrides Bootstrap .nav-tabs) + -------------------------------------------------------------------------- */ +.editor-tabs.nav-tabs, +.nav-tabs { + border-bottom: 1px solid var(--border); + gap: 0.25rem; +} +.editor-tabs .nav-link, +.nav-tabs .nav-link { + border: none; + border-bottom: 2px solid transparent; + border-radius: 0; + background: transparent; + color: var(--text-light); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + padding: 0.6rem 1rem; +} +.editor-tabs .nav-link:hover, +.nav-tabs .nav-link:hover { color: var(--text); } +.editor-tabs .nav-link.active, +.nav-tabs .nav-link.active { + color: var(--primary); + border-bottom-color: var(--primary); + background: transparent; +} + +/* -------------------------------------------------------------------------- + Content two-column multicol detail flow + -------------------------------------------------------------------------- */ +.content-grid { column-count: 2; column-gap: 25px; } +.content-grid .section-card { break-inside: avoid; } + +/* -------------------------------------------------------------------------- + Responsive: collapse the fixed rail under 768px, keep colors/spacing + -------------------------------------------------------------------------- */ +@media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + transition: transform 0.2s; + } + .sidebar.open { transform: translateX(0); } + .main-content { margin-left: 0; } + .content-grid { column-count: 1; } +} diff --git a/webapp/templates/_imaging_tiles.html b/webapp/templates/_imaging_tiles.html index 8243f06..9f1a8e6 100644 --- a/webapp/templates/_imaging_tiles.html +++ b/webapp/templates/_imaging_tiles.html @@ -58,7 +58,8 @@ {% endif %} {% endif %} {% set card_key = s.serial or ('mac-' ~ (s.mac or 'unknown') ~ '-' ~ (s.ip or '')) %} -
{% else %} -
+
{% if is_inferred %}log-only{% else %}no DeviceId{% endif %}
{% endif %} @@ -86,27 +87,27 @@ {% elif s.mac %}{{ s.mac }} {% else %}{{ s.ip or '(unknown bay)' }}{% endif %} - {% if s.hostname_target and s.serial %}{{ s.hostname_target }}{% endif %} - {% if s.ip %}{{ s.ip }}{% endif %} - {% if s.pctype %}{{ s.pctype }}{% endif %} - {% if s.machinenumber %}#{{ s.machinenumber }}{% endif %} - {% if is_inferred %}log-inferred{% endif %} - {{ s.status or 'in_progress' }} + {% if s.hostname_target and s.serial %}{{ s.hostname_target }}{% endif %} + {% if s.ip %}{{ s.ip }}{% endif %} + {% if s.pctype %}{{ s.pctype }}{% endif %} + {% if s.machinenumber %}#{{ s.machinenumber }}{% endif %} + {% if is_inferred %}log-inferred{% endif %} + {{ s.status or 'in_progress' }}
-
-
+
+
{{ friendly[0] }} {% if not is_inferred %} - {{ stage_idx }}/{{ stage_total or '?' }} + {{ stage_idx }}/{{ stage_total or '?' }} {% endif %}
- {% if not is_inferred %}{{ pct }}%{% endif %} + {% if not is_inferred %}{{ pct }}%{% endif %}
{% if not is_inferred %} -
+
{% endif %} @@ -114,46 +115,44 @@
-
- {% if friendly[1] %}
{{ friendly[1] }}
{% endif %} +
+ {% if friendly[1] %}
{{ friendly[1] }}
{% endif %} {% if s.intune_device_id %} -
- Intune: {{ s.intune_device_id }} - - set category - ARTS request
{% endif %} -
- started {{ s.started_at or '-' }} - last {{ s.last_updated or '-' }} - MAC {{ s.mac or '-' }} - {% if s.current_stage %}{{ s.current_stage }}{% endif %} +
+ started {{ s.started_at or '-' }} + last {{ s.last_updated or '-' }} + MAC {{ s.mac or '-' }} + {% if s.current_stage %}{{ s.current_stage }}{% endif %}
{% if s.error %} -
+
Error: {{ s.error }}
{% endif %} {% if not is_inferred %}
-
LAPS password QR (paste -> scan on bay - persists until cleared)
-
+
LAPS password QR (paste -> scan on bay - persists until cleared)
+
@@ -166,8 +165,8 @@ {% if s.log_tail %}
- {% if is_inferred %}Evidence{% else %}Log tail{% endif %} ({{ s.log_tail | length }} line{{ 's' if s.log_tail | length != 1 }}) -
{% for line in s.log_tail %}{{ line }}
+        {% if is_inferred %}Evidence{% else %}Log tail{% endif %} ({{ s.log_tail | length }} line{{ 's' if s.log_tail | length != 1 }})
+        
{% for line in s.log_tail %}{{ line }}
 {% endfor %}
{% endif %} diff --git a/webapp/templates/audit.html b/webapp/templates/audit.html index e35c9b1..c14f40d 100644 --- a/webapp/templates/audit.html +++ b/webapp/templates/audit.html @@ -1,61 +1,60 @@ -{% extends "base.html" %} -{% block title %}Audit Log - PXE Server Manager{% endblock %} - -{% block content %} -
-

Audit Log

- {{ entries|length }} entries -
- -
-
- Activity History -
-
- {% if entries %} -
- - - - - - - - - - - {% for entry in entries %} - - {% set parts = entry.split(' ', 1) %} - {% if parts|length == 2 %} - {% set meta = parts[1].split('] ', 1) %} - - {% if meta|length == 2 %} - - {% set action_detail = meta[1].split(': ', 1) %} - {% if action_detail|length == 2 %} - - - {% else %} - - {% endif %} - {% else %} - - {% endif %} - {% else %} - - {% endif %} - - {% endfor %} - -
TimestampSourceActionDetails
{{ parts[0] }}{{ meta[0].lstrip('[') }}{{ action_detail[0] }}{{ action_detail[1] }}{{ meta[1] }}{{ parts[1] }}{{ entry }}
-
- {% else %} -
-

No audit log entries yet.

-

Actions like image imports, unattend edits, and backup operations will be logged here.

-
- {% endif %} -
-
-{% endblock %} +{% extends "base.html" %} +{% block title %}Audit Log - PXE Server Manager{% endblock %} + +{% block content %} + + +
+
Activity History
+ {% if entries %} +
+ + + + + + + + + + + {% for entry in entries %} + + {% set parts = entry.split(' ', 1) %} + {% if parts|length == 2 %} + {% set meta = parts[1].split('] ', 1) %} + + {% if meta|length == 2 %} + + {% set action_detail = meta[1].split(': ', 1) %} + {% if action_detail|length == 2 %} + + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% else %} + + {% endif %} + + {% endfor %} + +
TimestampSourceActionDetails
{{ parts[0] }}{{ meta[0].lstrip('[') }}{{ action_detail[0] }}{{ action_detail[1] }}{{ meta[1] }}{{ parts[1] }}{{ entry }}
+
+ {% else %} +
+ +

No audit log entries yet.

+

Actions like image imports, unattend edits, and backup operations will be logged here.

+
+ {% endif %} +
+{% endblock %} diff --git a/webapp/templates/backups.html b/webapp/templates/backups.html index 28b609b..6bc9fa6 100644 --- a/webapp/templates/backups.html +++ b/webapp/templates/backups.html @@ -1,132 +1,140 @@ -{% extends "base.html" %} -{% block title %}Clonezilla Backups - PXE Server Manager{% endblock %} - -{% block content %} -
-

Clonezilla Backups

- -
- -
-
- Machine Backups - {{ backups|length }} -
-
- {% if backups %} - - - - - - - - - - - - {% for b in backups %} - - - - - - - - {% endfor %} - -
Machine #FilenameSizeLast ModifiedActions
{{ b.machine }}{{ b.filename }}{{ "%.1f"|format(b.size / 1073741824) }} GB{{ b.modified | timestamp_fmt }} - - Download - - -
- {% else %} -
-

No backups found. Upload a Clonezilla backup .zip to get started.

-
- {% endif %} -
-
- -
-
-
Backup Naming Convention
-

- Name backup files with the machine number (e.g., 6501.zip). - The Samba share \\pxe-server\clonezilla is also available on the network for direct Clonezilla save/restore operations. -

-
-
- - - - - - -{% endblock %} - -{% block extra_scripts %} - -{% endblock %} +{% extends "base.html" %} +{% block title %}Clonezilla Backups - PXE Server Manager{% endblock %} + +{% block content %} + + +
+
+ + Machine Backups + {{ backups|length }} +
+
+ {% if backups %} +
+ + + + + + + + + + + + {% for b in backups %} + + + + + + + + {% endfor %} + +
Machine #FilenameSizeLast ModifiedActions
{{ b.machine }}{{ b.filename }}{{ "%.1f"|format(b.size / 1073741824) }} GB{{ b.modified | timestamp_fmt }} + + Download + + +
+
+ {% else %} +
+ +

No backups found. Upload a Clonezilla backup .zip to get started.

+
+ {% endif %} +
+
+ +
+
Backup Naming Convention
+

+ Name backup files with the machine number (e.g., 6501.zip). + The Samba share \\pxe-server\clonezilla is also available on the network for direct Clonezilla save/restore operations. +

+
+ + + + + + +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/webapp/templates/base.html b/webapp/templates/base.html index 1ce07db..62915db 100644 --- a/webapp/templates/base.html +++ b/webapp/templates/base.html @@ -1,228 +1,178 @@ - - - - - - - {% block title %}PXE Server Manager{% endblock %} - - - - {% block extra_head %}{% endblock %} - - - - - - - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - - {% endfor %} - {% endif %} - {% endwith %} - - {% block content %}{% endblock %} -
- - - - - - {% block extra_scripts %}{% endblock %} - - + + + + + + + {% block title %}PXE Server Manager{% endblock %} + + + + + + + {% block extra_head %}{% endblock %} + + + + + + + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + + + + + + + {% block extra_scripts %}{% endblock %} + + diff --git a/webapp/templates/dashboard.html b/webapp/templates/dashboard.html index 05a724f..3de36e7 100644 --- a/webapp/templates/dashboard.html +++ b/webapp/templates/dashboard.html @@ -1,241 +1,278 @@ -{% extends "base.html" %} -{% block title %}Dashboard - PXE Server Manager{% endblock %} - -{% block content %} -
-

Dashboard

- -
- - -
-
- PXE Services -
-
- - - - - - - - - - {% for svc in services %} - - - - - - {% endfor %} - -
ServiceStatusState
- {{ svc.name }} - - - {{ "Running" if svc.active else "Stopped" }} - {{ svc.state }}
-
-
- - -
-
- Deployment Images - -
-
- - - - - - - - - - - - {% for img in images %} - - - - - - - - {% endfor %} - -
ImageDeploy Contentunattend.xmlPathActions
- {{ img.friendly_name }}
- {{ img.image_type }} -
- {% if img.has_content %} - Present - {% else %} - Empty - {% endif %} - - {% if img.has_unattend %} - Exists - {% else %} - Missing - {% endif %} - {{ img.deploy_path }} - - Config - - - Unattend - - - -
-
-
- - - - - - - - - - -{% endblock %} - -{% block extra_scripts %} - -{% endblock %} +{% extends "base.html" %} +{% block title %}Dashboard - PXE Server Manager{% endblock %} + +{% block content %} + + + +
+ {% set services_up = services | selectattr('active') | list | length %} + {% set services_total = services | list | length %} +
+
Services running
+
{{ services_up }} / {{ services_total }}
+
+ {% set images_total = images | list | length %} +
+
Image types
+
{{ images_total }}
+
+ {% set images_ready = images | selectattr('has_content') | list | length %} +
+
Images with content
+
{{ images_ready }}
+
+ {% set unattend_missing = images | rejectattr('has_unattend') | list | length %} +
+
Missing unattend.xml
+
{{ unattend_missing }}
+
+
+ + +
+
+ PXE Services +
+
+
+ + + + + + + + + + {% for svc in services %} + + + + + + {% endfor %} + +
ServiceStatusState
+ {{ svc.name }} + + + {{ "Running" if svc.active else "Stopped" }} + {{ svc.state }}
+
+
+
+ + +
+
+ Deployment Images + +
+
+
+ + + + + + + + + + + + {% for img in images %} + + + + + + + + {% endfor %} + +
ImageDeploy Contentunattend.xmlPathActions
+ {{ img.friendly_name }}
+ {{ img.image_type }} +
+ {% if img.has_content %} + Present + {% else %} + Empty + {% endif %} + + {% if img.has_unattend %} + Exists + {% else %} + Missing + {% endif %} + {{ img.deploy_path }} + + Config + + + Unattend + + + +
+
+
+
+ + + + + + + + + + +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/webapp/templates/enrollment.html b/webapp/templates/enrollment.html index c8459a7..672241c 100644 --- a/webapp/templates/enrollment.html +++ b/webapp/templates/enrollment.html @@ -1,135 +1,138 @@ -{% extends "base.html" %} -{% block title %}Enrollment Packages - PXE Server Manager{% endblock %} - -{% block content %} -
-

Enrollment Packages

- -
- -
-
- GCCH Provisioning Packages - {{ packages|length }} -
-
- {% if packages %} - - - - - - - - - - - {% for p in packages %} - - - - - - - {% endfor %} - -
FilenameSizeLast ModifiedActions
{{ p.filename }}{{ "%.1f"|format(p.size / 1048576) }} MB{{ p.modified | timestamp_fmt }} - - Download - - -
- {% else %} -
-

No enrollment packages found. Upload a .ppkg file to get started.

-
- {% endif %} -
-
- -
-
-
About Enrollment Packages
-

- GCCH enrollment .ppkg provisioning packages are copied to - C:\Enrollment\ on the target machine after imaging. - At OOBE, connect to a network with internet, press Windows key 5 times, - then browse to C:\Enrollment\ and select the package. No USB stick needed. -

-

- Naming convention: Use with-office.ppkg and - without-office.ppkg to match the WinPE enrollment menu options. -

-
-
- - - - - - -{% endblock %} - -{% block extra_scripts %} - -{% endblock %} +{% extends "base.html" %} +{% block title %}Enrollment Packages - PXE Server Manager{% endblock %} + +{% block content %} + + +
+
+ GCCH Provisioning Packages + {{ packages|length }} +
+
+ {% if packages %} +
+ + + + + + + + + + + {% for p in packages %} + + + + + + + {% endfor %} + +
FilenameSizeLast ModifiedActions
{{ p.filename }}{{ "%.1f"|format(p.size / 1048576) }} MB{{ p.modified | timestamp_fmt }} + + Download + + +
+
+ {% else %} +
+ +

No enrollment packages found. Upload a .ppkg file to get started.

+
+ {% endif %} +
+
+ +
+

About Enrollment Packages

+

+ GCCH enrollment .ppkg provisioning packages are copied to + C:\Enrollment\ on the target machine after imaging. + At OOBE, connect to a network with internet, press Windows key 5 times, + then browse to C:\Enrollment\ and select the package. No USB stick needed. +

+

+ Naming convention: Use with-office.ppkg and + without-office.ppkg to match the WinPE enrollment menu options. +

+
+ + + + + + +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/webapp/templates/image_config.html b/webapp/templates/image_config.html index 85004a7..8ec300d 100644 --- a/webapp/templates/image_config.html +++ b/webapp/templates/image_config.html @@ -1,458 +1,492 @@ -{% extends "base.html" %} -{% block title %}{{ friendly_name }} - Configuration{% endblock %} - -{% block extra_head %} - -{% endblock %} - -{% block content %} -
-
-

{{ friendly_name }}

- - Image Configuration - — OS Selection: {{ config.os_selection or 'Not set' }} - -
- - Edit Unattend - -
- -{# ==================== SECTION 1: Hardware Models ==================== #} -
-
- Hardware Models - {{ config.hardware_models|length }} - -
- - -
-
-
-
- - - -
- - - - - - - - - - - - {% for hm in config.hardware_models %} - - - - - - - - {% endfor %} - -
#ModelDriver Family IDOn Disk
{{ loop.index }} - {% if hm._on_disk %} - Yes - {% else %} - No - {% endif %} - - -
- {% if not config.hardware_models %} -
- No hardware models configured. -
- {% endif %} -
-
- -{# ==================== SECTION 2: Driver Packs ==================== #} -
-
- Driver Packs - {{ config.drivers|length }} - -
- - -
-
-
-
- - - -
-
- - - - - - - - - - - - - - {% for drv in config.drivers %} - - - - - - - - - - {% endfor %} - -
#FamilyModelsFile NameOS IDsOn Disk
{{ loop.index }}{{ drv.family }}{{ drv.models }} - {{ drv.FileName or drv.get('fileName','') }} - {{ drv.osId }} - {% if drv._on_disk %} - Yes - {% else %} - No - {% endif %} - - -
-
- {% if not config.drivers %} -
- No driver packs configured. -
- {% endif %} -
- - {# Orphan drivers sub-section: zips on disk that aren't referenced by #} - {# HardwareDriver.json. Each row has an inline Adopt form (family + dest #} - {# inputs -> adds an entry to HardwareDriver.json). Bulk remove deletes #} - {# the selected .zip files from Out-of-box Drivers/. #} - {% if config.orphan_drivers %} - - {% endif %} -
- - - - -{# ==================== SECTION 3: Operating Systems ==================== #} -
-
- Operating Systems - {{ config.operating_systems|length }} - -
- -
-
-
-
- - - -
- - - - - - - - - - - - - - - {% for entry in config.operating_systems %} - {% set osv = entry.operatingSystemVersion %} - - - - - - - - - - - {% endfor %} - -
#Product NameVersionBuildIDActiveWIM On Disk
{{ loop.index }}{{ osv.productName }}{{ osv.versionNumber }}{{ osv.buildNumber }}{{ osv.id }} - {% if osv.isActive %} - Active - {% else %} - Inactive - {% endif %} - - {% if entry._on_disk %} - Yes - {% else %} - No - {% endif %} - - -
- {% if not config.operating_systems %} -
- No operating systems configured. -
- {% endif %} -
-
- -{# ==================== SECTION 4: Packages ==================== #} -
-
- Packages - {{ config.packages|length }} - -
- - -
-
-
-
- - - -
-
- - - - - - - - - - - - - - - {% for pkg in config.packages %} - - - - - - - - - - - {% endfor %} - -
#NameCommentFileOS IDsEnabledOn Disk
{{ loop.index }}{{ pkg.name }}{{ pkg.comment }} - {{ pkg.fileName or pkg.get('FileName','') }} - {{ pkg.osId }} - {% if pkg.enabled %} - Yes - {% else %} - No - {% endif %} - - {% if pkg._on_disk %} - Yes - {% else %} - No - {% endif %} - - -
-
- {% if not config.packages %} -
- No packages configured. -
- {% endif %} -
-
- - - - -{% endblock %} - -{% block extra_scripts %} - -{% endblock %} +{% extends "base.html" %} +{% block title %}{{ friendly_name }} - Configuration{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block content %} + + +{# ==================== SECTION 1: Hardware Models ==================== #} +
+
+ + Hardware Models + {{ config.hardware_models|length }} + +
+ + +
+
+
+
+ + + +
+
+ + + + + + + + + + + + {% for hm in config.hardware_models %} + + + + + + + + {% endfor %} + +
#ModelDriver Family IDOn Disk
{{ loop.index }} + {% if hm._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.hardware_models %} +
+ No hardware models configured. +
+ {% endif %} +
+
+ +{# ==================== SECTION 2: Driver Packs ==================== #} +
+
+ + Driver Packs + {{ config.drivers|length }} + +
+ + +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + {% for drv in config.drivers %} + + + + + + + + + + {% endfor %} + +
#FamilyModelsFile NameOS IDsOn Disk
{{ loop.index }}{{ drv.family }}{{ drv.models }} + {{ drv.FileName or drv.get('fileName','') }} + {{ drv.osId }} + {% if drv._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.drivers %} +
+ No driver packs configured. +
+ {% endif %} +
+ + {# Orphan drivers sub-section: zips on disk that aren't referenced by #} + {# HardwareDriver.json. Each row has an inline Adopt form (family + dest #} + {# inputs -> adds an entry to HardwareDriver.json). Bulk remove deletes #} + {# the selected .zip files from Out-of-box Drivers/. #} + {% if config.orphan_drivers %} +
+ +
+
+ +
+ + + + + + + + + + + {% for orph in config.orphan_drivers %} + + + + + + + {% endfor %} + +
File NameRelative PathAdopt into HardwareDriver.json
{{ orph.fileName }}{{ orph.relPath }} + {# adopt inputs target standalone adoptForm below - form-in-form is dropped by parser and hijacked the delete form #} +
+ + + +
+
+
+
+ +
+
+ {# one adopt form per orphan row, kept OUTSIDE the delete form #} + {% for orph in config.orphan_drivers %} +
+ + +
+ {% endfor %} +
+
+ {% endif %} +
+ + + + +{# ==================== SECTION 3: Operating Systems ==================== #} +
+
+ + Operating Systems + {{ config.operating_systems|length }} + +
+ +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + + {% for entry in config.operating_systems %} + {% set osv = entry.operatingSystemVersion %} + + + + + + + + + + + {% endfor %} + +
#Product NameVersionBuildIDActiveWIM On Disk
{{ loop.index }}{{ osv.productName }}{{ osv.versionNumber }}{{ osv.buildNumber }}{{ osv.id }} + {% if osv.isActive %} + Active + {% else %} + Inactive + {% endif %} + + {% if entry._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.operating_systems %} +
+ No operating systems configured. +
+ {% endif %} +
+
+ +{# ==================== SECTION 4: Packages ==================== #} +
+
+ + Packages + {{ config.packages|length }} + +
+ + +
+
+
+
+ + + +
+
+ + + + + + + + + + + + + + + {% for pkg in config.packages %} + + + + + + + + + + + {% endfor %} + +
#NameCommentFileOS IDsEnabledOn Disk
{{ loop.index }}{{ pkg.name }}{{ pkg.comment }} + {{ pkg.fileName or pkg.get('FileName','') }} + {{ pkg.osId }} + {% if pkg.enabled %} + Yes + {% else %} + No + {% endif %} + + {% if pkg._on_disk %} + Yes + {% else %} + No + {% endif %} + + +
+
+ {% if not config.packages %} +
+ No packages configured. +
+ {% endif %} +
+
+ + + + +{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/webapp/templates/imaging.html b/webapp/templates/imaging.html index e103b0e..cdf8a9a 100644 --- a/webapp/templates/imaging.html +++ b/webapp/templates/imaging.html @@ -18,38 +18,41 @@ function cancelImagingReload() { {% block content %} -
+