Webapp redesign phase 5: Blancco reports, report and to-do

Reports (/reports):
- Grouped by month of the erasure date inside each report (not the file
  date), newest first; older months fold. Each month header shows the
  evidence pack state (builds on the 1st, to upload with Download, or
  uploaded).
- One line per report: erasure date, system serial, model, drives,
  result ('Successful, fallback' when a failed attempt fell back to
  another standard), cloud and ServiceNow state. The row body lists every
  drive and erasure attempt with the failure reason.
- Search by system serial, model, drive serial or file; filters for
  failed, not in cloud and to tick. 'To tick' uses the to-do's 30-day
  window; older unticked reports read 'Not ticked'.
- Export and the evidence pack download moved into header popovers.

Report (/reports/view/<file>):
- Header with serial, model and the per-drive result; a notice names
  the drive to destroy until its COD is recorded; Copy ServiceNow note.
- One card per drive with every attempt, a step track and the failure
  reason; steps and error counts fold. System, customer and provenance
  (integrity signature folded, copyable) on the side. Sizes in decimal
  units, as on the list.
- blancco_evidence.failure_note() is shared with the ServiceNow list.

To-do (/reports/todo):
- The ServiceNow tick list is now its first section (period and show
  ticked filters); /reports/servicenow redirects there.
- Five counters; one line per item with details in the row body.
- COD recorded, Mark uploaded and Resolved confirm first (the attribute
  quoting is safe for JSON) and can be undone for 24 hours from the
  page. The header total includes hardware gaps, matching the sidebar.
This commit is contained in:
cproudlock
2026-10-14 14:00:54 -04:00
parent cc1e76f3ab
commit a6f95fcca1
7 changed files with 695 additions and 645 deletions

View File

@@ -197,6 +197,26 @@ def build_evidence_zip(month: str, fileobj) -> int:
return len(reports)
def failure_note(data: dict):
"""(failed erasures, ServiceNow note) for a parsed report. A drive is
failed only if none of its erasures succeeded (a Purge failure followed
by a successful Clear fallback is a good wipe). The note is "" when every
drive was wiped, and also covers a report with no erasure at all."""
erasures = data.get("erasures") or []
outcomes = blancco_report.drive_outcomes(data)
failed = [e for i, e in enumerate(erasures)
if not outcomes[blancco_report._drive_key(e, i)]["ok"]]
if erasures and not failed:
return failed, ""
reasons = [blancco_report.failure_reason(e) for e in failed]
drives = ", ".join(
f"{(e.get('target') or {}).get('model', '')} {(e.get('target') or {}).get('serial', '')}".strip()
for e in failed)
return failed, (f"Blancco wipe failed on {drives or 'drive'}: "
f"{'; '.join(x.rstrip('. ') for x in reasons if x) or 'no erasure recorded'}. "
"Drive to be destroyed; record Certificate of Destruction.")
def servicenow_items(days: int = 30, include_done: bool = False) -> list[dict]:
"""Reports to reflect on the Onsite Bin Management RITM, newest first."""
since = datetime.now(timezone.utc) - timedelta(days=days)
@@ -211,15 +231,7 @@ def servicenow_items(days: int = 30, include_done: bool = False) -> list[dict]:
if isinstance(system, list):
system = system[0] if system else {}
erasures = data.get("erasures") or []
outcomes = blancco_report.drive_outcomes(data)
# A drive is failed only if none of its erasures succeeded (a Purge
# failure followed by a successful Clear fallback is a good wipe).
failed = [e for i, e in enumerate(erasures)
if not outcomes[blancco_report._drive_key(e, i)]["ok"]]
reasons = [blancco_report.failure_reason(e) for e in failed]
drives = ", ".join(
f"{(e.get('target') or {}).get('model', '')} {(e.get('target') or {}).get('serial', '')}".strip()
for e in failed)
failed, note = failure_note(data)
items.append({
"document_id": r["document_id"],
"key": r["key"],
@@ -228,10 +240,7 @@ def servicenow_items(days: int = 30, include_done: bool = False) -> list[dict]:
"serial": system.get("serial", ""),
"model": system.get("model", ""),
"failed": bool(failed) or not erasures,
"note": (f"Blancco wipe failed on {drives or 'drive'}: "
f"{'; '.join(x.rstrip('. ') for x in reasons if x) or 'no erasure recorded'}. "
"Drive to be destroyed; record Certificate of Destruction.")
if (failed or not erasures) else "",
"note": note,
"cloud": cloud_status(state, r["document_id"]),
"done": done.get("ts") if done else None,
})