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

@@ -565,17 +565,10 @@ def blancco_reports():
sysinfo = (data.get("hardware") or {}).get("system") or {}
serial = sysinfo.get("serial", "") or ""
model = sysinfo.get("model", "") or ""
# Overall erasure result: each erasure entry has its own
# 'state' (Successful / Failed / ...). If any drive failed
# the report rolls up to Failed; otherwise Successful.
erasures = data.get("erasures") or []
states = [(e.get("state") or "").strip() for e in erasures if e.get("state")]
if not states:
result = ""
elif any(s.lower() != "successful" for s in states):
result = "Failed"
else:
result = "Successful"
# Overall result per drive: Failed if any drive was not
# wiped. A failed Purge followed by a successful Clear
# fallback on the same drive is a successful wipe.
result = blancco_report.report_result(data)
cloud = blancco_evidence.cloud_status(
state, (data.get("meta") or {}).get("document_id", ""))
except Exception:
@@ -724,6 +717,33 @@ def blancco_servicenow_mark(document_id):
return redirect(url_for("blancco_servicenow", all=request.args.get("all")))
@app.route("/reports/todo")
def blancco_todo():
"""Technician to-do: the playbook steps the server cannot do itself."""
return render_template("reports_todo.html", todo=blancco_evidence.todo(),
site=config.BLANCCO_SITE)
@app.route("/reports/todo/<kind>/<key>", methods=["POST"])
def blancco_todo_mark(kind, key):
done = request.form.get("done", "1") == "1"
try:
blancco_evidence.mark(kind, key, done)
except ValueError:
abort(400)
audit("REPORT_TODO", f"{kind} {key} {'done' if done else 'undone'}")
return redirect(url_for("blancco_todo"))
@app.route("/reports/evidence/file/<filename>")
def blancco_evidence_file(filename):
filename = secure_filename(filename)
fpath = os.path.join(config.BLANCCO_REPORTS, "evidence", filename)
if not os.path.isfile(fpath):
abort(404)
return send_file(fpath, as_attachment=True)
@app.route("/reports/download/<filename>")
def blancco_download_report(filename):
filename = secure_filename(filename)
@@ -1481,6 +1501,7 @@ def inject_globals():
return {
"all_image_types": config.IMAGE_TYPES,
"all_friendly_names": config.FRIENDLY_NAMES,
"blancco_todo": blancco_evidence.todo_counts(),
}