Updates group: - Dell drivers and BIOS: check Dell's catalogs against all three driver maps and models.txt, download with a shared speed cap, repack, preview, apply with backups and rollback. - iPXE boot files: check GitHub for the signed Secure Boot set, verify the sha256 and each file's signer, install atomically, roll back. Includes the by-hand procedure. - Blancco: upload a release, stage it, test-boot it from the menu, activate, roll back. - BPRT sync: validate a batch uploaded from the MediaCreator Lite cache (scripts/windows/Sync-BPRT.ps1 or a zip), show the diff, swap with the site overlay files kept. Images: one sidebar entry per image with Overview, Drivers, Unattend and OS and Packages tabs. Every save shows a diff first, keeps the file's format, makes a backup and can be undone. Driver lint runs inline. Enrollment: upload standard/engineer MCL packages, one per tenant/purpose, with archive and restore. Hardware coverage counts engineer as Win11.
151 lines
7.9 KiB
HTML
151 lines
7.9 KiB
HTML
{#
|
|
_image_header.html - shared header + tabs for every GE Aerospace image page.
|
|
|
|
CONTRACT (image-shell; used by the Overview, Drivers, Unattend and OS and
|
|
Packages tabs):
|
|
|
|
Use it as the first thing inside {% block content %}:
|
|
|
|
{% set header_actions %}
|
|
<button type="button" class="btn btn-success btn-sm" id="...">Save</button>
|
|
{% endset %}
|
|
{% set header_subtitle = 'Deploy/FlatUnattendW10.xml' %}
|
|
{% include "_image_header.html" %}
|
|
|
|
Variables it reads from the template context:
|
|
image_type required. Image key, e.g. "gea-standard".
|
|
active_tab optional. One of "overview", "drivers", "unattend", "os".
|
|
When missing it is worked out from request.endpoint
|
|
(image_tab_for_endpoint), so a route need not pass it.
|
|
header_actions optional. HTML for the right side of the title row
|
|
(Save / Review / Undo buttons). Build it with a
|
|
{% set header_actions %}...{% endset %} block.
|
|
header_subtitle optional. Plain text appended to the meta line
|
|
(file path, last save time, ...).
|
|
|
|
Globals it uses (context processor in app.py):
|
|
image_tabs services/image_pages.IMAGE_TABS: id, label, icon,
|
|
endpoint for each tab, in display order.
|
|
image_tab_url(t, tab) URL of a tab; use it for links and redirects
|
|
(in Python: services.image_pages.tab_url).
|
|
image_header_info(t) friendly_name, os_label, driver_count,
|
|
package_count, has_unattend.
|
|
|
|
Tab endpoints (change a row of IMAGE_TABS if a page moves):
|
|
overview images.overview GET /images/<t> views/images.py
|
|
drivers images_drivers.page GET /images/<t>/drivers views/images_drivers.py
|
|
unattend unattend_editor GET /images/<t>/unattend app.py
|
|
os images.os_packages GET /images/<t>/os views/images.py
|
|
Old URLs: /images/<t>/config redirects to the Overview (or ?tab=<id>).
|
|
|
|
The sidebar marks an image active whenever active_tab is set (or the
|
|
endpoint maps to a tab) and image_type matches, so any page that includes
|
|
this partial highlights the right sidebar entry.
|
|
|
|
It also loads static/image-pages.js and the shared review modal
|
|
(#imageReviewModal). For a "Review changes" step call
|
|
PxeImage.reviewAndSave({previewUrl, saveUrl, body, title})
|
|
where previewUrl answers {"files": [control_files.public(plan), ...]}
|
|
and saveUrl takes the same body plus "expect": {name: sha_before} and
|
|
answers {"ok": true} (it flashes; the page reloads) or {"error": "..."}.
|
|
PxeImage.renderDiff(el, files) draws the same diff anywhere.
|
|
#}
|
|
{% set _tab = active_tab if active_tab is defined and active_tab else image_tab_for_endpoint(request.endpoint) %}
|
|
{% set _hdr = image_header_info(image_type) %}
|
|
<style>
|
|
.image-head { margin-bottom: 1.25rem; }
|
|
.image-head-top {
|
|
display: flex; flex-wrap: wrap; gap: 0.75rem 1rem;
|
|
justify-content: space-between; align-items: flex-end;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
.image-head-title { flex: 1 1 20rem; min-width: 0; }
|
|
.image-head h1 { font-size: 20px; font-weight: 500; line-height: 1.3; margin: 0.15rem 0 0.2rem; }
|
|
.image-head-meta { font-size: 13px; color: var(--text-light); display: flex; flex-wrap: wrap; gap: 0.25rem 0.6rem; align-items: center; }
|
|
.image-head-meta .sep { opacity: 0.5; }
|
|
.image-head .header-actions { flex-wrap: wrap; align-items: center; }
|
|
.image-tabs.nav-tabs { flex-wrap: wrap; }
|
|
.image-tabs .nav-link { font-size: 12px; padding: 0.65rem 1rem; display: flex; align-items: center; gap: 0.4rem; }
|
|
.image-tabs .nav-link .bi { font-size: 14px; }
|
|
.image-tabs .tab-count {
|
|
font-size: 10px; font-weight: 600; letter-spacing: 0; line-height: 1;
|
|
padding: 2px 7px; border-radius: 999px;
|
|
background: var(--bg); border: 1px solid var(--border); color: var(--text-light);
|
|
}
|
|
.image-tabs .nav-link.active .tab-count { color: var(--primary); border-color: var(--primary); }
|
|
|
|
/* Unified diff (review step of every save) */
|
|
.diff-file { border: 1px solid var(--border); border-radius: 0.25rem; margin-bottom: 1rem; overflow: hidden; }
|
|
.diff-file-head { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); font-weight: 600; font-size: 13px; }
|
|
.diff-view {
|
|
margin: 0; max-height: 55vh; overflow: auto; font-size: 12px; line-height: 1.45;
|
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; background: var(--bg-card-solid);
|
|
}
|
|
.diff-view span { display: block; padding: 0 0.75rem; white-space: pre-wrap; word-break: break-all; }
|
|
.diff-view .d-add { background: rgba(10, 214, 79, 0.16); }
|
|
.diff-view .d-del { background: rgba(220, 53, 69, 0.14); }
|
|
.diff-view .d-hunk { color: var(--primary); background: rgba(65, 129, 255, 0.08); }
|
|
.diff-view .d-meta { color: var(--text-light); }
|
|
[data-theme="dark"] .diff-view .d-add { background: rgba(10, 214, 79, 0.18); }
|
|
[data-theme="dark"] .diff-view .d-del { background: rgba(245, 54, 92, 0.2); }
|
|
</style>
|
|
|
|
<div class="image-head">
|
|
<div class="image-head-top">
|
|
<div class="image-head-title">
|
|
<div class="eyebrow">GE Aerospace image</div>
|
|
<h1>{{ _hdr.friendly_name }}</h1>
|
|
<div class="image-head-meta">
|
|
<span class="mono">{{ image_type }}</span>
|
|
{% if _hdr.os_label %}<span class="sep">|</span><span><i class="bi bi-windows"></i> {{ _hdr.os_label }}</span>{% endif %}
|
|
{% if header_subtitle is defined and header_subtitle %}<span class="sep">|</span><span class="mono">{{ header_subtitle }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if header_actions is defined and header_actions %}
|
|
<div class="header-actions">{{ header_actions }}</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<ul class="nav nav-tabs image-tabs" role="tablist">
|
|
{% for t in image_tabs %}
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if t.id == _tab %}active{% endif %}"
|
|
{% if t.id == _tab %}aria-current="page"{% endif %}
|
|
href="{{ image_tab_url(image_type, t.id) }}">
|
|
<i class="bi {{ t.icon }}"></i> {{ t.label }}
|
|
{% if t.id == 'drivers' %}<span class="tab-count" title="Entries in HardwareDriver.json">{{ _hdr.driver_count }}</span>
|
|
{% elif t.id == 'os' and _hdr.package_count %}<span class="tab-count" title="Entries in packages.json">{{ _hdr.package_count }}</span>
|
|
{% elif t.id == 'unattend' and not _hdr.has_unattend %}<span class="tab-count" title="FlatUnattendW10.xml is missing">missing</span>{% endif %}
|
|
</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
{# Shared review step: filled by PxeImage.reviewAndSave (static/image-pages.js) #}
|
|
<div class="modal fade" id="imageReviewModal" tabindex="-1" aria-labelledby="imageReviewTitle">
|
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title" id="imageReviewTitle">Review changes</h3>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted small mb-3" id="imageReviewIntro">
|
|
This is exactly what will be written. Each changed file is backed up next to itself
|
|
(<span class="mono">.bak-<date>-webui</span>) first, and the save can be undone from this page.
|
|
</p>
|
|
<div id="imageReviewBody"></div>
|
|
<div class="alert alert-danger d-none mb-0" id="imageReviewError"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-success" id="imageReviewConfirm" disabled>
|
|
<i class="bi bi-check-lg"></i> Save
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="{{ url_for('static', filename='image-pages.js') }}"></script>
|