Files
pxe-server/webapp/templates/reports_todo.html
cproudlock b691a82530 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.
2026-10-09 09:23:52 -04:00

174 lines
8.4 KiB
HTML

{% extends "base.html" %}
{% block title %}Technician To-Do - PXE Server Manager{% endblock %}
{% block content %}
<div class="page-header">
<h1><i class="bi bi-clipboard-check"></i> Technician To-Do</h1>
<div class="header-actions">
<span class="badge badge-{{ 'warning' if todo.total else 'success' }} badge-lg">{{ todo.total }} open</span>
</div>
</div>
<p class="text-muted">
Blancco wipes, reports, evidence packs and cloud uploads are automatic. These are the
CMMC playbook steps that still need a technician ({{ site }}). Mark each one done here once finished.
</p>
{# 1. ServiceNow #}
<div class="section-card">
<div class="section-title">1. Tick wipes on the Onsite Bin Management RITM (ENT-045 / ENT-159)</div>
{% if todo.servicenow %}
<p class="mb-2"><strong>{{ todo.servicenow }}</strong> wipe{{ 's' if todo.servicenow != 1 }} from the last 30 days still to tick in ServiceNow.</p>
<a href="{{ url_for('blancco_servicenow') }}" class="pxe-btn pxe-btn-primary"><i class="bi bi-list-check"></i> Open the tick list</a>
{% else %}
<p class="text-muted mb-0">All caught up.</p>
{% endif %}
</div>
{# 2. Evidence packs #}
<div class="section-card">
<div class="section-title">2. Upload the monthly evidence pack to Box (ENT-056 / ENT-158)</div>
{% if todo.evidence %}
<p class="mb-2">Saved automatically on the 1st. Download it, upload it to the site's ENT-056 / ENT-158 evidence folders in Box, then mark it uploaded.</p>
<div class="table-container">
<table class="data-table">
<thead><tr><th>Month</th><th>File</th><th class="text-end">Actions</th></tr></thead>
<tbody>
{% for p in todo.evidence %}
<tr>
<td class="mono">{{ p.month }}</td>
<td class="mono">{{ p.file }}</td>
<td class="text-end text-nowrap">
<a href="{{ url_for('blancco_evidence_file', filename=p.file) }}" class="pxe-btn pxe-btn-secondary btn-row-action"><i class="bi bi-download"></i> Download</a>
<form method="post" action="{{ url_for('blancco_todo_mark', kind='evidence_uploaded', key=p.month) }}" style="display:inline;">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="pxe-btn pxe-btn-success btn-row-action">Mark uploaded</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">No pack waiting. The next one is saved on the 1st of next month.</p>
{% endif %}
</div>
{# 3. Failed wipes #}
<div class="section-card">
<div class="section-title">3. Destroy drives that failed to wipe (ENT-158 / ENT-159)</div>
{% if todo.failed %}
<p class="mb-2">Remove the drive, destroy it per site procedure, record the Certificate of Destruction and the destruction note on the RITM, then mark it here.</p>
<div class="table-container">
<table class="data-table">
<thead><tr><th>Serial</th><th>Model</th><th>Wiped</th><th>Failure</th><th class="text-end">Actions</th></tr></thead>
<tbody>
{% for it in todo.failed %}
<tr title="{{ it.file }}">
<td class="mono">{{ it.serial or '-' }}</td>
<td>{{ it.model or '-' }}</td>
<td class="mono text-nowrap">{{ it.when | local_dt }}</td>
<td class="small wrap">{{ it.note }}</td>
<td class="text-end text-nowrap">
<a href="{{ url_for('blancco_view_report', filename=it.file) }}" class="pxe-btn pxe-btn-secondary btn-row-action"><i class="bi bi-eye"></i> Report</a>
<form method="post" action="{{ url_for('blancco_todo_mark', kind='destroyed', key=it.document_id) }}" style="display:inline;">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="pxe-btn pxe-btn-success btn-row-action">COD recorded</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">No failed wipes waiting for destruction.</p>
{% endif %}
</div>
{# 4. Not in the cloud #}
<div class="section-card">
<div class="section-title">4. Reports that did not reach the Blancco cloud (ENT-056)</div>
{% if todo.not_in_cloud %}
<p class="mb-2">Auditors pull certificates from the Blancco Management Portal. Upload the report's XML there by hand, then mark it resolved.</p>
<div class="table-container">
<table class="data-table">
<thead><tr><th>Serial</th><th>Model</th><th>Wiped</th><th class="text-end">Actions</th></tr></thead>
<tbody>
{% for it in todo.not_in_cloud %}
<tr title="{{ it.file }}">
<td class="mono">{{ it.serial or '-' }}</td>
<td>{{ it.model or '-' }}</td>
<td class="mono text-nowrap">{{ it.when | local_dt }}</td>
<td class="text-end text-nowrap">
<a href="{{ url_for('blancco_download_report', filename=it.file) }}" class="pxe-btn pxe-btn-secondary btn-row-action"><i class="bi bi-download"></i> XML</a>
<form method="post" action="{{ url_for('blancco_todo_mark', kind='cloud_resolved', key=it.document_id) }}" style="display:inline;">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="pxe-btn pxe-btn-success btn-row-action">Resolved</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">Every report with a known upload status reached the cloud.</p>
{% endif %}
</div>
{# 5. Hardware the server cannot image yet #}
<div class="section-card">
<div class="section-title">5. Add drivers / BIOS for new hardware</div>
{% if hardware %}
<p class="mb-2">These models booted WinPE here but the server has no Win11 driver pack or no BIOS entry for them.
Download the Dell driver pack and BIOS for the exact model below, add them, and they drop off this list.
<a href="{{ url_for('hardware_page') }}">Hardware coverage</a> shows every model.</p>
<div class="table-container">
<table class="data-table">
<thead><tr><th>Model (WMI)</th><th>System SKU</th><th>BIOS seen</th><th>Missing</th><th>Last seen</th><th class="text-end">Actions</th></tr></thead>
<tbody>
{% for r in hardware %}
<tr title="Serials: {{ r.serials | join(', ') }}">
<td class="mono">{{ r.model }}</td>
<td class="mono">{{ r.skus | join(', ') or '-' }}</td>
<td class="mono">{{ r.last_bios_version or '-' }}</td>
<td class="small wrap">
{% if r.missing %}Driver pack: {{ r.missing | join(', ') }}{% endif %}
{% if r.bios.status in ('none', 'file-missing') %}{% if r.missing %}<br>{% endif %}BIOS: {{ 'no manifest entry' if r.bios.status == 'none' else r.bios.file ~ ' not on server' }}{% endif %}
</td>
<td class="mono text-nowrap">{{ r.last_seen | local_dt }}</td>
<td class="text-end text-nowrap">
<button type="button" class="btn btn-sm btn-outline-secondary btn-row-action copy-btn" data-copy-text="{{ r.model }}">copy model</button>
<form method="post" action="{{ url_for('hardware_dismiss') }}" style="display:inline;">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="model" value="{{ r.model }}">
<input type="hidden" name="next" value="{{ url_for('blancco_todo') }}">
<button type="submit" class="pxe-btn pxe-btn-ghost btn-row-action">Dismiss</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">Every model seen has a Win11 driver pack and a BIOS entry.</p>
{% endif %}
</div>
{% endblock %}
{% block extra_scripts %}
<script>
document.querySelectorAll('.copy-btn').forEach(function (b) {
b.addEventListener('click', function () {
var t = b.getAttribute('data-copy-text') || '';
var done = function () { var o = b.textContent; b.textContent = 'copied'; setTimeout(function () { b.textContent = o; }, 1200); };
if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(t).then(done); return; }
var ta = document.createElement('textarea'); ta.value = t; document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); done(); } finally { document.body.removeChild(ta); }
});
});
</script>
{% endblock %}