Hardware coverage: flag models the server has no drivers or BIOS for

WinPE now reports each machine's WMI model, manufacturer, Dell system
SKU and BIOS version to /hardware/seen (winpe-status-push.ps1, best
effort, wmic only). The webapp records every model seen and checks it
against what the server has:
  - each image's Deploy/Control/HardwareDriver.json, matched exactly as
    PESetup's GetDriverByModel does (as reimplemented in
    lint-driver-catalogue.py), plus whether the pack's zip is on disk;
  - the WinPE BIOS manifest (substring match like check-bios.cmd).

/hardware lists every model with per-image driver status and BIOS
status. A model missing a Win11 pack or a BIOS entry becomes item 5 on
the Technician To-Do (and the banner count) until the pack/BIOS is added
or it is dismissed. Virtual machines are ignored.

Also deploys the repo winpe-status-push.ps1 over the live copy, which
predated the -CurrentStage/-StageIndex parameters startnet.cmd passes
for the BIOS-flash stage, so that status post was failing.
This commit is contained in:
cproudlock
2026-10-09 09:23:52 -04:00
parent e43ab2a652
commit b691a82530
8 changed files with 485 additions and 1 deletions

View File

@@ -41,6 +41,7 @@ from werkzeug.utils import secure_filename
import config
from services import (
blancco_evidence,
hardware_coverage,
blancco_report,
deploy,
fs,
@@ -721,6 +722,7 @@ def blancco_servicenow_mark(document_id):
def blancco_todo():
"""Technician to-do: the playbook steps the server cannot do itself."""
return render_template("reports_todo.html", todo=blancco_evidence.todo(),
hardware=hardware_coverage.open_gaps(),
site=config.BLANCCO_SITE)
@@ -744,6 +746,37 @@ def blancco_evidence_file(filename):
return send_file(fpath, as_attachment=True)
@app.route("/hardware/seen", methods=["POST"])
def hardware_seen():
# CSRF-exempt machine-to-machine endpoint: WinPE (winpe-status-push.ps1)
# reports each machine's model, SKU and BIOS version before imaging.
p = request.get_json(silent=True) or {}
try:
hardware_coverage.record(p)
except ValueError as ex:
return jsonify({"error": str(ex)}), 400
return jsonify({"ok": True}), 200
@app.route("/hardware")
def hardware_page():
"""Every model WinPE has seen, with driver-pack and BIOS coverage."""
return render_template("hardware.html", rows=hardware_coverage.coverage(),
images=hardware_coverage.IMAGES)
@app.route("/hardware/dismiss", methods=["POST"])
def hardware_dismiss():
model = request.form.get("model", "")
done = request.form.get("done", "1") == "1"
try:
hardware_coverage.dismiss(model, done)
except ValueError:
abort(400)
audit("HARDWARE_DISMISS", f"{model} {'dismissed' if done else 'reopened'}")
return redirect(request.form.get("next") or url_for("hardware_page"))
@app.route("/reports/download/<filename>")
def blancco_download_report(filename):
filename = secure_filename(filename)
@@ -1531,7 +1564,9 @@ def inject_globals():
return {
"all_image_types": config.IMAGE_TYPES,
"all_friendly_names": config.FRIENDLY_NAMES,
"blancco_todo": blancco_evidence.todo_counts(),
"blancco_todo": dict(blancco_evidence.todo_counts(),
hardware=hardware_coverage.gap_count(),
total=blancco_evidence.todo_counts()["total"] + hardware_coverage.gap_count()),
}