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:
@@ -75,6 +75,12 @@
|
||||
<i class="bi bi-shield-check"></i> Blancco Reports
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'hardware_page' %}active{% endif %}"
|
||||
href="{{ url_for('hardware_page') }}">
|
||||
<i class="bi bi-cpu"></i> Hardware Coverage
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'blancco_todo' %}active{% endif %}"
|
||||
href="{{ url_for('blancco_todo') }}">
|
||||
@@ -145,6 +151,7 @@
|
||||
{% if blancco_todo.evidence %}{% set _ = parts.append(blancco_todo.evidence ~ ' evidence pack' ~ ('s' if blancco_todo.evidence != 1) ~ ' to upload to Box') %}{% endif %}
|
||||
{% if blancco_todo.failed %}{% set _ = parts.append(blancco_todo.failed ~ ' failed wipe' ~ ('s' if blancco_todo.failed != 1) ~ ' needing a Certificate of Destruction') %}{% endif %}
|
||||
{% if blancco_todo.not_in_cloud %}{% set _ = parts.append(blancco_todo.not_in_cloud ~ ' report' ~ ('s' if blancco_todo.not_in_cloud != 1) ~ ' not in the Blancco cloud') %}{% endif %}
|
||||
{% if blancco_todo.hardware %}{% set _ = parts.append(blancco_todo.hardware ~ ' new model' ~ ('s' if blancco_todo.hardware != 1) ~ ' missing drivers/BIOS') %}{% endif %}
|
||||
<span>{{ parts | join(' | ') }}</span>
|
||||
<a href="{{ url_for('blancco_todo') }}" class="ms-auto pxe-btn pxe-btn-secondary btn-row-action">Open to-do</a>
|
||||
</div>
|
||||
|
||||
81
webapp/templates/hardware.html
Normal file
81
webapp/templates/hardware.html
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Hardware Coverage - PXE Server Manager{% endblock %}
|
||||
|
||||
{% macro driver_badge(d) -%}
|
||||
{% if d.status == 'ok' %}<span class="badge badge-success" title="{{ d.file }}">OK</span>
|
||||
{% elif d.status == 'no-entry' %}<span class="badge badge-danger" title="No HardwareDriver.json entry matches this model">Missing</span>
|
||||
{% elif d.status == 'zip-missing' %}<span class="badge badge-danger" title="Entry {{ d.file }} matches, but the zip is not on the server">Zip missing</span>
|
||||
{% else %}<span class="text-muted">-</span>{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro bios_badge(b) -%}
|
||||
{% if b.status == 'current' %}<span class="badge badge-success">{{ b.target }} (current)</span>
|
||||
{% elif b.status == 'update' %}<span class="badge badge-info" title="{{ b.file }}">Flashes to {{ b.target }}</span>
|
||||
{% elif b.status == 'file-missing' %}<span class="badge badge-danger" title="{{ b.file }} not in the BIOS folder">File missing</span>
|
||||
{% else %}<span class="badge badge-danger">No BIOS entry</span>{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-cpu"></i> Hardware Coverage</h1>
|
||||
<div class="header-actions">
|
||||
<span class="badge badge-secondary badge-lg">{{ rows | length }} model{{ 's' if rows | length != 1 }} seen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted">
|
||||
Every model that has booted WinPE here, as reported by the machine itself (WMI model, Dell system SKU,
|
||||
BIOS version), checked against each image's driver map and the WinPE BIOS manifest. A model missing a
|
||||
Win11 driver pack or a BIOS entry also shows on the Technician To-Do until it is added or dismissed.
|
||||
</p>
|
||||
|
||||
<div class="section-card">
|
||||
{% if rows %}
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model (WMI)</th>
|
||||
<th>System SKU</th>
|
||||
<th>BIOS seen</th>
|
||||
{% for image, os_label in images %}<th title="{{ image }}">{{ image | replace('gea-', '') }} <span class="text-muted">({{ os_label }})</span></th>{% endfor %}
|
||||
<th>BIOS update</th>
|
||||
<th>Last seen</th>
|
||||
<th class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr title="Serials: {{ r.serials | join(', ') }}{% if r.pctypes %} | Types: {{ r.pctypes | join(', ') }}{% endif %}">
|
||||
<td>
|
||||
<span class="mono">{{ r.model }}</span>
|
||||
{% if r.manufacturer %}<div class="small text-muted">{{ r.manufacturer }}</div>{% endif %}
|
||||
</td>
|
||||
<td class="mono">{{ r.skus | join(', ') or '-' }}</td>
|
||||
<td class="mono">{{ r.last_bios_version or '-' }}</td>
|
||||
{% for image, os_label in images %}<td>{{ driver_badge(r.drivers[image]) }}</td>{% endfor %}
|
||||
<td>{{ bios_badge(r.bios) }}</td>
|
||||
<td class="mono text-nowrap">{{ r.last_seen | local_dt }}<div class="small text-muted">{{ r.count }} boot{{ 's' if r.count != 1 }}</div></td>
|
||||
<td class="text-end text-nowrap">
|
||||
{% if r.gap %}
|
||||
<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="done" value="{{ '0' if r.dismissed else '1' }}">
|
||||
<button type="submit" class="pxe-btn pxe-btn-ghost btn-row-action">{{ 'Reopen' if r.dismissed else 'Dismiss' }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<p class="mb-1">No machines have reported yet.</p>
|
||||
<p class="small">Models appear here the next time a machine boots WinPE from this server.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -117,4 +117,57 @@
|
||||
<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 %}
|
||||
|
||||
Reference in New Issue
Block a user