Adopt the shopdb-flask visual language across the PXE webapp (presentation only, Flask/Jinja logic unchanged): - New static/pxe-theme.css: GE Aerospace palette (atmosphere-blue sidebar, sky-blue primary, avionics-green), Inter font stack, light/dark theming via data-theme + localStorage (key pxe-theme) with system-pref fallback, and card/button/table/form/badge/alert component styles layered over Bootstrap. - base.html: shopdb-style sidebar (logo + title, nav sections, footer light/dark toggle) + theme boot script. - All 13 content templates restyled to the new page-header + card/table/badge vocabulary; unattend_editor grouped per unattend-UX research. - Fixed a pre-existing CRITICAL bug found during review: nested <form>s in image_config.html made Adopt submit the delete form and Delete-selected post every orphan filename regardless of checkboxes; split into standalone forms wired via the form= attribute. Built by a Fable-orchestrated Opus workflow (17 agents). All 14 templates parse clean under Jinja2.
181 lines
6.9 KiB
HTML
181 lines
6.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Image Import - PXE Server Manager{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1><i class="bi bi-box-arrow-in-down"></i> Image Import</h1>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<div class="col-lg-8">
|
|
|
|
<!-- Network Upload Import -->
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-hdd-network"></i> Import from Network Upload</div>
|
|
{% if upload_sources %}
|
|
<form method="POST" id="uploadImportForm">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-field">
|
|
<label for="uploadSource" class="form-label">Source</label>
|
|
<select class="form-select" name="source" id="uploadSource" required>
|
|
<option value="">-- Select upload source --</option>
|
|
{% for src in upload_sources %}
|
|
<option value="{{ src }}">{{ src }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text">
|
|
Files uploaded via SMB to <code class="mono">\\172.16.9.1\image-upload</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="uploadTarget" class="form-label">Target Image Type</label>
|
|
<select class="form-select" name="target" id="uploadTarget" required>
|
|
<option value="">-- Select target image --</option>
|
|
{% for it in image_types %}
|
|
<option value="{{ it }}">{{ friendly_names[it] }} ({{ it }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text">
|
|
Content will be copied into the Deploy directory. Shared resources
|
|
(Out-of-box Drivers) are stored once and linked across all image types.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info" role="alert">
|
|
<strong>Shared Drivers:</strong> Out-of-box Drivers are automatically pooled
|
|
into a shared directory and symlinked for each image type to save disk space.
|
|
</div>
|
|
|
|
<div class="alert alert-warning" role="alert">
|
|
<strong>Warning:</strong> Existing files in the target Deploy directory with the
|
|
same names will be overwritten. This operation may take several minutes for large
|
|
images.
|
|
</div>
|
|
|
|
<button type="submit" class="pxe-btn pxe-btn-primary" id="uploadImportBtn">
|
|
<i class="bi bi-play-fill"></i> Start Import
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="text-center py-4">
|
|
<i class="bi bi-cloud-slash" style="font-size:2rem;color:var(--text-light);"></i>
|
|
<h5 class="mt-3" style="color:var(--text-light);">No Upload Content Found</h5>
|
|
<p class="mb-0" style="color:var(--text-light);">
|
|
Map <code class="mono">\\172.16.9.1\image-upload</code> on your Windows PC and copy
|
|
the Deploy directory contents there.
|
|
</p>
|
|
<button class="pxe-btn pxe-btn-ghost pxe-btn-sm mt-3" onclick="location.reload()">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- USB Import -->
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-usb-drive"></i> Import from USB Drive</div>
|
|
{% if usb_mounts %}
|
|
<form method="POST" id="importForm">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-field">
|
|
<label for="source" class="form-label">Source (USB Mount Point)</label>
|
|
<select class="form-select" 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>
|
|
<div class="form-text">
|
|
Select the mounted USB drive containing the WinPE deployment content.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-field">
|
|
<label for="target" class="form-label">Target Image Type</label>
|
|
<select class="form-select" name="target" id="target" required>
|
|
<option value="">-- Select target image --</option>
|
|
{% for it in image_types %}
|
|
<option value="{{ it }}">{{ friendly_names[it] }} ({{ it }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text">
|
|
Content will be copied into the Deploy directory for this image type.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-warning" role="alert">
|
|
<strong>Warning:</strong> Existing files in the target Deploy directory with the
|
|
same names will be overwritten. This operation may take several minutes for large
|
|
images.
|
|
</div>
|
|
|
|
<button type="submit" class="pxe-btn pxe-btn-primary" id="importBtn">
|
|
<i class="bi bi-play-fill"></i> Start Import
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="text-center py-4">
|
|
<i class="bi bi-usb-drive" style="font-size:2rem;color:var(--text-light);"></i>
|
|
<h5 class="mt-3" style="color:var(--text-light);">No USB Drives Detected</h5>
|
|
<p class="mb-0" style="color:var(--text-light);">
|
|
No mounted USB drives were found under <code class="mono">/mnt/</code> or <code class="mono">/media/</code>.<br>
|
|
Mount a USB drive and refresh this page.
|
|
</p>
|
|
<button class="pxe-btn pxe-btn-ghost pxe-btn-sm mt-3" onclick="location.reload()">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-4">
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-list-check"></i> Current Image Status</div>
|
|
<div class="table-container">
|
|
<table class="data-table mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Image</th>
|
|
<th>Content</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for img in images %}
|
|
<tr>
|
|
<td>{{ img.friendly_name }}</td>
|
|
<td>
|
|
{% if img.has_content %}
|
|
<span class="badge badge-success">Present</span>
|
|
{% else %}
|
|
<span class="badge badge-secondary">Empty</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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() {
|
|
var btn = form.querySelector('button[type="submit"]');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Importing...';
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|