- Unattend: a command's '...' menu stayed open after Move up/down and
covered the next row's menu, so the next click could stage removing the
wrong command. The menu now closes and focus returns to its button.
- '...' menus close on an outside click, on Escape and when another
opens (static/app.js), for every page.
- Drivers: Remove asks to confirm again, as before the redesign. Long zip
paths in a row body wrap (the page scrolled sideways at 390 px).
- Rollback review returns to the page it was opened from (Dell page or
Updates overview), for both Cancel and the rollback itself.
- To-do: the ServiceNow period offers a year again (366 days).
- Imaging floor: a search that matches a log-only bay opens that group;
its open state survives the live refresh. 'Clear all' states the
number of session files it deletes, hidden finished ones included.
- Dashboard: the image key pattern is valid under the browser's v flag
(client-side validation was silently skipped).
- Import: allowed_import_source() compares real paths, so
'<upload dir>/../..' is refused. This path traversal predates the
redesign; the import moves the source tree, so it mattered.
Blocker
- /reports/export returned 500: REPORT_EXPORT_RANGES and _months_ago were
deleted with the old reports route. Restored.
Major
- A session without stage_index/stage_total took down /, /imaging and
/imaging/tiles (int filter on Undefined). The templates default both;
saving a LAPS password for a session that no longer exists is refused
(it created a bare session).
- /reports/todo parsed every report four times (23 s at 700 reports).
blancco_report.parse() now caches by path, mtime and size: 0.09 s.
- Stored XSS through the session serial in the Clear confirm (the
browser decodes ' before running an inline handler). The message
moves to data-confirm, and update_session() only accepts serials of
letters, digits, '.', '_' and '-' (400 from /imaging/status otherwise).
- Reports without a document id and with odd characters in the file name
got a key the mark routes rejected, so they could never be cleared.
report_key() now always produces a valid key.
Minor
- failure_note() handles a list-valued erasure target (500 on the report
and to-do pages).
- _recently_marked() tolerates naive, 'Z' and non-dict state entries.
- Key regexes use fullmatch (a trailing newline was accepted).
- Unreadable reports are no longer counted as to tick / to destroy; the
reports page already flags them as Unreadable.
- Rollback review recounts restorable files after reading the backups.
- Enrollment: the startnet fallback package (no or empty ppkg.conf) and a
quoted SOURCE_PPKG are protected too.
- Backup upload overwrite check is case-insensitive (SMB share) and
replaces the existing file rather than adding a case twin.
- Overview polling keeps the last good rows on an error and pauses while
the tab is hidden. A shown or typed LAPS password survives the floor's
refresh.
- hardware_dismiss only redirects to same-site paths; tiles ?limit is
clamped; the Updates overview survives an unreadable Dell scan or iPXE
state; describe() uses aversion(); a client cannot label its session
'inferred'.
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.
Updates group:
- Dell drivers and BIOS: check Dell's catalogs against all three driver maps
and models.txt, download with a shared speed cap, repack, preview, apply
with backups and rollback.
- iPXE boot files: check GitHub for the signed Secure Boot set, verify the
sha256 and each file's signer, install atomically, roll back. Includes the
by-hand procedure.
- Blancco: upload a release, stage it, test-boot it from the menu, activate,
roll back.
- BPRT sync: validate a batch uploaded from the MediaCreator Lite cache
(scripts/windows/Sync-BPRT.ps1 or a zip), show the diff, swap with the site
overlay files kept.
Images: one sidebar entry per image with Overview, Drivers, Unattend and OS and
Packages tabs. Every save shows a diff first, keeps the file's format, makes a
backup and can be undone. Driver lint runs inline.
Enrollment: upload standard/engineer MCL packages, one per tenant/purpose,
with archive and restore. Hardware coverage counts engineer as Win11.
- Accessibility: dark ink (#00003d) on warning/success buttons, badges, and success alert - white text on #ff9500/#0ad64f failed WCAG AA (~2:1); dark-on-bright now passes.
- Heading hierarchy: sidebar brand h1 -> div.brand-title (with matching CSS selector), and standardized all page-header titles to a single <h1> per page (8 templates were h2).
- Prefix-safe modal JS: dashboard/backups/reports/enrollment delete+clone actions now build their form action from url_for(...'__K__'/'__F__').replace(...) instead of hardcoded paths, so they survive a URL-prefix mount.
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 <form>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.
Imaging dashboard
- services/imaging_log_tail.py: parses dnsmasq leases, Apache access log,
Samba per-host log files, and dnsmasq syslog (DHCP/TFTP). Synthesizes
inferred sessions keyed by MAC for bays that have only touched the boot
chain but not yet pushed to /imaging/status. Active window 90 min.
- imaging_status.list_sessions() merges inferred sessions into the dashboard
list. Real client-pushed sessions win for the same MAC.
- imaging_status: stage_history field tracks every stage transition (capped
30); sidecar .log file per serial records every log_lines push uncapped
(read_full_log() caps detail-page response to 1 MB).
- delete_session/delete_all_sessions clean up sidecar .log too.
- New SSE endpoint /imaging/stream emits a session-list hash every 5s.
Client fetches /imaging/tiles (HTML partial) on hash change and swaps
#imaging-tiles innerHTML. Polling fallback at 15s if SSE drops.
- Tile-swap preserves scroll, filter input, expanded state via localStorage,
and any LAPS input the operator is mid-pasting (swap skipped when a
laps-input is focused).
- imaging.html: removed 15s location.reload(). Added live-status dot in
header (gray idle / green SSE connected / red SSE lost).
- _imaging_tiles.html: shared partial used by both /imaging full render and
/imaging/tiles SSE refresh. Inferred bays render with yellow border +
log-inferred badge + no progress bar (stage_index inference is coarse).
- imaging_detail.html (new): per-bay forensics page at /imaging/session/
<serial>. Session metadata grid, stage timeline table, full sidecar log
with truncation indicator, Copy-support-summary button. Linked from each
client-pushed tile.
- qr-render.js exposes window.renderAllQRs() so the SSE swap can re-render
Intune device-ID QRs in the swapped-in tiles.
Image management
- services/image_registry.py: JSON registry of image types at
{SAMBA_SHARE}/image-registry.json. Bootstraps from baked-in
config.IMAGE_TYPES on first run. create/clone/delete/rename_friendly
mutate the file then call reload() which rewrites config.IMAGE_TYPES +
config.FRIENDLY_NAMES in place. Sidebar reflects on next request.
- app.py routes: /images/new, /images/<t>/clone, /images/<t>/delete (with
optional content-wipe checkbox), /images/<t>/rename.
- dashboard.html: + New image type button + Clone/Delete per row, all in
Bootstrap modals with confirmation copy.
- Clone copies Deploy/ tree but preserves symlinks to shared dirs (Out-of-
box Drivers, Operating Systems, Packages) so disk usage stays low.
- Delete with content checked unlinks symlinks (does not follow into shared
dirs).
Driver / package upload + orphan adoption
- services/images.py: upload_driver, adopt_orphan, remove_orphans,
upload_package. Filename sanitization blocks path traversal.
- app.py routes: /images/<t>/drivers/upload, /images/<t>/drivers/adopt,
/images/<t>/drivers/orphans/delete, /images/<t>/packages/upload.
- image_config.html: Upload .zip button + modal on Drivers section. Orphan
drivers card-footer rebuilt as interactive list with per-row Adopt inline
form (family + destinationDir inputs) and bulk select+delete.
- Upload .zip on Packages section with optional destinationDir field that
appends a packages.json entry.
Configuration
- config.py: new env vars DNSMASQ_LEASES, APACHE_ACCESS_LOG, SAMBA_LOG_DIR,
DNSMASQ_SYSLOG for the log-tailer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- webapp/: Flask web management app with:
- Dashboard showing image types and service status
- USB import page for WinPE deployment content
- Unattend.xml visual editor (driver paths, specialize commands,
OOBE settings, first logon commands, raw XML view)
- API endpoints for services and image management
- SETUP.md: Complete setup documentation for streamlined process
- build-usb.sh: Now copies webapp and optional WinPE images to USB
- playbook: Added webapp deployment (systemd service, Apache reverse
proxy), offline package verification, WinPE auto-import from USB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>