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).
178 lines
9.0 KiB
HTML
178 lines
9.0 KiB
HTML
{% extends "base.html" %}
|
|
{% import '_ui.html' as ui %}
|
|
{% block title %}Image Import - PXE Server Manager{% endblock %}
|
|
|
|
{# Image import: two panels (network upload folder, USB drive), then the
|
|
images one line each. Submitting asks for confirmation (upload-folder
|
|
sources are MOVED, same-name files are overwritten) and then disables the
|
|
button with a spinner while the import runs. #}
|
|
|
|
{% block extra_head %}
|
|
<style>
|
|
.imp-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); gap: 22px; align-items: start; }
|
|
.imp-panel { padding: 20px 24px 22px; display: flex; flex-direction: column; gap: 16px; }
|
|
.imp-panel h2 { margin: 0; font-size: 16px; font-weight: 600; }
|
|
.imp-panel .lead { margin: 2px 0 0; font-size: 13px; color: var(--muted); }
|
|
.imp-panel form { display: flex; flex-direction: column; gap: 16px; margin: 0; }
|
|
.imp-field { display: flex; flex-direction: column; gap: 6px; }
|
|
.imp-field label { font-size: 13px; font-weight: 600; color: var(--text-2); }
|
|
.imp-field .field { width: 100%; }
|
|
.imp-field .sub { line-height: 1.5; }
|
|
.imp-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
|
.imp-empty { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; font-size: 14px; color: var(--text-2); line-height: 1.6; }
|
|
.imp-empty p { margin: 0; }
|
|
|
|
.ig { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) 92px 112px auto; align-items: center; gap: 0 14px;
|
|
padding: 11px 24px; font-size: 14px; border-top: 1px solid var(--line); }
|
|
.ig.ihead { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); padding-top: 6px; padding-bottom: 6px; border-top: 0; }
|
|
.ig .name { font-weight: 600; }
|
|
.ig a.ui-btn { justify-self: end; }
|
|
.imp-phead { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 16px 24px 10px; }
|
|
.imp-phead h2 { margin: 0; font-size: 16px; font-weight: 600; }
|
|
@container (max-width: 720px) {
|
|
.ig { grid-template-columns: minmax(0, 1fr) auto auto; padding: 11px 16px; gap: 6px 10px; }
|
|
.ig .c-path { grid-column: 1 / -1; grid-row: 2; }
|
|
.ig .c-un { display: none; }
|
|
.ig.ihead { display: none; }
|
|
.imp-phead { padding-left: 16px; padding-right: 16px; }
|
|
}
|
|
@media (max-width: 600px) { .imp-panel { padding: 16px; } }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{{ ui.head('Image import', 'Images', "Copy WinPE deployment content into an image's Deploy folder, from the network upload share or a USB drive.") }}
|
|
|
|
<div class="ui-stack">
|
|
<div class="imp-cols">
|
|
|
|
<!-- Network upload import -->
|
|
<section class="panel imp-panel" aria-labelledby="imp-up-h">
|
|
<div>
|
|
<h2 id="imp-up-h">From the network upload share</h2>
|
|
<p class="lead">Files copied over SMB to <span class="mono">\\172.16.9.1\image-upload</span>. They are moved, not copied.</p>
|
|
</div>
|
|
{% if upload_sources %}
|
|
<form method="POST" id="uploadImportForm">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<div class="imp-field">
|
|
<label for="uploadSource">Source</label>
|
|
<select class="field" name="source" id="uploadSource" required>
|
|
<option value="">Select an upload folder</option>
|
|
{% for src in upload_sources %}
|
|
<option value="{{ src }}">{{ src }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="imp-field">
|
|
<label for="uploadTarget">Target image</label>
|
|
<select class="field" name="target" id="uploadTarget" required>
|
|
<option value="">Select the image to import into</option>
|
|
{% for it in image_types %}
|
|
<option value="{{ it }}">{{ friendly_names[it] }} ({{ it }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<span class="sub">Content goes into the image's Deploy folder. Out-of-box Drivers are pooled once in a shared folder and linked from each image to save disk space.</span>
|
|
</div>
|
|
<div class="notice"><span><strong>The files are moved out of the upload folder</strong>, so the source is used up.
|
|
Files with the same name in the image are overwritten. Large images take several minutes.</span></div>
|
|
<div class="imp-actions">
|
|
<button type="submit" class="ui-btn pri" id="uploadImportBtn">Start import</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="imp-empty">
|
|
{{ ui.chip('Nothing uploaded', 'off') }}
|
|
<p>Map <span class="mono">\\172.16.9.1\image-upload</span> on your Windows PC and copy the Deploy folder contents there, then refresh.</p>
|
|
<button type="button" class="ui-btn" onclick="location.reload()">Refresh</button>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<!-- USB import -->
|
|
<section class="panel imp-panel" aria-labelledby="imp-usb-h">
|
|
<div>
|
|
<h2 id="imp-usb-h">From a USB drive</h2>
|
|
<p class="lead">A drive mounted under <span class="mono">/mnt/</span> or <span class="mono">/media/</span>. Files are copied.</p>
|
|
</div>
|
|
{% if usb_mounts %}
|
|
<form method="POST" id="importForm">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<div class="imp-field">
|
|
<label for="source">Source (USB mount point)</label>
|
|
<select class="field" name="source" id="source" required>
|
|
<option value="">Select a mounted USB drive</option>
|
|
{% for mount in usb_mounts %}
|
|
<option value="{{ mount }}">{{ mount }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<span class="sub">The drive holding the WinPE deployment content.</span>
|
|
</div>
|
|
<div class="imp-field">
|
|
<label for="target">Target image</label>
|
|
<select class="field" name="target" id="target" required>
|
|
<option value="">Select the image to import into</option>
|
|
{% for it in image_types %}
|
|
<option value="{{ it }}">{{ friendly_names[it] }} ({{ it }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<span class="sub">Content goes into the image's Deploy folder.</span>
|
|
</div>
|
|
<div class="notice"><span>Files with the same name in the image are overwritten. Large images take several minutes.</span></div>
|
|
<div class="imp-actions">
|
|
<button type="submit" class="ui-btn pri" id="importBtn">Start import</button>
|
|
</div>
|
|
</form>
|
|
{% else %}
|
|
<div class="imp-empty">
|
|
{{ ui.chip('No USB drive', 'off') }}
|
|
<p>No mounted USB drive was found under <span class="mono">/mnt/</span> or <span class="mono">/media/</span>. Mount one and refresh.</p>
|
|
<button type="button" class="ui-btn" onclick="location.reload()">Refresh</button>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
<section class="panel ui-list" aria-labelledby="imp-st-h">
|
|
<div class="imp-phead"><h2 id="imp-st-h">Images</h2>{{ ui.chip(images | length, 'neutral') }}</div>
|
|
<div class="ig ihead" aria-hidden="true"><span>Image</span><span class="c-path">Deploy folder</span><span>Content</span><span class="c-un">Unattend</span><span></span></div>
|
|
{% for img in images %}
|
|
<div class="ig">
|
|
<span class="tip" data-tip="{{ img.friendly_name }}"><span class="trunc name">{{ img.friendly_name }}</span></span>
|
|
<span class="tip c-path" data-tip="{{ img.deploy_path }}"><span class="trunc mono sub">{{ img.image_type }}/Deploy</span></span>
|
|
<span>{% if img.has_content %}{{ ui.chip('Present', 'ok') }}{% else %}{{ ui.chip('Empty', 'off') }}{% endif %}</span>
|
|
<span class="c-un">{% if img.has_unattend %}{{ ui.chip('Unattend', 'neutral') }}{% else %}<span class="sub">No unattend</span>{% endif %}</span>
|
|
{% if img.image_type in image_types %}<a class="ui-btn sm" href="{{ image_tab_url(img.image_type, 'overview') }}" aria-label="Open {{ img.friendly_name }}">Open</a>{% else %}<span></span>{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</section>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
['importForm', 'uploadImportForm'].forEach(function(formId) {
|
|
var form = document.getElementById(formId);
|
|
if (form) {
|
|
form.addEventListener('submit', function(e) {
|
|
// Import overwrites same-name files in the image and, from the
|
|
// upload folder, moves (consumes) the source. Make that explicit.
|
|
var src = form.querySelector('select[name="source"]');
|
|
var tgt = form.querySelector('select[name="target"]');
|
|
var what = formId === 'uploadImportForm'
|
|
? 'The files are MOVED out of the upload folder, so the source is used up.'
|
|
: 'The files are copied from the USB drive.';
|
|
var msg = 'Import ' + (src ? src.value : '') + ' into ' + (tgt ? tgt.options[tgt.selectedIndex].text : '') + '?\n\n'
|
|
+ what + '\nFiles with the same name in the image are overwritten. This takes several minutes.';
|
|
if (!confirm(msg)) { e.preventDefault(); return; }
|
|
var btn = form.querySelector('button[type="submit"]');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm" aria-hidden="true"></span> Importing...';
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|