webapp: technician to-do for the manual CMMC steps; count fallbacks as wiped

Technician To-Do (sidebar link with a count, and a banner on every page
while anything is open) lists the playbook steps the server cannot do:
  1. wipes still to tick on the Onsite Bin RITM (ENT-045/159)
  2. monthly evidence packs to upload to Box (ENT-056/158), Mark uploaded
  3. drives that failed to wipe, to destroy (COD recorded)
  4. reports Blancco could not upload to the cloud (Resolved)
Marks are stored in the Blancco state file; the counts are cached for 60s
and refreshed on every mark, ServiceNow tick and cloud-status post.

Results are now judged per drive. Blancco keeps every attempt in the
report, so a NIST Purge that failed followed by a successful Clear
fallback on the same drive appeared as two entries, and the Reports page
rolled the whole report up to Failed. 20 such wipes since May showed as
Failed; they were wiped (the work instruction counts the fallback as a
success). The CSV marks those rows "Fallback: ... then erased with ...".
One genuine failure remains: HNMD1V3, Seagate Z500R4KG, 2026-08-11.
This commit is contained in:
cproudlock
2026-10-05 13:50:26 -04:00
parent 44a7eeae95
commit 020f043d8b
5 changed files with 297 additions and 15 deletions

View File

@@ -167,6 +167,41 @@ def failure_reason(er: dict) -> str:
return "; ".join(parts) or f"state: {er.get('state') or 'unknown'}"
def _drive_key(er: dict, index: int) -> str:
target = er.get("target") or {}
if isinstance(target, list):
target = target[0] if target else {}
return target.get("serial") or f"#{index}"
def drive_outcomes(data: dict) -> dict:
"""Per drive: did any erasure on it succeed?
Blancco falls back to a weaker standard when one fails on a drive (e.g.
NIST 800-88 Purge fails, then Clear succeeds) and the report keeps both
entries. The drive is wiped if any of its erasures succeeded; that is
what the GE work instruction counts as a successful wipe.
Returns {drive_key: {"ok": bool, "standard": str}}.
"""
out = {}
for i, er in enumerate(data.get("erasures") or []):
key = _drive_key(er, i)
ok = (er.get("state") or "").strip().lower() == "successful"
cur = out.setdefault(key, {"ok": False, "standard": ""})
if ok and not cur["ok"]:
cur.update(ok=True, standard=er.get("erasure_standard_name", ""))
return out
def report_result(data: dict) -> str:
""""Successful" when every drive was wiped, "Failed" if any drive was
not, "" when the report has no erasures."""
outcomes = drive_outcomes(data)
if not outcomes:
return ""
return "Successful" if all(o["ok"] for o in outcomes.values()) else "Failed"
def export_rows(filename: str, data: dict, when: str, cloud: str = "") -> list[dict]:
"""One CSV row per erased drive; a report with no erasures still gets a row.
@@ -191,7 +226,12 @@ def export_rows(filename: str, data: dict, when: str, cloud: str = "") -> list[d
"cloud_upload": cloud,
}
rows = []
for er in data.get("erasures") or []:
outcomes = drive_outcomes(data)
for i, er in enumerate(data.get("erasures") or []):
reason = failure_reason(er)
outcome = outcomes.get(_drive_key(er, i)) or {}
if reason and outcome.get("ok"):
reason = f"Fallback: {reason}; then erased with {outcome.get('standard') or 'another standard'}"
target = er.get("target") or {}
if isinstance(target, list):
target = target[0] if target else {}
@@ -211,6 +251,6 @@ def export_rows(filename: str, data: dict, when: str, cloud: str = "") -> list[d
"end_time": er.get("end_time", ""),
"elapsed_time": er.get("elapsed_time", ""),
"total_errors": er.get("total_errors", ""),
"failure_reason": failure_reason(er),
"failure_reason": reason,
})
return rows or [base]