Webapp redesign phase 6: the remaining pages
Restyled to the same row pattern (one line, details in an expandable body, one action plus a menu, tokens for light and dark): - hardware coverage, audit log (family chips, destructive actions in red, client-side filters, first 300 rows), Clonezilla backups, enrollment packages (active package marked and undeletable, MCL flags with a visible legend) - image Overview, OS and packages, Unattend (sticky save bar), Import - Dell, iPXE, Blancco and BPRT update pages; Dell history links to the rollback review instead of rolling back on one click - boot menu editor (tabs, rows with a menu, lint chips that jump to the line, tokens instead of hard-coded dark colours) and the imaging session detail page Fixes found on the way: - BPRT batch Activate never asked to confirm: a hidden input named 'confirm' shadowed window.confirm in the inline handler, so the form submitted without a prompt. It now calls window.confirm. - Backup upload refused when a backup with that name exists, unless 'Replace the existing backup' is ticked (it overwrote silently). - app.js: em dashes in comments replaced (plain ASCII).
This commit is contained in:
@@ -1,213 +1,315 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Enrollment Packages - PXE Server Manager{% endblock %}
|
||||
{% import '_ui.html' as ui %}
|
||||
{% block title %}Enrollment packages - PXE Manager{% endblock %}
|
||||
|
||||
{# Enrollment packages, in sections: the active SFLD PPKG (ppkg.conf), the
|
||||
SFLD packages (the active one is marked and cannot be deleted), the MCL
|
||||
packages in _shared/Applications/BPRT with their duplicate/ignored/used
|
||||
flags, the MCL archive, and which images link to the shared BPRT. #}
|
||||
|
||||
{% set link_chip = {
|
||||
'ok': ('Linked', 'ok'),
|
||||
'copy': ('Own copy', 'err'),
|
||||
'elsewhere': ('Links elsewhere', 'err'),
|
||||
'none': ('No BPRT', 'off'),
|
||||
} %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.phead { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 18px 24px 12px; }
|
||||
.phead .sp { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
.ptext { padding: 0 24px 14px; font-size: 13px; color: var(--text-2); display: flex; flex-direction: column; gap: 8px; max-width: 90ch; }
|
||||
.ptext p { margin: 0; }
|
||||
.notice > span, .ptext { overflow-wrap: anywhere; min-width: 0; }
|
||||
.colhead { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); padding-top: 6px; padding-bottom: 6px; min-height: 0; }
|
||||
.when { font-size: 13px; color: var(--muted); white-space: nowrap; }
|
||||
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||||
.empty { padding: 22px 24px; color: var(--muted); border-top: 1px solid var(--line); font-size: 13px; }
|
||||
|
||||
/* Active PPKG */
|
||||
.active { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding: 18px 24px; }
|
||||
.active .file { font-size: 16px; font-weight: 600; overflow-wrap: anywhere; }
|
||||
|
||||
/* SFLD rows */
|
||||
.sgrid { display: grid; grid-template-columns: minmax(0, 1fr) 90px 190px; align-items: center; gap: 0 14px;
|
||||
padding: 11px 160px 11px 24px; font-size: 14px; min-height: 36px; }
|
||||
.s-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||||
.s-name .tip { flex: 0 1 auto; }
|
||||
|
||||
/* MCL rows */
|
||||
.mgrid { display: grid; grid-template-columns: 210px minmax(0, 1fr) 70px 80px 14px; align-items: center; gap: 0 14px;
|
||||
padding: 11px 72px 11px 24px; font-size: 14px; min-height: 24px; }
|
||||
.m-pre { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
||||
.m-pre .tip { flex: 0 1 auto; }
|
||||
.flags { display: flex; gap: 6px; flex-wrap: nowrap; }
|
||||
.mbody { padding: 2px 72px 14px 24px; font-size: 13px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.mfacts { display: flex; flex-wrap: wrap; gap: 4px 20px; color: var(--muted); font-size: 12px; }
|
||||
.mfacts .mono { overflow-wrap: anywhere; color: var(--text-2); }
|
||||
.legend { display: flex; flex-direction: column; gap: 6px; padding: 0 24px 14px; font-size: 13px; }
|
||||
.legend div { display: flex; gap: 8px; align-items: baseline; }
|
||||
.upform { display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: flex-end; padding: 0 24px 18px; }
|
||||
.upform .form-field { min-width: min(22rem, 100%); flex: 0 1 28rem; margin: 0; }
|
||||
|
||||
/* Archive rows */
|
||||
.agrid { display: grid; grid-template-columns: 150px minmax(0, 1fr) 70px 80px; align-items: center; gap: 0 14px;
|
||||
padding: 11px 130px 11px 24px; font-size: 14px; min-height: 36px; }
|
||||
|
||||
/* Image links */
|
||||
.lgrid { display: grid; grid-template-columns: 170px 130px minmax(0, 1fr); align-items: center; gap: 4px 14px; padding: 11px 24px; font-size: 14px; }
|
||||
.lgrid .mono { overflow-wrap: anywhere; font-size: 13px; }
|
||||
|
||||
@container (max-width: 760px) {
|
||||
.colhead { display: none; }
|
||||
.sgrid { grid-template-columns: minmax(0, 1fr) auto; padding: 12px 16px 4px; gap: 2px 12px; }
|
||||
.sgrid .when { grid-column: 1 / -1; }
|
||||
.mgrid { grid-template-columns: minmax(0, 1fr) auto 14px; padding: 12px 64px 12px 16px; gap: 4px 10px; }
|
||||
.mgrid .m-file { grid-column: 1 / -1; grid-row: 2; }
|
||||
.mgrid .m-size { display: none; }
|
||||
.mbody { padding: 2px 16px 12px; }
|
||||
.agrid { grid-template-columns: minmax(0, 1fr) auto; padding: 12px 16px 4px; gap: 2px 12px; }
|
||||
.agrid .a-file { grid-column: 1 / -1; }
|
||||
.agrid .a-size { display: none; }
|
||||
.ui-row > .act.wide { position: static; display: flex; justify-content: flex-end; padding: 0 16px 10px; }
|
||||
.lgrid { grid-template-columns: minmax(0, 1fr) auto; padding: 11px 16px; }
|
||||
.lgrid .mono, .lgrid .lnote { grid-column: 1 / -1; }
|
||||
.phead, .active { padding-left: 16px; padding-right: 16px; }
|
||||
.ptext, .legend, .upform { padding-left: 16px; padding-right: 16px; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-pc-display"></i> Enrollment Packages</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#uploadModal">
|
||||
<i class="bi bi-upload"></i> Upload Package
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% call ui.head('Enrollment packages', 'Tools', 'Shop floor (SFLD) and Standard / Engineer (MCL) provisioning packages.') %}
|
||||
<button type="button" class="ui-btn pri" data-bs-toggle="modal" data-bs-target="#uploadModal">Upload SFLD package</button>
|
||||
{% endcall %}
|
||||
|
||||
{% if active_ppkg and active_ppkg.SOURCE_PPKG %}
|
||||
<div class="alert alert-info">
|
||||
<strong>Active PPKG:</strong> <span class="mono">{{ active_ppkg.SOURCE_PPKG }}</span>
|
||||
<span class="badge badge-info">{{ active_ppkg.PPKG_VER }}</span>
|
||||
{% if active_ppkg.PPKG_EXP %}<span class="badge badge-secondary">exp {{ active_ppkg.PPKG_EXP }}</span>{% endif %}
|
||||
<span class="form-text">- startnet.cmd boots with this (from <span class="mono">ppkg.conf</span>).</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="ui-stack">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span><i class="bi bi-box-seam"></i> Shop Floor (SFLD) Provisioning Packages</span>
|
||||
<span class="badge badge-secondary">{{ packages|length }}</span>
|
||||
{# ---------- Active PPKG ---------- #}
|
||||
<section class="panel" aria-labelledby="act-h">
|
||||
<div class="phead" style="padding-bottom: 0"><h2 id="act-h" class="sec">Active SFLD package</h2></div>
|
||||
{% if active_ppkg and active_ppkg.SOURCE_PPKG %}
|
||||
<div class="active">
|
||||
<span class="file mono">{{ active_ppkg.SOURCE_PPKG }}</span>
|
||||
{% if active_ppkg.PPKG_VER %}{{ ui.chip(active_ppkg.PPKG_VER, 'run') }}{% endif %}
|
||||
{% if active_ppkg.PPKG_EXP %}{{ ui.chip('Expires ' ~ active_ppkg.PPKG_EXP, 'neutral') }}{% endif %}
|
||||
<span class="sub" style="flex-basis: 100%">startnet.cmd boots with this (from <span class="mono">ppkg.conf</span> on the enrollment share).</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if packages %}
|
||||
<div class="table-container">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
<th>Last Modified</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in packages %}
|
||||
{% set is_active = active_ppkg and active_ppkg.SOURCE_PPKG and p.filename|lower == active_ppkg.SOURCE_PPKG|lower %}
|
||||
<tr>
|
||||
<td><span class="mono"><i class="bi bi-file-earmark-zip"></i> {{ p.filename }}</span>{% if is_active %} <span class="badge badge-info">Active</span>{% endif %}</td>
|
||||
<td>{{ "%.1f"|format(p.size / 1048576) }} MB</td>
|
||||
<td>{{ p.modified | timestamp_fmt }}</td>
|
||||
<td class="actions text-end text-nowrap">
|
||||
<a href="{{ url_for('enrollment_download', filename=p.filename) }}"
|
||||
class="btn btn-sm btn-outline-primary btn-row-action" title="Download">
|
||||
<i class="bi bi-download"></i> Download
|
||||
</a>
|
||||
{% if is_active %}
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary btn-row-action" disabled>
|
||||
<i class="bi bi-lock"></i> Active
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-row-action"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-filename="{{ p.filename }}" title="Delete">
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="active"><div class="notice" style="flex: 1"><span aria-hidden="true" style="font-weight: 700">!</span><span>No active package is set in <span class="mono">ppkg.conf</span>. Upload a standard <span class="mono">GCCH_Prod_SFLD_v<ver>.ppkg</span> to set one.</span></div></div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{# ---------- SFLD packages ---------- #}
|
||||
<section class="panel ui-list" aria-labelledby="sfld-h">
|
||||
<div class="phead">
|
||||
<h2 id="sfld-h" class="sec">Shop floor (SFLD) packages</h2>
|
||||
<span class="sub">{{ packages | length }}</span>
|
||||
</div>
|
||||
{% if packages %}
|
||||
<div class="sgrid colhead" aria-hidden="true"><span>File</span><span class="num">Size</span><span class="when">Modified</span></div>
|
||||
{% for p in packages %}
|
||||
{% set is_active = active_ppkg and active_ppkg.SOURCE_PPKG and p.filename|lower == active_ppkg.SOURCE_PPKG|lower %}
|
||||
<div class="ui-row">
|
||||
<div class="sgrid">
|
||||
<span class="s-name">
|
||||
<span class="tip" data-tip="{{ p.filename }}"><span class="trunc mono">{{ p.filename }}</span></span>
|
||||
{% if is_active %}{{ ui.chip('Active', 'ok') }}{% endif %}
|
||||
</span>
|
||||
<span class="num">{{ "%.1f"|format(p.size / 1048576) }} MB</span>
|
||||
<span class="when mono">{{ p.modified | timestamp_fmt }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-inbox" style="font-size: 2.5rem; opacity: 0.5;"></i>
|
||||
<p class="mt-2 mb-0">No enrollment packages found. Upload a <span class="mono">.ppkg</span> file to get started.</p>
|
||||
<div class="act wide">
|
||||
<a class="ui-btn" href="{{ url_for('enrollment_download', filename=p.filename) }}">Download</a>
|
||||
{% call ui.more(p.filename) %}
|
||||
<button type="button" class="copy-btn" data-copy-text="{{ p.filename }}">Copy file name</button>
|
||||
<div class="sep"></div>
|
||||
{% if is_active %}
|
||||
<button type="button" disabled>Active: cannot delete</button>
|
||||
{% else %}
|
||||
<button type="button" class="del" data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-filename="{{ p.filename }}">Delete package...</button>
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty">No enrollment packages found. Upload a <span class="mono">.ppkg</span> file to get started.</div>
|
||||
{% endif %}
|
||||
<details class="hidden-bar" style="display: block">
|
||||
<summary class="disc" style="min-height: 28px">About SFLD packages <span class="chev" aria-hidden="true">▾</span></summary>
|
||||
<div class="ptext" style="padding: 6px 0 4px">
|
||||
<p>GCCH enrollment <span class="mono">.ppkg</span> provisioning packages are copied to
|
||||
<span class="mono">C:\Enrollment\</span> on the target machine after imaging. At OOBE, connect to a network with
|
||||
internet, press <strong>Windows key 5 times</strong>, then browse to <span class="mono">C:\Enrollment\</span> and
|
||||
select the package. No USB stick needed.</p>
|
||||
<p><strong>Naming:</strong> use <span class="mono">with-office.ppkg</span> and <span class="mono">without-office.ppkg</span>
|
||||
to match the WinPE enrollment menu options.</p>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<div class="pxe-card">
|
||||
<h3 class="pxe-card-title"><i class="bi bi-info-circle"></i> About Enrollment Packages</h3>
|
||||
<p>
|
||||
GCCH enrollment <span class="mono">.ppkg</span> provisioning packages are copied to
|
||||
<span class="mono">C:\Enrollment\</span> on the target machine after imaging.
|
||||
At OOBE, connect to a network with internet, press <strong>Windows key 5 times</strong>,
|
||||
then browse to <span class="mono">C:\Enrollment\</span> and select the package. No USB stick needed.
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<strong>Naming convention:</strong> Use <span class="mono">with-office.ppkg</span> and
|
||||
<span class="mono">without-office.ppkg</span> to match the WinPE enrollment menu options.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="mcl">
|
||||
<div class="card-header">
|
||||
<span><i class="bi bi-box-seam"></i> Standard / Engineer (MCL) Provisioning Packages</span>
|
||||
<span class="badge badge-secondary">{{ mcl_packages|length }}</span>
|
||||
{# ---------- MCL packages ---------- #}
|
||||
{% set mcl_users = mcl_links | selectattr('state', 'equalto', 'ok') | map(attribute='image') | list %}
|
||||
{% set any_dup = mcl_packages | selectattr('conflict') | list | length %}
|
||||
{% set any_ign = mcl_packages | rejectattr('prefix') | list | length %}
|
||||
{% set any_used = mcl_packages | selectattr('prefix') | rejectattr('placeholder') | list | length %}
|
||||
<section class="panel ui-list" id="mcl" aria-labelledby="mcl-h">
|
||||
<div class="phead">
|
||||
<h2 id="mcl-h" class="sec">Standard / Engineer (MCL) packages</h2>
|
||||
<span class="sub">{{ mcl_packages | length }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% set mcl_users = mcl_links | selectattr('state', 'equalto', 'ok') | map(attribute='image') | list %}
|
||||
<p class="text-muted">
|
||||
Packages live in <span class="mono">_shared/Applications/BPRT</span>, which {{ mcl_users | join(', ') if mcl_users else 'no image' }} link{{ 's' if mcl_users|length == 1 }} to.
|
||||
The bulk-enroll scripts pick the package by its first three name fields (tenant, environment, purpose) and fail
|
||||
enrollment if two match, so an upload <strong>replaces</strong> the package with the same fields; the old one moves
|
||||
to the archive below and can be restored. Every PC built from those images copies this folder while it images, so
|
||||
upload, archive or restore only when no build is in progress.
|
||||
</p>
|
||||
<p class="text-muted">
|
||||
<i class="bi bi-arrow-left-right"></i> A whole new vendor drop (packages, Office zips and scripts from MediaCreator Lite)
|
||||
goes through <a href="/updates/bprt/">BPRT Sync</a> instead: it uploads only the changed files from the work PC, checks
|
||||
them and swaps the folder in one step, keeping the site's own scripts.
|
||||
</p>
|
||||
{% for l in mcl_links if l.state in ('copy', 'elsewhere') %}
|
||||
<div class="alert alert-warning py-2 mb-2">
|
||||
<div class="ptext">
|
||||
<p>Packages live in <span class="mono">_shared/Applications/BPRT</span>, which {{ mcl_users | join(', ') if mcl_users else 'no image' }}
|
||||
link{{ 's' if mcl_users|length == 1 }} to. The bulk-enroll scripts pick the package by its first three name fields
|
||||
(tenant, environment, purpose) and fail enrollment if two match, so an upload <strong>replaces</strong> the package with
|
||||
the same fields; the old one moves to the archive below and can be restored. Every PC built from those images copies
|
||||
this folder while it images, so upload, archive or restore only when no build is in progress.</p>
|
||||
<p>A whole new vendor drop (packages, Office zips and scripts from MediaCreator Lite) goes through
|
||||
<a href="/updates/bprt/">BPRT Sync</a> instead: it uploads only the changed files from the work PC, checks them and
|
||||
swaps the folder in one step, keeping the site's own scripts.</p>
|
||||
</div>
|
||||
{% for l in mcl_links if l.state in ('copy', 'elsewhere') %}
|
||||
<div style="padding: 0 24px 10px">
|
||||
<div class="notice"><span aria-hidden="true" style="font-weight: 700">!</span><span>
|
||||
<strong>{{ l.image }}</strong>
|
||||
{% if l.state == 'copy' %}has its own copy of BPRT (<span class="mono">{{ l.path }}</span> is a real folder, not a link to
|
||||
<span class="mono">_shared/Applications/BPRT</span>; an image import does this),
|
||||
{% else %}links BPRT to <span class="mono">{{ l.target or '?' }}</span> instead of <span class="mono">_shared/Applications/BPRT</span>,
|
||||
{% endif %}so packages changed here do not reach its PCs. Move that folder aside and link it back to the shared one.
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form action="{{ url_for('mcl_ppkg_upload') }}" method="post" enctype="multipart/form-data" class="d-flex flex-wrap gap-2 align-items-end mb-3">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-field mb-0" style="min-width: min(22rem, 100%);">
|
||||
<label for="mclFile" class="form-label">Package (.ppkg)</label>
|
||||
<input type="file" class="form-control" id="mclFile" name="ppkg_file" accept=".ppkg" required>
|
||||
<div class="form-text">Vendor name, unchanged, e.g. <span class="mono">GCCH_Prod_MCL-PERS_Exp_XXXXXXXX_v2.19.ppkg</span></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-upload"></i> Upload and replace</button>
|
||||
</form>
|
||||
{% if mcl_packages %}
|
||||
<div class="table-container">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr><th>Tenant / purpose</th><th>Filename</th><th>Version</th><th>Size</th><th>Last Modified</th><th class="text-end"></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in mcl_packages %}
|
||||
<tr>
|
||||
<td class="mono">{{ p.prefix or '-' }}
|
||||
{% if p.conflict %}<span class="badge badge-danger" title="This prefix also matches {{ p.conflict_with | join(', ') }}; enrollment for it will fail">Duplicate</span>{% endif %}
|
||||
{% if not p.prefix %}<span class="badge badge-warning" title="Name does not match the vendor pattern; the enroll scripts ignore it">Ignored</span>
|
||||
{% elif not p.placeholder %}<span class="badge badge-warning" title="No Exp_XXXXXXXX placeholder in the name: this looks like a package already used on a PC, and the enroll script cannot inject a new token into it">Used</span>{% endif %}
|
||||
</td>
|
||||
<td class="mono">{{ p.filename }}</td>
|
||||
<td><span class="badge badge-info">{{ p.version or '?' }}</span></td>
|
||||
<td>{{ "%.1f"|format(p.size / 1048576) }} MB</td>
|
||||
<td>{{ p.modified | timestamp_fmt }}</td>
|
||||
<td class="text-end">
|
||||
<form action="{{ url_for('mcl_ppkg_archive') }}" method="post" style="display:inline;"
|
||||
onsubmit="return confirm('Move this package to the archive? Enrollment for its tenant and purpose fails until another package is uploaded or restored.');">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="filename" value="{{ p.filename }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger btn-row-action" title="Move to the archive"><i class="bi bi-archive"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">No MCL packages found in BPRT.</p>
|
||||
{% endif %}
|
||||
{% if mcl_archive %}
|
||||
<h6 class="mt-4">Archive</h6>
|
||||
<div class="table-container">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead><tr><th>Archived</th><th>Filename</th><th>Version</th><th>Size</th><th class="text-end"></th></tr></thead>
|
||||
<tbody>
|
||||
{% for p in mcl_archive %}
|
||||
<tr>
|
||||
<td class="mono">{{ p.batch }}</td>
|
||||
<td class="mono">{{ p.filename }}</td>
|
||||
<td>{{ p.version or '?' }}</td>
|
||||
<td>{{ "%.1f"|format(p.size / 1048576) }} MB</td>
|
||||
<td class="text-end">
|
||||
<form action="{{ url_for('mcl_ppkg_restore') }}" method="post" style="display:inline;"
|
||||
onsubmit="return confirm('Restore this package? The current package for the same tenant and purpose moves to the archive.');">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="batch" value="{{ p.batch }}">
|
||||
<input type="hidden" name="filename" value="{{ p.filename }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary btn-row-action"><i class="bi bi-arrow-counterclockwise"></i> Restore</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</span></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form action="{{ url_for('mcl_ppkg_upload') }}" method="post" enctype="multipart/form-data" class="upform">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-field">
|
||||
<label for="mclFile" class="form-label">Package (.ppkg)</label>
|
||||
<input type="file" class="form-control" id="mclFile" name="ppkg_file" accept=".ppkg" required>
|
||||
<div class="form-text">Vendor name, unchanged, e.g. <span class="mono">GCCH_Prod_MCL-PERS_Exp_XXXXXXXX_v2.19.ppkg</span></div>
|
||||
</div>
|
||||
<button type="submit" class="ui-btn pri" style="margin-bottom: 22px">Upload and replace</button>
|
||||
</form>
|
||||
|
||||
{% if any_dup or any_ign or any_used %}
|
||||
<div class="legend" aria-label="What the flags mean">
|
||||
{% if any_dup %}<div>{{ ui.chip('Duplicate', 'err') }}<span>Its prefix also matches another package: enrollment for that tenant and purpose fails.</span></div>{% endif %}
|
||||
{% if any_ign %}<div>{{ ui.chip('Ignored', 'warn') }}<span>The name does not match the vendor pattern, so the enroll scripts ignore it.</span></div>{% endif %}
|
||||
{% if any_used %}<div>{{ ui.chip('Used', 'warn') }}<span>No <span class="mono">Exp_XXXXXXXX</span> placeholder in the name: it looks already used on a PC, and the enroll script cannot inject a new token into it.</span></div>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if mcl_packages %}
|
||||
<div class="mgrid colhead" aria-hidden="true"><span>Tenant / purpose</span><span class="m-file">File</span><span>Version</span><span class="num m-size">Size</span><span></span></div>
|
||||
{% for p in mcl_packages %}
|
||||
<div class="ui-row">
|
||||
<details>
|
||||
<summary class="mgrid">
|
||||
<span class="m-pre">
|
||||
<span class="tip" data-tip="{{ p.prefix or 'No prefix' }}"><span class="trunc mono">{{ p.prefix or '-' }}</span></span>
|
||||
<span class="flags">
|
||||
{% if p.conflict %}{{ ui.chip('Duplicate', 'err') }}{% endif %}
|
||||
{% if not p.prefix %}{{ ui.chip('Ignored', 'warn') }}{% elif not p.placeholder %}{{ ui.chip('Used', 'warn') }}{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
<span class="m-file tip" data-tip="{{ p.filename }}"><span class="trunc mono sub">{{ p.filename }}</span></span>
|
||||
<span>{{ ui.chip(p.version or '?', 'neutral') }}</span>
|
||||
<span class="num m-size">{{ "%.1f"|format(p.size / 1048576) }} MB</span>
|
||||
<span class="chev" aria-hidden="true">▾</span>
|
||||
</summary>
|
||||
<div class="mbody">
|
||||
{% if p.conflict %}<div class="notice bad"><span aria-hidden="true" style="font-weight: 700">!</span><span>This prefix also matches {{ p.conflict_with | join(', ') }}; enrollment for it will fail.</span></div>{% endif %}
|
||||
{% if not p.prefix %}<div class="notice"><span>Name does not match the vendor pattern; the enroll scripts ignore it.</span></div>
|
||||
{% elif not p.placeholder %}<div class="notice"><span>No <span class="mono">Exp_XXXXXXXX</span> placeholder in the name: this looks like a package already used on a PC, and the enroll script cannot inject a new token into it.</span></div>{% endif %}
|
||||
<div class="mfacts"><span>File <span class="mono">{{ p.filename }}</span></span><span>{{ "%.1f"|format(p.size / 1048576) }} MB</span><span>Modified {{ p.modified | timestamp_fmt }}</span></div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="act">
|
||||
{% call ui.more(p.filename) %}
|
||||
<button type="button" class="copy-btn" data-copy-text="{{ p.filename }}">Copy file name</button>
|
||||
<div class="sep"></div>
|
||||
<form action="{{ url_for('mcl_ppkg_archive') }}" method="post"
|
||||
onsubmit="return confirm('Move this package to the archive? Enrollment for its tenant and purpose fails until another package is uploaded or restored.');">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="filename" value="{{ p.filename }}">
|
||||
<button type="submit" class="del">Move to archive...</button>
|
||||
</form>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty">No MCL packages found in BPRT.</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{# ---------- MCL archive ---------- #}
|
||||
{% if mcl_archive %}
|
||||
<section class="panel ui-list" aria-labelledby="arc-h">
|
||||
<div class="phead">
|
||||
<h2 id="arc-h" class="sec">MCL archive</h2>
|
||||
<span class="sub">{{ mcl_archive | length }}</span>
|
||||
</div>
|
||||
<div class="agrid colhead" aria-hidden="true"><span>Archived</span><span class="a-file">File</span><span>Version</span><span class="num a-size">Size</span></div>
|
||||
{% for p in mcl_archive %}
|
||||
<div class="ui-row">
|
||||
<div class="agrid">
|
||||
<span class="mono when">{{ p.batch }}</span>
|
||||
<span class="a-file tip" data-tip="{{ p.filename }}"><span class="trunc mono sub">{{ p.filename }}</span></span>
|
||||
<span>{{ ui.chip(p.version or '?', 'neutral') }}</span>
|
||||
<span class="num a-size">{{ "%.1f"|format(p.size / 1048576) }} MB</span>
|
||||
</div>
|
||||
<div class="act wide">
|
||||
<form action="{{ url_for('mcl_ppkg_restore') }}" method="post" style="margin: 0"
|
||||
onsubmit="return confirm('Restore this package? The current package for the same tenant and purpose moves to the archive.');">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="batch" value="{{ p.batch }}">
|
||||
<input type="hidden" name="filename" value="{{ p.filename }}">
|
||||
<button type="submit" class="ui-btn">Restore</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{# ---------- Image links ---------- #}
|
||||
{% if mcl_links %}
|
||||
<section class="panel ui-list" aria-labelledby="lnk-h">
|
||||
<div class="phead">
|
||||
<h2 id="lnk-h" class="sec">Image links to the shared BPRT</h2>
|
||||
</div>
|
||||
{% for l in mcl_links %}
|
||||
{% set c = link_chip.get(l.state, (l.state, 'neutral')) %}
|
||||
<div class="ui-row lgrid">
|
||||
<span>{{ l.image }}</span>
|
||||
<span>{{ ui.chip(c[0], c[1]) }}</span>
|
||||
<span class="mono">{% if l.state == 'ok' %}{{ l.path }}{% elif l.state == 'elsewhere' %}{{ l.path }} links to {{ l.target or '?' }}{% elif l.state == 'copy' %}{{ l.path }} is a real folder{% else %}<span class="sub">No Deploy/Applications/BPRT</span>{% endif %}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Upload Modal -->
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1">
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" aria-labelledby="uploadTitle">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('enrollment_upload') }}" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Upload Enrollment Package</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<h3 class="modal-title" id="uploadTitle">Upload SFLD package</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-field">
|
||||
<label for="ppkgFile" class="form-label">Provisioning Package (.ppkg)</label>
|
||||
<label for="ppkgFile" class="form-label">Provisioning package (.ppkg)</label>
|
||||
<input type="file" class="form-control" id="ppkgFile" name="ppkg_file"
|
||||
accept=".ppkg" required>
|
||||
<div class="form-text">
|
||||
A standard <span class="mono">GCCH_Prod_SFLD_v<ver>.ppkg</span> upload is set as the active PPKG automatically
|
||||
(written to <span class="mono">ppkg.conf</span> on the share; startnet.cmd reads it at boot - no boot.wim edit).
|
||||
(written to <span class="mono">ppkg.conf</span> on the share; startnet.cmd reads it at boot, no boot.wim edit).
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
@@ -218,8 +320,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-upload"></i> Upload</button>
|
||||
<button type="button" class="ui-btn" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="ui-btn pri">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -227,22 +329,22 @@
|
||||
</div>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteTitle">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form id="deleteForm" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Confirm Delete</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<h3 class="modal-title" id="deleteTitle">Delete package</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete <strong id="deleteFilename" class="mono"></strong>?</p>
|
||||
<p class="text-muted mb-0">This action cannot be undone.</p>
|
||||
<p class="muted mb-0">This action cannot be undone.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger"><i class="bi bi-trash"></i> Delete</button>
|
||||
<button type="button" class="ui-btn pri" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="ui-btn danger">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user