Files
pxe-server/webapp/templates/unattend_editor.html
cproudlock fd00d5ff89 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).
2026-10-14 14:29:03 -04:00

612 lines
37 KiB
HTML

{% extends "base.html" %}
{% import '_ui.html' as ui %}
{% block title %}{{ friendly_name }} - Unattend{% endblock %}
{#
Unattend tab. Rows carry data-orig = their index in the file as parsed, so a
save can tell an edited or moved item from a new one (services/unattend.py,
"Form edits"). All behaviour is in static/unattend-editor.js; class names
are una-* so the old handlers in static/app.js never fire here. Command
rows keep Move up / Move down / Remove (.una-up .una-down .una-remove) in
their "..." menu; the save bar (Review changes, #unaReview) shows once
something is edited.
#}
{% block extra_head %}
<style>
.una-bar { display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; justify-content: space-between; }
/* Form / Raw XML switch: Bootstrap pills underneath, drawn as a segmented control */
.una-seg { display: flex; margin: 0; padding: 0; list-style: none; border: 1px solid var(--control); border-radius: 8px; overflow: hidden; flex: none; }
.una-seg .nav-link { display: flex; align-items: center; height: 34px; padding: 0 14px; border: 0; border-radius: 0; background: var(--surface);
font-size: 13px; color: var(--text); text-decoration: none; }
.una-seg .nav-item + .nav-item .nav-link { border-left: 1px solid var(--control); }
.una-seg .nav-link.active { background: var(--text); color: var(--surface); font-weight: 600; }
.una-meta { font-size: 13px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; }
.una-meta a.chip { text-decoration: none; }
.una-lint-list { font-size: 13px; margin: 8px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
.una-lint-list .chip { font-size: 11px; padding: 1px 6px; margin-right: 6px; }
.una-lintbox { flex-direction: column; gap: 0; }
/* Form view: section index beside the sections (wide), or a sticky row of
links above them (narrow). */
.una-layout { display: block; }
.una-index { position: sticky; top: 0; z-index: 20; background: var(--bg); margin: 0 0 14px; padding: 8px 0; }
.una-index ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: nowrap; gap: 6px; overflow-x: auto; scrollbar-width: none; }
.una-index .ix-item:not(.ix-keep), .una-index .ix-wide, .una-index .ix-long { display: none; }
.una-index a { display: flex; align-items: center; gap: 6px; text-decoration: none; font-size: 13px; color: var(--text-2);
padding: 0 12px; min-height: 30px; border: 1px solid var(--line-2); border-radius: 15px; white-space: nowrap; background: var(--surface-3); }
.una-index a:hover { color: var(--text); }
.una-index a.active { background: var(--text); color: var(--surface); border-color: var(--text); }
.una-index .count { font-family: var(--font-mono); font-size: 11px; color: inherit; opacity: .8; }
.una-index a.has-edits::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--act); flex: none; }
@media (min-width: 1280px) {
.una-layout { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 24px; align-items: start; }
.una-index { top: 16px; margin: 0; padding: 0; background: transparent; max-height: calc(100vh - 32px); overflow-y: auto; }
.una-index ul { display: block; }
.una-index .ix-item:not(.ix-keep), .una-index .ix-wide { display: flex; }
.una-index .ix-long { display: inline; }
.una-index .ix-short { display: none; }
.una-index a { border: 0; border-radius: 7px; background: transparent; min-height: 32px; padding: 0 10px; justify-content: flex-start; }
.una-index a.ix-group { font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin-top: 14px; }
.una-index li:first-child a.ix-group { margin-top: 0; }
.una-index a.active { background: var(--surface); color: var(--text); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent), 0 1px 2px var(--shadow); }
.una-index a.ix-group.active { background: transparent; box-shadow: none; color: var(--text); }
.una-index a .count { margin-left: auto; }
}
.una-main { min-width: 0; display: flex; flex-direction: column; gap: 18px; }
.una-section { scroll-margin-top: 4.5rem; }
@media (min-width: 1280px) { .una-section { scroll-margin-top: 1rem; } }
.group-heading { margin: 14px 0 0; font-size: 18px; font-weight: 600; }
.una-main > .group-heading:first-child { margin-top: 0; }
.una-phead { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 16px 24px 12px; }
.una-phead h3 { margin: 0; font-size: 16px; font-weight: 600; }
.una-phead .sp { margin-left: auto; }
.una-pbody { padding: 0 24px 20px; }
.una-pbody .form-label { font-size: 13px; color: var(--text-2); margin-bottom: 4px; }
.una-pbody .form-text { font-size: 12px; color: var(--muted); }
.una-pbody .form-control, .una-pbody .form-select { min-height: 38px; height: 38px; padding-top: 6px; padding-bottom: 6px; font-size: 14px; }
.una-hint { font-size: 13px; color: var(--muted); }
.una-intro { padding: 0 24px 12px; font-size: 13px; color: var(--text-2); }
.una-empty { text-align: center; color: var(--muted); padding: 16px; font-size: 14px; border-top: 1px solid var(--line); }
.una-foot-note { padding: 10px 24px; font-size: 13px; color: var(--text-2); border-top: 1px solid var(--line); background: var(--surface-2); border-radius: 0 0 14px 14px; }
/* Command filter: stays in view while the lists scroll (wide layout). */
.una-filter { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; background: var(--bg); padding: 8px 0; }
@media (min-width: 1280px) { .una-filter { position: sticky; top: 0; z-index: 15; } }
.una-filter .input-wrap { flex: 1 1 18rem; max-width: 34rem; display: flex; }
.una-filter .field { flex: 1; min-width: 0; }
.una-filter .una-hint strong { color: var(--text); }
/* One command: number + grip | description, full command, counters | menu */
.una-list { border-top: 1px solid var(--line); }
.una-list:empty { border-top: 0; }
.una-row { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 10px; padding: 10px 16px 10px 8px;
border-bottom: 1px solid var(--line); box-shadow: inset 3px 0 0 transparent; transition: background-color .15s; }
.una-row:last-child { border-bottom: 0; }
.una-row.is-edited, .una-simple.is-edited { background: var(--edit-soft); box-shadow: inset 3px 0 0 var(--accent); }
.una-row.is-new, .una-simple.is-new { background: var(--edit-soft); box-shadow: inset 3px 0 0 var(--ok); }
.una-row.dragging { opacity: .45; }
.una-row.drop-before { box-shadow: inset 0 2px 0 var(--accent); }
.una-row.drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
.una-side { display: flex; flex-direction: column; align-items: center; gap: 6px; padding-top: 6px; }
.una-pos { font-size: 13px; color: var(--muted); line-height: 1; }
.una-grip { color: var(--muted); cursor: grab; padding: 2px 5px; border-radius: 4px; }
.una-grip:hover { color: var(--text); background: var(--surface-3); }
.una-body { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.una-note { font-size: 13px; color: var(--muted); font-style: italic; display: flex; gap: 6px; align-items: baseline; }
.una-note .bi { font-style: normal; flex: none; }
.una-head { display: flex; gap: 10px; align-items: center; }
.una-head .una-desc { flex: 1 1 auto; font-weight: 600; }
.una-tag { font-size: 12px; color: var(--muted); white-space: nowrap; display: flex; flex-direction: column; align-items: flex-end; line-height: 1.35; min-width: 5.5rem; }
.una-tag .chip { font-size: 11px; padding: 1px 6px; }
textarea.una-cmd { font-family: var(--font-mono); font-size: 13px; line-height: 1.5; resize: none; overflow: hidden; white-space: pre-wrap;
overflow-wrap: anywhere; word-break: normal; min-height: 2.1rem; }
.una-foot { display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12px; color: var(--muted); }
.una-foot[hidden] { display: none; }
.una-inner { font-style: italic; }
.una-count.near { color: var(--warn-text); }
.una-count.over { color: var(--fail-text); font-weight: 600; }
.una-actions { display: flex; align-items: flex-start; padding-top: 2px; }
.filtering .una-grip, .filtering .una-up, .filtering .una-down { opacity: .3; pointer-events: none; }
.una-row[hidden] { display: none; }
/* Short rows (driver paths, accounts) */
.una-simple { display: grid; grid-template-columns: 36px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 8px 16px 8px 8px; border-bottom: 1px solid var(--line); }
.una-simple:last-child { border-bottom: 0; }
.una-simple .una-pos { text-align: center; }
.una-acct-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
@media (max-width: 1100px) { .una-acct-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.una-acct-fields label { font-size: 12px; color: var(--muted); margin-bottom: 2px; display: block; }
.una-field-changed { border-color: var(--accent) !important; box-shadow: inset 0 0 0 1px var(--accent); }
.raw-xml-editor { font-family: var(--font-mono); font-size: 13px; min-height: 600px; height: 70vh; tab-size: 4; white-space: pre; resize: vertical; }
/* Review modal additions (the modal itself is in _image_header.html) */
.una-review-lint { margin-top: 1rem; }
#imageReviewBody > .notice { margin-bottom: 12px; }
.una-review-lint .alert { font-size: 13.5px; }
.una-lint-ok { font-size: 13px; color: var(--muted); display: flex; gap: 6px; align-items: center; }
.una-lint-ok .bi { color: var(--ok); }
.una-review-xml summary { cursor: pointer; font-size: 13px; color: var(--accent); font-weight: 600; margin-top: 1rem; }
.una-review-xml pre { max-height: 50vh; overflow: auto; font-size: 12px; padding: 12px; margin: 8px 0 0; background: var(--surface-2);
border: 1px solid var(--line-2); border-radius: 8px; white-space: pre; }
.savebar { position: sticky; bottom: 12px; z-index: 50; display: none; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px;
padding: 12px 12px 12px 20px; border-radius: 12px; background: var(--savebar-bg); border: 1px solid var(--savebar-edge);
color: var(--savebar-text); box-shadow: 0 8px 24px rgba(0,0,0,.25); }
.dirty .savebar { display: flex; }
.savebar .ui-btn { background: transparent; color: var(--savebar-text); border-color: #8E9AAB; }
.savebar .ui-btn.pri { background: var(--savebar-pri); border-color: var(--savebar-pri); color: var(--on-savebar-pri); }
.savebar .dirty-flag { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; }
.savebar .dirty-flag .dot { background: var(--act); }
.savebar .sub { color: var(--savebar-sub); }
@media (max-width: 600px) {
.una-phead, .una-intro, .una-foot-note { padding-left: 16px; padding-right: 16px; }
.una-pbody { padding: 0 16px 16px; }
.una-row { grid-template-columns: 30px minmax(0, 1fr) auto; padding-right: 8px; gap: 8px; }
.una-head { flex-wrap: wrap; }
.una-tag { flex-direction: row; gap: 8px; min-width: 0; }
}
</style>
{% endblock %}
{% macro note(item) -%}
{% if item.note %}<div class="una-note"><i class="bi bi-chat-left-text" aria-hidden="true"></i><span>{{ item.note | join(' | ') }}</span></div>{% endif %}
{%- endmacro %}
{% macro row_menu(label) -%}
{% call ui.more(label) %}
<button type="button" class="una-up">Move up</button>
<button type="button" class="una-down">Move down</button>
<div class="sep"></div>
<button type="button" class="una-remove del">Remove</button>
{% endcall %}
{%- endmacro %}
{% macro cmd_row(cmd, idx, field, limit) -%}
<div class="una-row" data-orig="{{ idx }}">
<div class="una-side">
<span class="una-pos">{{ idx + 1 }}</span>
<span class="una-grip" title="Drag to reorder" aria-hidden="true"><i class="bi bi-grip-vertical"></i></span>
</div>
<div class="una-body">
{{ note(cmd) }}
<div class="una-head">
<input type="text" class="form-control form-control-sm una-desc" value="{{ cmd.description }}"
placeholder="Description" aria-label="Description" data-limit="{{ limits.description }}">
<span class="una-tag"><span title="Order value in the file">Order {{ cmd.order or '?' }}</span><span class="una-count" data-for="cmd"></span></span>
</div>
<textarea class="form-control una-cmd" rows="1" spellcheck="false" aria-label="Command"
data-limit="{{ limit }}">{{ cmd[field] }}</textarea>
<div class="una-foot">
<span class="una-count" data-for="desc"></span>
{% if cmd.inner_note %}<span class="una-inner" title="A comment inside this command; it is kept"><i class="bi bi-chat-left-text" aria-hidden="true"></i> {{ cmd.inner_note | join(' | ') | truncate(160) }}</span>{% endif %}
</div>
</div>
<div class="una-actions">{{ row_menu('command ' ~ (idx + 1)) }}</div>
</div>
{%- endmacro %}
{% block content %}
{% set header_subtitle = 'Deploy/FlatUnattendW10.xml' %}
{% set header_actions %}
{% if last_save %}
<form method="POST" action="{{ url_for('images_unattend.undo', image_type=image_type) }}" class="d-inline"
onsubmit="return confirm('Put FlatUnattendW10.xml back as it was before the save of {{ last_save.ts | local_dt }}?');">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="ui-btn" title="Last save: {{ last_save.ts | local_dt }} ({{ last_save.action }})">Undo last save</button>
</form>
{% else %}
<span class="sub">Undo last save: nothing saved from this page yet</span>
{% endif %}
{% endset %}
{% include "_image_header.html" %}
<div class="ui-stack" id="unaPage">
{% if unattend_override %}
<div class="notice"><i class="bi bi-exclamation-triangle" aria-hidden="true"></i><span>
<span class="mono">{{ unattend_override }}</span> exists in this image. PESetup loads that file first,
so edits to <span class="mono">Deploy/FlatUnattendW10.xml</span> here do not reach the PCs until it is removed.</span>
</div>
{% endif %}
{% if not file_exists %}
<div class="notice info"><i class="bi bi-info-circle" aria-hidden="true"></i><span>
This image has no <span class="mono">Deploy/FlatUnattendW10.xml</span> yet. The form starts from an empty
answer file; saving creates it.</span>
</div>
{% endif %}
{% set lint_errors = lint | selectattr('level', 'equalto', 'ERROR') | list %}
<div>
<div class="una-bar">
<ul class="nav nav-pills una-seg" role="tablist" aria-label="Editor view">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="unaFormTab" data-bs-toggle="pill" href="#unaFormView" role="tab" aria-controls="unaFormView" aria-selected="true">Form</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="unaRawTab" data-bs-toggle="pill" href="#unaRawView" role="tab" aria-controls="unaRawView" aria-selected="false">Raw XML</a>
</li>
</ul>
<div class="una-meta">
{% if file_exists %}
<span title="Last modified">Modified {{ file_mtime | local_dt }}</span>
<span>{{ file_size | filesizeformat }}</span>
<span title="FlatUnattendW10.xml.bak-* next to the file">{{ backup_count }} backup{{ '' if backup_count == 1 else 's' }}</span>
{% endif %}
{% if not lint %}
<span class="chip c-ok" title="The checks of scripts/lint-unattend.py">Lint clean</span>
{% elif lint_errors %}
<a href="#unaLint" data-bs-toggle="collapse" class="chip c-err" aria-expanded="true">{{ lint_errors | length }} lint error{{ '' if lint_errors | length == 1 else 's' }}</a>
{% else %}
<a href="#unaLint" data-bs-toggle="collapse" class="chip c-warn" aria-expanded="false">{{ lint | length }} lint warning{{ '' if lint | length == 1 else 's' }}</a>
{% endif %}
</div>
</div>
{% if lint %}
<div class="collapse {% if lint_errors %}show{% endif %}" id="unaLint" style="margin-top: 12px">
<div class="notice una-lintbox {{ 'bad' if lint_errors }}">
<div><strong>The file as it is now:</strong> checks from <span class="mono">scripts/lint-unattend.py</span>.
{% if lint_errors %}One ERROR invalidates the whole answer file for its pass; fix it before imaging.{% endif %}</div>
<ul class="una-lint-list">
{% for f in lint %}<li>{{ ui.chip(f.level, 'err' if f.level == 'ERROR' else 'warn') }}{{ f.message }}</li>{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
<div class="tab-content">
<!-- ==================== FORM VIEW ==================== -->
<div class="tab-pane fade show active" id="unaFormView" role="tabpanel" aria-labelledby="unaFormTab">
<div class="una-layout">
<nav class="una-index" id="unaIndex" aria-label="Sections">
{# Wide: every section under its group. Narrow: one chip per group plus
the two command lists (ix-keep), so the bar stays one row. #}
<ul>
<li><a class="ix-group" href="#grp-system" data-group="system"><span class="ix-long">System and regional</span><span class="ix-short">System</span></a></li>
<li><a class="ix-item" href="#sec-machine" data-group="system">Machine settings</a></li>
<li><a class="ix-item" href="#sec-intl" data-group="system">International</a></li>
<li><a class="ix-item" href="#sec-driverpaths" data-group="system">Driver paths <span class="count" data-count="driverpaths">{{ data.driver_paths | length }}</span></a></li>
<li><a class="ix-group" href="#grp-accounts" data-group="accounts"><span class="ix-long">Accounts and sign-in</span><span class="ix-short">Accounts</span></a></li>
<li><a class="ix-item" href="#sec-accounts" data-group="accounts">User accounts <span class="count" data-count="accounts">{{ data.user_accounts | length }}</span></a></li>
<li><a class="ix-item" href="#sec-autologon" data-group="accounts">AutoLogon</a></li>
<li><a class="ix-group" href="#grp-oobe" data-group="oobe"><span class="ix-long">Out-of-box experience</span><span class="ix-short">OOBE</span></a></li>
<li><a class="ix-item" href="#sec-oobe" data-group="oobe">OOBE settings</a></li>
<li><a class="ix-item" href="#sec-oobetz" data-group="oobe">OOBE time zone</a></li>
<li><a class="ix-group ix-wide" href="#grp-commands" data-group="commands">Setup commands</a></li>
<li><a class="ix-item ix-keep" href="#sec-spec" data-group="commands">Specialize <span class="count" data-count="spec">{{ data.specialize_commands | length }}</span></a></li>
<li><a class="ix-item ix-keep" href="#sec-fl" data-group="commands">First logon <span class="count" data-count="fl">{{ data.firstlogon_commands | length }}</span></a></li>
</ul>
</nav>
<div class="una-main" id="unaMain">
<!-- ============= SYSTEM AND REGIONAL ============= -->
<h2 class="group-heading una-section" id="grp-system" data-group="system">System and regional</h2>
<section class="panel una-section" id="sec-machine" data-section="machine" data-group="system" aria-labelledby="h-machine">
<div class="una-phead">
<h3 id="h-machine">Machine settings</h3>
<span class="una-hint">specialize pass, Microsoft-Windows-Shell-Setup</span>
</div>
<div class="una-pbody">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="unaComputerName">Computer name</label>
<input type="text" class="form-control mono una-field" id="unaComputerName" data-key="computer_name"
value="{{ data.computer_name }}" placeholder="* (Windows makes one up)">
<div class="form-text">PESetup replaces <span class="mono">%serialnumber%</span>; <span class="mono">*</span> lets Windows pick a name.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="unaTimeZone">Time zone</label>
<input type="text" class="form-control una-field" id="unaTimeZone" data-key="time_zone"
value="{{ data.time_zone }}" placeholder="Eastern Standard Time">
</div>
<div class="col-md-6">
<label class="form-label" for="unaOrg">Registered organization</label>
<input type="text" class="form-control una-field" id="unaOrg" data-key="registered_organization"
value="{{ data.registered_organization }}">
</div>
<div class="col-md-6">
<label class="form-label" for="unaOwner">Registered owner</label>
<input type="text" class="form-control una-field" id="unaOwner" data-key="registered_owner"
value="{{ data.registered_owner }}">
</div>
</div>
</div>
</section>
<section class="panel una-section" id="sec-intl" data-section="intl" data-group="system" aria-labelledby="h-intl">
<div class="una-phead">
<h3 id="h-intl">International</h3>
<span class="una-hint">oobeSystem pass, Microsoft-Windows-International-Core</span>
</div>
<div class="una-pbody">
<div class="row g-3">
{% for key, label in [("input_locale", "Input locale"), ("system_locale", "System locale"), ("ui_language", "UI language"), ("user_locale", "User locale")] %}
<div class="col-md-3 col-6">
<label class="form-label" for="unaIntl_{{ key }}">{{ label }}</label>
<input type="text" class="form-control una-field" id="unaIntl_{{ key }}" data-key="intl.{{ key }}"
value="{{ data.intl[key] }}" placeholder="en-US">
</div>
{% endfor %}
</div>
</div>
</section>
<section class="panel una-section" id="sec-driverpaths" data-section="driverpaths" data-group="system" aria-labelledby="h-dp">
<div class="una-phead">
<h3 id="h-dp">Driver paths</h3>
<span class="chip c-neutral" data-count="driverpaths">{{ data.driver_paths | length }}</span>
<button type="button" class="ui-btn sp una-add" data-list="driverpaths">Add path</button>
</div>
<div class="una-intro">
Folders DISM searches for drivers while servicing the applied image (offlineServicing pass).
<span class="mono">W:\Drivers</span> is where PESetup unpacks this model's driver pack.
</div>
<div class="una-list" id="unaDriverPaths" data-list="driverpaths">
{% for dp in data.driver_paths %}
<div class="una-simple" data-orig="{{ loop.index0 }}">
<span class="una-pos">{{ loop.index }}</span>
<input type="text" class="form-control form-control-sm mono una-dp" value="{{ dp }}" aria-label="Driver path {{ loop.index }}">
<button type="button" class="ui-btn ghost sm una-remove" aria-label="Remove driver path {{ loop.index }}">Remove</button>
</div>
{% endfor %}
</div>
<div class="una-empty" data-empty="driverpaths" {% if data.driver_paths %}hidden{% endif %}>No driver paths. Windows only uses the drivers in the image.</div>
</section>
<!-- ============= ACCOUNTS AND SIGN-IN ============= -->
<h2 class="group-heading una-section" id="grp-accounts" data-group="accounts">Accounts and sign-in</h2>
<section class="panel una-section" id="sec-accounts" data-section="accounts" data-group="accounts" aria-labelledby="h-acct">
<div class="una-phead">
<h3 id="h-acct">User accounts (local)</h3>
<span class="chip c-neutral" data-count="accounts">{{ data.user_accounts | length }}</span>
<button type="button" class="ui-btn sp una-add" data-list="accounts">Add account</button>
</div>
<div class="una-list" id="unaAccounts" data-list="accounts">
{% for acct in data.user_accounts %}
<div class="una-simple" data-orig="{{ loop.index0 }}" data-plain="{{ acct.plain_text }}">
<span class="una-pos">{{ loop.index }}</span>
<div class="una-acct-fields">
<div><label for="unaAcct{{ loop.index }}n">Name</label><input type="text" class="form-control form-control-sm" id="unaAcct{{ loop.index }}n" data-f="name" value="{{ acct.name }}"></div>
<div><label for="unaAcct{{ loop.index }}p">Password{% if acct.plain_text == 'false' %} <span title="PlainText is false in the file: this is the encoded value. A password typed here is stored as plain text.">(encoded)</span>{% endif %}</label><input type="text" class="form-control form-control-sm mono" id="unaAcct{{ loop.index }}p" data-f="password" value="{{ acct.password }}" autocomplete="off"></div>
<div><label for="unaAcct{{ loop.index }}g">Group</label><input type="text" class="form-control form-control-sm" id="unaAcct{{ loop.index }}g" data-f="group" value="{{ acct.group }}"></div>
<div><label for="unaAcct{{ loop.index }}d">Display name</label><input type="text" class="form-control form-control-sm" id="unaAcct{{ loop.index }}d" data-f="display_name" value="{{ acct.display_name }}"></div>
</div>
<button type="button" class="ui-btn ghost sm una-remove" aria-label="Remove account {{ acct.name }}">Remove</button>
</div>
{% endfor %}
</div>
<div class="una-empty" data-empty="accounts" {% if data.user_accounts %}hidden{% endif %}>No local accounts.</div>
<div class="una-foot-note">Passwords sit in the answer file as plain text. Treat this file as sensitive.</div>
</section>
<section class="panel una-section" id="sec-autologon" data-section="autologon" data-group="accounts" aria-labelledby="h-al">
<div class="una-phead">
<h3 id="h-al">AutoLogon</h3>
<span class="una-hint">clear the user name to remove AutoLogon</span>
</div>
<div class="una-pbody">
<div class="row g-3 align-items-end">
<div class="col-md-3 col-6">
<label class="form-label" for="unaAlEnabled">Enabled</label>
<select class="form-select una-field" id="unaAlEnabled" data-key="autologon.enabled">
{% for opt, label in [("", "Not set"), ("true", "true"), ("false", "false")] %}
<option value="{{ opt }}" {% if data.autologon.enabled == opt %}selected{% endif %}>{{ label }}</option>
{% endfor %}
{% if data.autologon.enabled not in ["", "true", "false"] %}<option value="{{ data.autologon.enabled }}" selected>{{ data.autologon.enabled }}</option>{% endif %}
</select>
</div>
<div class="col-md-3 col-6">
<label class="form-label" for="unaAlUser">User name</label>
<input type="text" class="form-control una-field" id="unaAlUser" data-key="autologon.username"
value="{{ data.autologon.username }}" placeholder="e.g. SupportUser">
</div>
<div class="col-md-3 col-6">
<label class="form-label" for="unaAlPass">Password{% if data.autologon.plain_text == 'false' %} <span title="PlainText is false in the file: this is the encoded value. A password typed here is stored as plain text.">(encoded)</span>{% endif %}</label>
<input type="text" class="form-control mono una-field" id="unaAlPass" data-key="autologon.password"
value="{{ data.autologon.password }}" autocomplete="off">
<input type="hidden" class="una-field" data-key="autologon.plain_text" value="{{ data.autologon.plain_text }}">
</div>
<div class="col-md-3 col-6">
<label class="form-label" for="unaAlCount">Logon count</label>
<input type="text" class="form-control una-field" id="unaAlCount" data-key="autologon.logon_count"
value="{{ data.autologon.logon_count }}" placeholder="e.g. 2">
</div>
</div>
</div>
</section>
<!-- ============= OUT-OF-BOX EXPERIENCE ============= -->
<h2 class="group-heading una-section" id="grp-oobe" data-group="oobe">Out-of-box experience (OOBE)</h2>
<section class="panel una-section" id="sec-oobe" data-section="oobe" data-group="oobe" aria-labelledby="h-oobe">
<div class="una-phead">
<h3 id="h-oobe">OOBE settings</h3>
<span class="una-hint">"Not set" = not in the file (Windows default)</span>
</div>
<div class="una-pbody">
<div class="row g-3">
{% set oobe_fields = [
("HideEULAPage", "Hide EULA page", ["true", "false"]),
("HideOEMRegistrationScreen", "Hide OEM registration screen", ["true", "false"]),
("HideOnlineAccountScreens", "Hide online account screens", ["true", "false"]),
("HideWirelessSetupInOOBE", "Hide wireless setup", ["true", "false"]),
("HideLocalAccountScreen", "Hide local account screen", ["true", "false"]),
("SkipUserOOBE", "Skip user OOBE", ["true", "false"]),
("SkipMachineOOBE", "Skip machine OOBE", ["true", "false"]),
("NetworkLocation", "Network location", ["Home", "Work", "Other"]),
("ProtectYourPC", "ProtectYourPC", ["1", "2", "3"]),
] %}
{% set oobe_hints = {"ProtectYourPC1": " (recommended)", "ProtectYourPC2": " (updates only)", "ProtectYourPC3": " (skip)"} %}
{% for key, label, opts in oobe_fields %}
<div class="col-lg-4 col-md-6">
<label class="form-label" for="unaOobe_{{ key }}">{{ label }}</label>
<select class="form-select form-select-sm una-field" id="unaOobe_{{ key }}" data-key="oobe.{{ key }}">
<option value="" {% if data.oobe[key] == "" %}selected{% endif %}>Not set</option>
{% for o in opts %}<option value="{{ o }}" {% if data.oobe[key] == o %}selected{% endif %}>{{ o }}{{ oobe_hints.get(key ~ o, '') }}</option>{% endfor %}
{% if data.oobe[key] and data.oobe[key] not in opts %}<option value="{{ data.oobe[key] }}" selected>{{ data.oobe[key] }}</option>{% endif %}
</select>
</div>
{% endfor %}
</div>
</div>
</section>
<section class="panel una-section" id="sec-oobetz" data-section="oobetz" data-group="oobe" aria-labelledby="h-oobetz">
<div class="una-phead">
<h3 id="h-oobetz">OOBE time zone</h3>
<span class="una-hint">oobeSystem pass; separate from the specialize time zone above</span>
</div>
<div class="una-pbody">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="unaOobeTz">Time zone</label>
<input type="text" class="form-control una-field" id="unaOobeTz" data-key="oobe_timezone"
value="{{ data.oobe_timezone }}" placeholder="e.g. Eastern Standard Time">
</div>
</div>
</div>
</section>
<!-- ============= SETUP COMMANDS ============= -->
<h2 class="group-heading una-section" id="grp-commands" data-group="commands">Setup commands</h2>
<details class="panel" style="border-radius: 10px">
<summary class="disc" style="padding: 0 16px; min-height: 44px; color: var(--text); font-weight: 400">
<i class="bi bi-info-circle" aria-hidden="true" style="color: var(--accent)"></i>
<span class="trunc" style="flex: 1 1 auto"><strong>Order matters.</strong> <span class="muted">Each list runs top to bottom as a tested sequence.</span></span>
<span class="chev" aria-hidden="true">&#9662;</span>
</summary>
<p style="margin: 0; padding: 0 16px 14px 42px; font-size: 14px; line-height: 1.6; color: var(--text-2)">
Reorder with Move up and Move down in a command's menu, or by dragging its <i class="bi bi-grip-vertical" aria-label="grip"></i> handle.
Comments shown in italics are XML comments from the file; they move with the command below them.
Limits (one over invalidates the whole pass): specialize command {{ limits.path }} characters,
first logon command {{ limits.commandline }}, description {{ limits.description }}.
</p>
</details>
<div class="una-filter" id="unaFilterBar">
<div class="input-wrap" role="search">
<label for="unaFilter" class="vh">Filter commands</label>
<input type="search" class="field" id="unaFilter"
placeholder="Filter commands (text, description or position)">
</div>
<span class="una-hint" id="unaFilterCount"></span>
</div>
<section class="panel una-section" id="sec-spec" data-section="spec" data-group="commands" aria-labelledby="h-spec">
<div class="una-phead">
<h3 id="h-spec">Specialize commands</h3>
<span class="chip c-neutral" data-count="spec">{{ data.specialize_commands | length }}</span>
<button type="button" class="ui-btn sp una-add" data-list="spec">Add command</button>
</div>
<div class="una-intro">RunSynchronous: run as SYSTEM during the specialize pass, before anyone signs in.</div>
<div class="una-list una-cmds" id="unaSpec" data-list="spec" data-limit="{{ limits.path }}">
{% for cmd in data.specialize_commands %}{{ cmd_row(cmd, loop.index0, 'path', limits.path) }}{% endfor %}
</div>
<div class="una-empty" data-empty="spec" {% if data.specialize_commands %}hidden{% endif %}>No specialize commands.</div>
<div class="una-empty" data-nomatch="spec" hidden>No specialize command matches the filter.</div>
</section>
<section class="panel una-section" id="sec-fl" data-section="fl" data-group="commands" aria-labelledby="h-fl">
<div class="una-phead">
<h3 id="h-fl">First logon commands</h3>
<span class="chip c-neutral" data-count="fl">{{ data.firstlogon_commands | length }}</span>
<button type="button" class="ui-btn sp una-add" data-list="fl">Add command</button>
</div>
<div class="una-intro">FirstLogonCommands: run in the first user session (the AutoLogon account), oobeSystem pass.</div>
<div class="una-list una-cmds" id="unaFl" data-list="fl" data-limit="{{ limits.commandline }}">
{% for cmd in data.firstlogon_commands %}{{ cmd_row(cmd, loop.index0, 'commandline', limits.commandline) }}{% endfor %}
</div>
<div class="una-empty" data-empty="fl" {% if data.firstlogon_commands %}hidden{% endif %}>No first logon commands.</div>
<div class="una-empty" data-nomatch="fl" hidden>No first logon command matches the filter.</div>
</section>
</div><!-- una-main -->
</div><!-- una-layout -->
</div><!-- formView -->
<!-- ==================== RAW XML VIEW ==================== -->
<div class="tab-pane fade" id="unaRawView" role="tabpanel" aria-labelledby="unaRawTab">
<section class="panel" aria-labelledby="h-raw">
<div class="una-phead">
<h3 id="h-raw">Raw XML</h3>
<span class="una-hint">Review changes shows the diff of this text against the file</span>
</div>
<div class="una-pbody">
<p class="una-hint" style="margin: 0 0 10px">
For what the form does not cover. This text is written exactly as it stands; edits made on the
Form view are not part of a Raw XML save (and the other way round).
</p>
<textarea class="form-control raw-xml-editor" id="unaRawXml" spellcheck="false"
aria-label="Raw XML">{{ data.raw_xml }}</textarea>
</div>
</section>
</div>
</div><!-- tab-content -->
<div class="savebar" role="region" aria-label="Unsaved changes">
<span class="dirty-flag" id="unaDirty" role="status"><span class="dot" aria-hidden="true"></span> Unsaved changes <span class="sub">Ctrl+S reviews</span></span>
<button type="button" class="ui-btn pri" id="unaReview" disabled title="Ctrl+S">Review changes</button>
</div>
</div><!-- unaPage -->
{# Row templates for Add #}
<template id="unaTplCmd">
<div class="una-row is-new">
<div class="una-side">
<span class="una-pos"></span>
<span class="una-grip" title="Drag to reorder" aria-hidden="true"><i class="bi bi-grip-vertical"></i></span>
</div>
<div class="una-body">
<div class="una-head">
<input type="text" class="form-control form-control-sm una-desc" placeholder="Description" aria-label="Description" data-limit="{{ limits.description }}">
<span class="una-tag"><span class="chip c-edit">New</span><span class="una-count" data-for="cmd"></span></span>
</div>
<textarea class="form-control una-cmd" rows="1" spellcheck="false" aria-label="Command" placeholder="Command"></textarea>
<div class="una-foot"><span class="una-count" data-for="desc"></span></div>
</div>
<div class="una-actions">{{ row_menu('new command') }}</div>
</div>
</template>
<template id="unaTplDp">
<div class="una-simple is-new">
<span class="una-pos"></span>
<input type="text" class="form-control form-control-sm mono una-dp" placeholder="e.g. W:\Drivers" aria-label="Driver path">
<button type="button" class="ui-btn ghost sm una-remove" aria-label="Remove this driver path">Remove</button>
</div>
</template>
<template id="unaTplAcct">
<div class="una-simple is-new" data-plain="true">
<span class="una-pos"></span>
<div class="una-acct-fields">
<div><label>Name <input type="text" class="form-control form-control-sm" data-f="name"></label></div>
<div><label>Password <input type="text" class="form-control form-control-sm mono" data-f="password" autocomplete="off"></label></div>
<div><label>Group <input type="text" class="form-control form-control-sm" data-f="group" value="Administrators"></label></div>
<div><label>Display name <input type="text" class="form-control form-control-sm" data-f="display_name"></label></div>
</div>
<button type="button" class="ui-btn ghost sm una-remove" aria-label="Remove this account">Remove</button>
</div>
</template>
{% endblock %}
{% block extra_scripts %}
<script>
window.PXE_UNATTEND = {
previewUrl: "{{ url_for('images_unattend.preview', image_type=image_type) }}",
saveUrl: "{{ url_for('images_unattend.save_route', image_type=image_type) }}",
baseSha: "{{ file_sha }}"
};
</script>
<script src="{{ url_for('static', filename='unattend-editor.js') }}"></script>
{% endblock %}