Files
pxe-server/webapp/templates/unattend_editor.html
cproudlock 18db077475 webapp: reskin to shopdb-flask design system
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.
2026-07-23 10:05:09 -04:00

571 lines
23 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ friendly_name }} - Unattend Editor{% endblock %}
{% block extra_head %}
<style>
/* Group dividers that organize the sections in configuration-pass order */
.group-heading {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.5px;
font-weight: 600;
color: var(--text-light);
margin: 1.9rem 0 0.9rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 0.5rem;
}
.group-heading:first-of-type { margin-top: 0.5rem; }
.group-heading .bi { color: var(--primary); }
/* Command-list feedback while dragging to reorder */
.command-table tbody tr { transition: background-color 0.15s; }
.command-table tbody tr.dragging {
opacity: 0.5;
background-color: rgba(65, 129, 255, 0.12);
}
.raw-xml-editor {
font-family: 'SF Mono', 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.85rem;
min-height: 600px;
height: 70vh;
tab-size: 2;
white-space: pre;
resize: vertical;
}
.order-num {
width: 40px;
text-align: center;
font-weight: 600;
color: var(--text-light);
}
.drag-handle { text-align: center; user-select: none; }
/* Count pill sitting next to a card title */
.count-pill {
font-size: 11px;
font-weight: 600;
background: var(--bg);
color: var(--text-light);
border: 1px solid var(--border);
border-radius: 999px;
padding: 1px 9px;
}
.card-header .title-group { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; }
.card-header .title-group .bi { color: var(--primary); }
</style>
{% endblock %}
{% block content %}
<div class="page-header">
<div>
<h1>{{ friendly_name }}</h1>
<small class="mono" style="color: var(--text-light);">{{ image_type }}/Deploy/FlatUnattendW10.xml</small>
</div>
<div class="header-actions">
<button type="button" class="btn btn-success" id="saveFormBtn">
<i class="bi bi-floppy"></i> Save
</button>
</div>
</div>
<!-- Form / Raw XML toggle (ids read by app.js) -->
<ul class="nav nav-tabs editor-tabs mb-3" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="form-tab" data-bs-toggle="tab"
href="#formView" role="tab">
<i class="bi bi-ui-checks-grid"></i> Form Editor
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="raw-tab" data-bs-toggle="tab"
href="#rawView" role="tab">
<i class="bi bi-code-slash"></i> Raw XML
</a>
</li>
</ul>
<form method="POST" id="unattendForm">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="tab-content">
<!-- ==================== FORM VIEW ==================== -->
<div class="tab-pane fade show active" id="formView" role="tabpanel">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
Edit this image's answer file below. Fields are grouped by Windows setup stage.
The <strong>Form Editor</strong> is the source of truth; switch to <strong>Raw XML</strong>
only for changes the form does not cover.
</div>
<!-- ============= GROUP 1: SYSTEM & REGIONAL ============= -->
<div class="group-heading"><i class="bi bi-pc-display-horizontal"></i> System &amp; Regional</div>
<!-- Machine Settings -->
<div class="card">
<div class="card-header">
<span class="title-group"><i class="bi bi-hdd-network"></i> Machine Settings</span>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Computer Name</label>
<input type="text" class="form-control" name="computer_name"
value="{{ data.computer_name }}" placeholder="* (auto-generate)">
<div class="form-text">Use <code>*</code> to let Windows auto-generate a name.</div>
</div>
<div class="col-md-6">
<label class="form-label">Time Zone</label>
<input type="text" class="form-control" name="time_zone"
value="{{ data.time_zone }}" placeholder="Eastern Standard Time">
<div class="form-text">Applied during the specialize pass.</div>
</div>
<div class="col-md-6">
<label class="form-label">Registered Organization</label>
<input type="text" class="form-control" name="registered_organization"
value="{{ data.registered_organization }}" placeholder="GE Aerospace">
</div>
<div class="col-md-6">
<label class="form-label">Registered Owner</label>
<input type="text" class="form-control" name="registered_owner"
value="{{ data.registered_owner }}" placeholder="GE Aerospace">
</div>
</div>
</div>
</div>
<!-- International Settings (collapsible) -->
<div class="card">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#intlCollapse"
role="button" style="cursor:pointer">
<span class="title-group"><i class="bi bi-globe2"></i> International Settings</span>
<small style="color: var(--text-light);"><i class="bi bi-chevron-expand"></i> click to expand</small>
</div>
<div class="collapse {% if data.intl.input_locale or data.intl.system_locale or data.intl.ui_language or data.intl.user_locale %}show{% endif %}"
id="intlCollapse">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Input Locale</label>
<input type="text" class="form-control" name="intl_input_locale"
value="{{ data.intl.input_locale }}" placeholder="e.g. en-US">
</div>
<div class="col-md-3">
<label class="form-label">System Locale</label>
<input type="text" class="form-control" name="intl_system_locale"
value="{{ data.intl.system_locale }}" placeholder="e.g. en-US">
</div>
<div class="col-md-3">
<label class="form-label">UI Language</label>
<input type="text" class="form-control" name="intl_ui_language"
value="{{ data.intl.ui_language }}" placeholder="e.g. en-US">
</div>
<div class="col-md-3">
<label class="form-label">User Locale</label>
<input type="text" class="form-control" name="intl_user_locale"
value="{{ data.intl.user_locale }}" placeholder="e.g. en-US">
</div>
</div>
</div>
</div>
</div>
<!-- Driver Paths -->
<div class="card">
<div class="card-header">
<span class="title-group">
<i class="bi bi-usb-drive"></i> Driver Paths
<span class="count-pill">{{ data.driver_paths|length }}</span>
</span>
<button type="button" class="btn btn-sm btn-outline-primary" id="addDriverPath">
<i class="bi bi-plus-lg"></i> Add
</button>
</div>
<div class="card-body p-0">
<div class="table-container">
<table class="table table-sm mb-0 align-middle" id="driverPathsTable">
<thead>
<tr>
<th style="width:40px">#</th>
<th>Path</th>
<th style="width:60px"></th>
</tr>
</thead>
<tbody>
{% for dp in data.driver_paths %}
<tr>
<td class="order-num">{{ loop.index }}</td>
<td>
<input type="text" class="form-control form-control-sm mono"
name="driver_path[]" value="{{ dp }}">
</td>
<td>
<button type="button" class="btn btn-outline-danger btn-row-action remove-row" title="Remove">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if not data.driver_paths %}
<div class="text-center py-3 empty-message" id="driverPathsEmpty" style="color: var(--text-light);">
No driver paths configured. Click <strong>Add</strong> to add one.
</div>
{% endif %}
</div>
</div>
<!-- ============= GROUP 2: ACCOUNTS & SIGN-IN ============= -->
<div class="group-heading"><i class="bi bi-people"></i> Accounts &amp; Sign-in</div>
<!-- User Accounts -->
<div class="card">
<div class="card-header">
<span class="title-group">
<i class="bi bi-person-badge"></i> User Accounts (Local)
<span class="count-pill">{{ data.user_accounts|length }}</span>
</span>
<button type="button" class="btn btn-sm btn-outline-primary" id="addUserAccount">
<i class="bi bi-plus-lg"></i> Add
</button>
</div>
<div class="card-body p-0">
<div class="table-container">
<table class="table table-sm mb-0 align-middle" id="userAccountsTable">
<thead>
<tr>
<th style="width:40px">#</th>
<th>Name</th>
<th>Password</th>
<th>Group</th>
<th>Display Name</th>
<th style="width:60px"></th>
</tr>
</thead>
<tbody>
{% for acct in data.user_accounts %}
<tr>
<td class="order-num">{{ loop.index }}</td>
<td>
<input type="text" class="form-control form-control-sm"
name="account_name_{{ loop.index0 }}" value="{{ acct.name }}">
</td>
<td>
<input type="text" class="form-control form-control-sm"
name="account_password_{{ loop.index0 }}" value="{{ acct.password }}">
</td>
<td>
<input type="text" class="form-control form-control-sm"
name="account_group_{{ loop.index0 }}" value="{{ acct.group }}">
</td>
<td>
<input type="text" class="form-control form-control-sm"
name="account_display_{{ loop.index0 }}" value="{{ acct.display_name }}">
</td>
<td>
<input type="hidden" name="account_plaintext_{{ loop.index0 }}" value="{{ acct.plain_text }}">
<button type="button" class="btn btn-outline-danger btn-row-action remove-account-row" title="Remove">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if not data.user_accounts %}
<div class="text-center py-3 empty-message" id="userAccountsEmpty" style="color: var(--text-light);">
No local accounts configured. Click <strong>Add</strong> to add one.
</div>
{% endif %}
</div>
<div class="card-footer">
<small style="color: var(--text-light);">
<i class="bi bi-shield-lock"></i> Passwords are stored in the answer file. Handle this image config as sensitive.
</small>
</div>
</div>
<!-- AutoLogon -->
<div class="card">
<div class="card-header">
<span class="title-group"><i class="bi bi-box-arrow-in-right"></i> AutoLogon</span>
</div>
<div class="card-body">
<div class="row g-3 align-items-end">
<div class="col-md-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="autologonEnabledToggle"
{% if data.autologon.enabled|lower == 'true' %}checked{% endif %}>
<label class="form-check-label" for="autologonEnabledToggle">Enabled</label>
<input type="hidden" name="autologon_enabled" id="autologon_enabled_val"
value="{{ data.autologon.enabled }}">
</div>
</div>
<div class="col-md-3">
<label class="form-label">Username</label>
<input type="text" class="form-control" name="autologon_username"
value="{{ data.autologon.username }}" placeholder="e.g. SupportUser">
</div>
<div class="col-md-3">
<label class="form-label">Password</label>
<input type="text" class="form-control" name="autologon_password"
value="{{ data.autologon.password }}">
<input type="hidden" name="autologon_plaintext" value="{{ data.autologon.plain_text }}">
</div>
<div class="col-md-3">
<label class="form-label">Logon Count</label>
<input type="text" class="form-control" name="autologon_logoncount"
value="{{ data.autologon.logon_count }}" placeholder="e.g. 2 or 999">
<div class="form-text">Number of auto-logon attempts.</div>
</div>
</div>
</div>
</div>
<!-- ============= GROUP 3: OUT-OF-BOX EXPERIENCE ============= -->
<div class="group-heading"><i class="bi bi-window-desktop"></i> Out-of-Box Experience (OOBE)</div>
<!-- OOBE Settings -->
<div class="card">
<div class="card-header">
<span class="title-group"><i class="bi bi-toggles"></i> OOBE Settings</span>
</div>
<div class="card-body">
<div class="row g-3">
{% set bool_oobe_fields = [
("HideEULAPage", "Hide EULA Page"),
("HideOEMRegistrationScreen", "Hide OEM Registration Screen"),
("HideOnlineAccountScreens", "Hide Online Account Screens"),
("HideWirelessSetupInOOBE", "Hide Wireless Setup in OOBE"),
("HideLocalAccountScreen", "Hide Local Account Screen"),
("SkipUserOOBE", "Skip User OOBE"),
("SkipMachineOOBE", "Skip Machine OOBE"),
] %}
{% for key, label in bool_oobe_fields %}
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input oobe-toggle" type="checkbox"
id="oobe_{{ key }}"
data-field="oobe_{{ key }}"
{% if data.oobe[key]|lower == 'true' %}checked{% endif %}>
<label class="form-check-label" for="oobe_{{ key }}">{{ label }}</label>
<input type="hidden" name="oobe_{{ key }}" id="oobe_{{ key }}_val"
value="{{ data.oobe[key] }}">
</div>
</div>
{% endfor %}
<div class="col-md-4">
<label class="form-label">Network Location</label>
<select class="form-select form-select-sm" name="oobe_NetworkLocation">
{% for opt in ["Home", "Work", "Other"] %}
<option value="{{ opt }}" {% if data.oobe.NetworkLocation == opt %}selected{% endif %}>
{{ opt }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">ProtectYourPC</label>
<select class="form-select form-select-sm" name="oobe_ProtectYourPC">
{% for opt in ["1", "2", "3"] %}
<option value="{{ opt }}" {% if data.oobe.ProtectYourPC == opt %}selected{% endif %}>
{{ opt }}{% if opt == "1" %} (Recommended){% elif opt == "3" %} (Skip){% endif %}
</option>
{% endfor %}
</select>
<div class="form-text">1 = Recommended, 2 = Install only updates, 3 = Skip</div>
</div>
</div>
</div>
</div>
<!-- OOBE Time Zone -->
<div class="card">
<div class="card-header">
<span class="title-group"><i class="bi bi-clock-history"></i> OOBE Time Zone</span>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Time Zone (oobeSystem pass)</label>
<input type="text" class="form-control" name="oobe_timezone"
value="{{ data.oobe_timezone }}" placeholder="e.g. Eastern Standard Time">
<div class="form-text">Separate from the specialize-pass time zone in Machine Settings above.</div>
</div>
</div>
</div>
</div>
<!-- ============= GROUP 4: SETUP COMMANDS ============= -->
<div class="group-heading"><i class="bi bi-terminal"></i> Setup Commands (RunSynchronous)</div>
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
These commands run as a validated golden sequence. <strong>Order matters within each list.</strong>
Reorder with the arrows or by dragging the <i class="bi bi-grip-vertical"></i> handle only when you are sure.
</div>
<!-- Specialize Commands -->
<div class="card">
<div class="card-header">
<span class="title-group">
<i class="bi bi-gear-wide-connected"></i> Specialize Commands
<span class="count-pill">{{ data.specialize_commands|length }}</span>
</span>
<button type="button" class="btn btn-sm btn-outline-primary" id="addSpecCmd">
<i class="bi bi-plus-lg"></i> Add
</button>
</div>
<div class="card-body p-0">
<div class="table-container">
<table class="table table-sm mb-0 align-middle command-table" id="specCmdTable">
<thead>
<tr>
<th style="width:30px"></th>
<th style="width:50px">Order</th>
<th>Path / Command</th>
<th>Description</th>
<th style="width:120px"></th>
</tr>
</thead>
<tbody>
{% for cmd in data.specialize_commands %}
<tr draggable="true">
<td class="drag-handle"><i class="bi bi-grip-vertical"></i></td>
<td class="order-num">{{ loop.index }}</td>
<td>
<input type="text" class="form-control form-control-sm mono"
name="spec_cmd_path[]" value="{{ cmd.path }}">
</td>
<td>
<input type="text" class="form-control form-control-sm"
name="spec_cmd_desc[]" value="{{ cmd.description }}">
</td>
<td class="text-nowrap">
<button type="button" class="btn btn-outline-secondary btn-row-action move-up" title="Move up">
<i class="bi bi-arrow-up"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-row-action move-down" title="Move down">
<i class="bi bi-arrow-down"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-row-action remove-row" title="Remove">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if not data.specialize_commands %}
<div class="text-center py-3 empty-message" id="specCmdEmpty" style="color: var(--text-light);">
No specialize commands configured. Click <strong>Add</strong> to add one.
</div>
{% endif %}
</div>
</div>
<!-- First Logon Commands -->
<div class="card">
<div class="card-header">
<span class="title-group">
<i class="bi bi-play-circle"></i> First Logon Commands
<span class="count-pill">{{ data.firstlogon_commands|length }}</span>
</span>
<button type="button" class="btn btn-sm btn-outline-primary" id="addFlCmd">
<i class="bi bi-plus-lg"></i> Add
</button>
</div>
<div class="card-body p-0">
<div class="table-container">
<table class="table table-sm mb-0 align-middle command-table" id="flCmdTable">
<thead>
<tr>
<th style="width:30px"></th>
<th style="width:50px">Order</th>
<th>Command Line</th>
<th>Description</th>
<th style="width:120px"></th>
</tr>
</thead>
<tbody>
{% for cmd in data.firstlogon_commands %}
<tr draggable="true">
<td class="drag-handle"><i class="bi bi-grip-vertical"></i></td>
<td class="order-num">{{ loop.index }}</td>
<td>
<input type="text" class="form-control form-control-sm mono"
name="fl_cmd_commandline[]" value="{{ cmd.commandline }}">
</td>
<td>
<input type="text" class="form-control form-control-sm"
name="fl_cmd_desc[]" value="{{ cmd.description }}">
</td>
<td class="text-nowrap">
<button type="button" class="btn btn-outline-secondary btn-row-action move-up" title="Move up">
<i class="bi bi-arrow-up"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-row-action move-down" title="Move down">
<i class="bi bi-arrow-down"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-row-action remove-row" title="Remove">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if not data.firstlogon_commands %}
<div class="text-center py-3 empty-message" id="flCmdEmpty" style="color: var(--text-light);">
No first logon commands configured. Click <strong>Add</strong> to add one.
</div>
{% endif %}
</div>
</div>
</div><!-- end formView -->
<!-- ==================== RAW XML VIEW ==================== -->
<div class="tab-pane fade" id="rawView" role="tabpanel">
<div class="card">
<div class="card-header">
<span class="title-group"><i class="bi bi-filetype-xml"></i> Raw XML</span>
<button type="button" class="btn btn-sm btn-success" id="saveRawBtn">
<i class="bi bi-floppy"></i> Save Raw XML
</button>
</div>
<div class="card-body">
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
Editing raw XML bypasses the form. Save here to write your changes directly.
</div>
<textarea class="form-control raw-xml-editor" name="raw_xml"
id="rawXmlEditor">{{ data.raw_xml }}</textarea>
</div>
</div>
</div>
</div><!-- end tab-content -->
<input type="hidden" name="save_mode" id="saveMode" value="form">
</form>
{% endblock %}
{% block extra_scripts %}
<script>
// Pass the image_type and API URL to JavaScript
window.PXE_IMAGE_TYPE = "{{ image_type }}";
window.PXE_API_URL = "{{ url_for('api_save_unattend', image_type=image_type) }}";
</script>
{% endblock %}