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:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user