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.
This commit is contained in:
cproudlock
2026-10-14 13:44:07 -04:00
parent 35cad62733
commit 0b86255711
8 changed files with 427 additions and 422 deletions

View File

@@ -243,6 +243,42 @@ def hide_stale(sessions: list[dict], hours: float = ACTIVE_WINDOW_HOURS) -> tupl
return kept, hidden
# Imaging floor order: what needs a person first, then live work, then done.
FLOOR_RANK = {"failed": 0, "action": 1, "running": 2, "done": 3, "logs": 4}
def floor_state(s: dict) -> str:
"""One of failed / action / running / done / logs for the floor list.
"action" is stage 7 of a Windows imaging run (registered in Intune and
waiting for someone to assign the device category or request lockdown).
"""
if s.get("source") == "inferred":
return "logs"
status = s.get("status") or "in_progress"
if status == "failed":
return "failed"
if status == "succeeded":
return "done"
blancco = s.get("source") == "blancco" or s.get("pctype") == "blancco"
try:
idx = int(s.get("stage_index") or 0)
except (TypeError, ValueError):
idx = 0
if not blancco and idx == 7:
return "action"
return "running"
def floor_order(sessions: list[dict]) -> list[dict]:
"""Sessions sorted for the floor, each tagged with "floor_state"."""
for s in sessions:
s["floor_state"] = floor_state(s)
# Stable sorts: newest first within each state.
out = sorted(sessions, key=lambda s: s.get("last_updated") or "", reverse=True)
return sorted(out, key=lambda s: FLOOR_RANK[s["floor_state"]])
def get_session(serial: str) -> Optional[dict]:
path = _path_for(serial)
if not os.path.isfile(path):