Updates group: - Dell drivers and BIOS: check Dell's catalogs against all three driver maps and models.txt, download with a shared speed cap, repack, preview, apply with backups and rollback. - iPXE boot files: check GitHub for the signed Secure Boot set, verify the sha256 and each file's signer, install atomically, roll back. Includes the by-hand procedure. - Blancco: upload a release, stage it, test-boot it from the menu, activate, roll back. - BPRT sync: validate a batch uploaded from the MediaCreator Lite cache (scripts/windows/Sync-BPRT.ps1 or a zip), show the diff, swap with the site overlay files kept. Images: one sidebar entry per image with Overview, Drivers, Unattend and OS and Packages tabs. Every save shows a diff first, keeps the file's format, makes a backup and can be undone. Driver lint runs inline. Enrollment: upload standard/engineer MCL packages, one per tenant/purpose, with archive and restore. Hardware coverage counts engineer as Win11.
648 lines
36 KiB
HTML
648 lines
36 KiB
HTML
{% extends "base.html" %}
|
|
{% 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.
|
|
#}
|
|
|
|
{% block extra_head %}
|
|
<style>
|
|
.una-bar {
|
|
display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center;
|
|
justify-content: space-between; margin-bottom: 1rem;
|
|
}
|
|
.una-bar .nav-pills .nav-link {
|
|
font-size: 12px; padding: 0.4rem 0.9rem; display: flex; align-items: center; gap: 0.4rem;
|
|
color: var(--text-light); border: 1px solid var(--border); background: transparent;
|
|
}
|
|
.una-bar .nav-pills .nav-item + .nav-item .nav-link { margin-left: 0.4rem; }
|
|
.una-bar .nav-pills .nav-link.active { background: var(--primary); border-color: var(--primary); color: #fff; }
|
|
.una-meta { font-size: 12.5px; color: var(--text-light); display: flex; flex-wrap: wrap; gap: 0.25rem 0.75rem; align-items: center; }
|
|
.una-meta .badge { font-size: 10.5px; }
|
|
.una-lint-list { font-size: 13px; margin: 0.5rem 0 0; padding-left: 1.1rem; }
|
|
.una-lint-list li { margin-bottom: 0.25rem; }
|
|
|
|
/* 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 -10px 1rem; padding: 0.5rem 10px; border-bottom: 1px solid var(--border);
|
|
}
|
|
.una-index ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
|
.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: 0.4rem; text-decoration: none;
|
|
font-size: 12.5px; color: var(--text-light); padding: 0.25rem 0.6rem;
|
|
border: 1px solid var(--border); border-radius: 999px; white-space: nowrap;
|
|
}
|
|
.una-index a:hover { color: var(--text); border-color: var(--primary); }
|
|
.una-index a.active { color: var(--primary); border-color: var(--primary); }
|
|
.una-index .count { font-size: 10px; font-weight: 600; color: var(--text-light); }
|
|
.una-index a.has-edits::after {
|
|
content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--warning);
|
|
}
|
|
@media (min-width: 1280px) {
|
|
.una-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 1.5rem; align-items: start; }
|
|
.una-index {
|
|
top: 1rem; margin: 0; padding: 0; border-bottom: 0; background: transparent;
|
|
max-height: calc(100vh - 2rem); 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.ix-group {
|
|
font-size: 11px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
|
|
margin-top: 0.9rem; border-left-color: transparent !important; background: transparent !important;
|
|
}
|
|
.una-index li:first-child a.ix-group { margin-top: 0; }
|
|
.una-index a {
|
|
border: 0; border-left: 2px solid transparent; border-radius: 0;
|
|
padding: 0.3rem 0.6rem; justify-content: flex-start;
|
|
}
|
|
.una-index a.active { border-left-color: var(--primary); background: rgba(65, 129, 255, 0.08); }
|
|
.una-index a.has-edits::after { margin-left: 0.4rem; flex: none; }
|
|
.una-index a .count { margin-left: auto; }
|
|
}
|
|
.una-main { min-width: 0; }
|
|
.una-section { scroll-margin-top: 4.5rem; }
|
|
@media (min-width: 1280px) { .una-section { scroll-margin-top: 1rem; } }
|
|
|
|
.group-heading {
|
|
font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px; font-weight: 600;
|
|
color: var(--text-light); margin: 1.75rem 0 0.9rem; padding-bottom: 0.4rem;
|
|
border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 0.5rem;
|
|
}
|
|
.una-main > .group-heading:first-child { margin-top: 0; }
|
|
.group-heading .bi { color: var(--primary); }
|
|
.card-header .title-group { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; }
|
|
.card-header .title-group .bi { color: var(--primary); }
|
|
.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;
|
|
}
|
|
.una-hint { font-size: 12.5px; color: var(--text-light); }
|
|
.una-empty { text-align: center; color: var(--text-light); padding: 1rem; font-size: 13.5px; }
|
|
|
|
/* Command filter: stays in view while the lists scroll (wide layout). */
|
|
.una-filter {
|
|
display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; align-items: center;
|
|
background: var(--bg); padding: 0.5rem 0; margin-bottom: 0.75rem;
|
|
}
|
|
@media (min-width: 1280px) { .una-filter { position: sticky; top: 0; z-index: 15; } }
|
|
.una-filter .input-wrap { position: relative; flex: 1 1 18rem; max-width: 34rem; }
|
|
.una-filter .input-wrap .bi { position: absolute; left: 0.7rem; top: 50%; transform: translateY(-50%); color: var(--text-light); }
|
|
.una-filter input { padding-left: 2rem; }
|
|
.una-filter .una-hint strong { color: var(--text); }
|
|
|
|
/* One command: number + grip | description, full command, counters | actions */
|
|
.una-list { border-top: 1px solid var(--border); }
|
|
.una-list:empty { border-top: 0; }
|
|
.una-row {
|
|
display: grid; grid-template-columns: 2.4rem minmax(0, 1fr) auto; gap: 0.6rem;
|
|
padding: 0.6rem 0.9rem 0.6rem 0.4rem; border-bottom: 1px solid var(--border);
|
|
border-left: 3px solid transparent; transition: background-color 0.15s;
|
|
}
|
|
.una-row:last-child { border-bottom: 0; }
|
|
.una-row.is-edited { border-left-color: var(--warning); }
|
|
.una-row.is-new { border-left-color: var(--success); }
|
|
.una-row.dragging { opacity: 0.45; }
|
|
.una-row.drop-before { box-shadow: inset 0 2px 0 var(--primary); }
|
|
.una-row.drop-after { box-shadow: inset 0 -2px 0 var(--primary); }
|
|
.una-side { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding-top: 0.2rem; }
|
|
.una-pos { font-weight: 600; color: var(--text-light); font-size: 13px; line-height: 1; }
|
|
.una-grip { color: var(--text-light); cursor: grab; padding: 0.1rem 0.3rem; border-radius: 4px; }
|
|
.una-grip:hover { color: var(--text); background: var(--bg); }
|
|
.una-body { min-width: 0; display: flex; flex-direction: column; gap: 0.4rem; }
|
|
.una-note {
|
|
font-size: 12.5px; color: var(--text-light); font-style: italic;
|
|
display: flex; gap: 0.4rem; align-items: baseline;
|
|
}
|
|
.una-note .bi { font-style: normal; flex: none; }
|
|
.una-head { display: flex; gap: 0.5rem; align-items: center; }
|
|
.una-head .una-desc { flex: 1 1 auto; font-weight: 500; }
|
|
.una-tag { font-size: 11px; color: var(--text-light); white-space: nowrap; display: flex; flex-direction: column; align-items: flex-end; line-height: 1.35; min-width: 5.5rem; }
|
|
textarea.una-cmd {
|
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; font-size: 12.5px; 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: 0.25rem 1rem; font-size: 11.5px; color: var(--text-light); }
|
|
.una-foot[hidden] { display: none; }
|
|
.una-inner { font-style: italic; }
|
|
.una-count.near { color: var(--warning-dark); }
|
|
[data-theme="dark"] .una-count.near { color: var(--warning); }
|
|
.una-count.over { color: var(--danger); font-weight: 600; }
|
|
.una-actions { display: flex; flex-direction: column; gap: 0.3rem; }
|
|
.una-actions .btn { padding: 6px 9px; }
|
|
.filtering .una-grip, .filtering .una-up, .filtering .una-down { opacity: 0.3; pointer-events: none; }
|
|
.una-row[hidden] { display: none; }
|
|
|
|
/* Short rows (driver paths, accounts) */
|
|
.una-simple { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr) auto; gap: 0.6rem; align-items: center; padding: 0.55rem 1rem 0.55rem 0.5rem; border-bottom: 1px solid var(--border); border-left: 3px solid transparent; }
|
|
.una-simple:last-child { border-bottom: 0; }
|
|
.una-simple.is-edited { border-left-color: var(--warning); }
|
|
.una-simple.is-new { border-left-color: var(--success); }
|
|
.una-simple .una-pos { text-align: center; }
|
|
.una-acct-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.5rem; }
|
|
@media (max-width: 1100px) { .una-acct-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
.una-acct-fields label { font-size: 11px; color: var(--text-light); margin-bottom: 0.15rem; display: block; }
|
|
|
|
.una-field-changed { border-color: var(--warning) !important; }
|
|
|
|
.raw-xml-editor {
|
|
font-family: 'SF Mono', 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.85rem; 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; }
|
|
.una-review-lint .alert { font-size: 13.5px; }
|
|
.una-lint-ok { font-size: 13px; color: var(--text-light); display: flex; gap: 0.4rem; align-items: center; }
|
|
.una-lint-ok .bi { color: var(--success-dark); }
|
|
[data-theme="dark"] .una-lint-ok .bi { color: var(--success); }
|
|
.una-review-xml summary { cursor: pointer; font-size: 13px; color: var(--text-light); margin-top: 1rem; }
|
|
.una-review-xml pre {
|
|
max-height: 50vh; overflow: auto; font-size: 12px; padding: 0.75rem; margin: 0.5rem 0 0;
|
|
background: var(--bg-card-solid); border: 1px solid var(--border); border-radius: 0.25rem; white-space: pre;
|
|
}
|
|
.dirty-flag { font-size: 12px; color: var(--warning-dark); display: none; align-items: center; gap: 0.35rem; }
|
|
[data-theme="dark"] .dirty-flag { color: var(--warning); }
|
|
.dirty .dirty-flag { display: inline-flex; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% macro note(item) -%}
|
|
{% if item.note %}<div class="una-note"><i class="bi bi-chat-left-text"></i><span>{{ item.note | join(' | ') }}</span></div>{% endif %}
|
|
{%- 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"><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"></i> {{ cmd.inner_note | join(' | ') | truncate(160) }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="una-actions">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm una-up" title="Move up"><i class="bi bi-arrow-up"></i></button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm una-down" title="Move down"><i class="bi bi-arrow-down"></i></button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></button>
|
|
</div>
|
|
</div>
|
|
{%- endmacro %}
|
|
|
|
{% block content %}
|
|
{% set header_subtitle = 'Deploy/FlatUnattendW10.xml' %}
|
|
{% set header_actions %}
|
|
<span class="dirty-flag" id="unaDirty"><i class="bi bi-circle-fill" style="font-size:8px;"></i> Unsaved changes</span>
|
|
{% 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="btn btn-outline-secondary btn-sm" title="Last save: {{ last_save.ts | local_dt }} ({{ last_save.action }})">
|
|
<i class="bi bi-arrow-counterclockwise"></i> Undo last save
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-success btn-sm" id="unaReview" disabled title="Ctrl+S">
|
|
<i class="bi bi-file-earmark-diff"></i> Review changes
|
|
</button>
|
|
{% endset %}
|
|
{% include "_image_header.html" %}
|
|
|
|
{% if unattend_override %}
|
|
<div class="alert alert-warning">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
<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.
|
|
</div>
|
|
{% endif %}
|
|
{% if not file_exists %}
|
|
<div class="alert alert-info">
|
|
<i class="bi bi-info-circle"></i>
|
|
This image has no <span class="mono">Deploy/FlatUnattendW10.xml</span> yet. The form starts from an empty
|
|
answer file; saving creates it.
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set lint_errors = lint | selectattr('level', 'equalto', 'ERROR') | list %}
|
|
<div class="una-bar">
|
|
<ul class="nav nav-pills" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" id="unaFormTab" data-bs-toggle="pill" href="#unaFormView" role="tab">
|
|
<i class="bi bi-ui-checks-grid"></i> Form
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="unaRawTab" data-bs-toggle="pill" href="#unaRawView" role="tab">
|
|
<i class="bi bi-code-slash"></i> 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="badge badge-success" title="The checks of scripts/lint-unattend.py">Lint clean</span>
|
|
{% elif lint_errors %}
|
|
<a href="#unaLint" data-bs-toggle="collapse" class="badge badge-danger text-decoration-none">{{ lint_errors | length }} lint error{{ '' if lint_errors | length == 1 else 's' }}</a>
|
|
{% else %}
|
|
<a href="#unaLint" data-bs-toggle="collapse" class="badge badge-warning text-decoration-none">{{ 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">
|
|
<div class="alert {% if lint_errors %}alert-danger{% else %}alert-warning{% endif %}">
|
|
<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 %}
|
|
<ul class="una-lint-list">
|
|
{% for f in lint %}<li><span class="badge {{ 'badge-danger' if f.level == 'ERROR' else 'badge-warning' }}">{{ f.level }}</span> {{ f.message }}</li>{% endfor %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<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 ============= -->
|
|
<div class="group-heading una-section" id="grp-system" data-group="system"><i class="bi bi-pc-display-horizontal"></i> System and regional</div>
|
|
|
|
<section class="card una-section" id="sec-machine" data-section="machine" data-group="system">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-hdd-network"></i> Machine settings</span>
|
|
<span class="una-hint">specialize pass, Microsoft-Windows-Shell-Setup</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<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="card una-section" id="sec-intl" data-section="intl" data-group="system">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-globe2"></i> International</span>
|
|
<span class="una-hint">oobeSystem pass, Microsoft-Windows-International-Core</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<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="card una-section" id="sec-driverpaths" data-section="driverpaths" data-group="system">
|
|
<div class="card-header">
|
|
<span class="title-group">
|
|
<i class="bi bi-usb-drive"></i> Driver paths
|
|
<span class="count-pill" data-count="driverpaths">{{ data.driver_paths | length }}</span>
|
|
</span>
|
|
<button type="button" class="btn btn-sm btn-outline-primary una-add" data-list="driverpaths">
|
|
<i class="bi bi-plus-lg"></i> Add
|
|
</button>
|
|
</div>
|
|
<div class="una-hint px-3 pt-2 pb-2">
|
|
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">
|
|
<button type="button" class="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></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 ============= -->
|
|
<div class="group-heading una-section" id="grp-accounts" data-group="accounts"><i class="bi bi-people"></i> Accounts and sign-in</div>
|
|
|
|
<section class="card una-section" id="sec-accounts" data-section="accounts" data-group="accounts">
|
|
<div class="card-header">
|
|
<span class="title-group">
|
|
<i class="bi bi-person-badge"></i> User accounts (local)
|
|
<span class="count-pill" data-count="accounts">{{ data.user_accounts | length }}</span>
|
|
</span>
|
|
<button type="button" class="btn btn-sm btn-outline-primary una-add" data-list="accounts">
|
|
<i class="bi bi-plus-lg"></i> Add
|
|
</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>Name</label><input type="text" class="form-control form-control-sm" data-f="name" value="{{ acct.name }}"></div>
|
|
<div><label>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" data-f="password" value="{{ acct.password }}" autocomplete="off"></div>
|
|
<div><label>Group</label><input type="text" class="form-control form-control-sm" data-f="group" value="{{ acct.group }}"></div>
|
|
<div><label>Display name</label><input type="text" class="form-control form-control-sm" data-f="display_name" value="{{ acct.display_name }}"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="una-empty" data-empty="accounts" {% if data.user_accounts %}hidden{% endif %}>No local accounts.</div>
|
|
<div class="card-footer una-hint">
|
|
<i class="bi bi-shield-lock"></i> Passwords sit in the answer file as plain text. Treat this file as sensitive.
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card una-section" id="sec-autologon" data-section="autologon" data-group="accounts">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-box-arrow-in-right"></i> AutoLogon</span>
|
|
<span class="una-hint">clear the user name to remove AutoLogon</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<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 ============= -->
|
|
<div class="group-heading una-section" id="grp-oobe" data-group="oobe"><i class="bi bi-window-desktop"></i> Out-of-box experience (OOBE)</div>
|
|
|
|
<section class="card una-section" id="sec-oobe" data-section="oobe" data-group="oobe">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-toggles"></i> OOBE settings</span>
|
|
<span class="una-hint">"Not set" = not in the file (Windows default)</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<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="card una-section" id="sec-oobetz" data-section="oobetz" data-group="oobe">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-clock-history"></i> OOBE time zone</span>
|
|
<span class="una-hint">oobeSystem pass; separate from the specialize time zone above</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<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 ============= -->
|
|
<div class="group-heading una-section" id="grp-commands" data-group="commands"><i class="bi bi-terminal"></i> Setup commands</div>
|
|
|
|
<div class="una-filter" id="unaFilterBar">
|
|
<div class="input-wrap">
|
|
<i class="bi bi-search"></i>
|
|
<input type="search" class="form-control form-control-sm" id="unaFilter"
|
|
placeholder="Filter commands (text, description or position)" aria-label="Filter commands">
|
|
</div>
|
|
<span class="una-hint" id="unaFilterCount"></span>
|
|
</div>
|
|
|
|
<div class="alert alert-warning">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
Each list runs top to bottom as a tested sequence; <strong>order matters</strong>. Reorder with the arrows
|
|
or by dragging the <i class="bi bi-grip-vertical"></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 }}.
|
|
</div>
|
|
|
|
<section class="card una-section" id="sec-spec" data-section="spec" data-group="commands">
|
|
<div class="card-header">
|
|
<span class="title-group">
|
|
<i class="bi bi-gear-wide-connected"></i> Specialize commands
|
|
<span class="count-pill" data-count="spec">{{ data.specialize_commands | length }}</span>
|
|
</span>
|
|
<button type="button" class="btn btn-sm btn-outline-primary una-add" data-list="spec">
|
|
<i class="bi bi-plus-lg"></i> Add
|
|
</button>
|
|
</div>
|
|
<div class="una-hint px-3 pt-2 pb-2">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="card una-section" id="sec-fl" data-section="fl" data-group="commands">
|
|
<div class="card-header">
|
|
<span class="title-group">
|
|
<i class="bi bi-play-circle"></i> First logon commands
|
|
<span class="count-pill" data-count="fl">{{ data.firstlogon_commands | length }}</span>
|
|
</span>
|
|
<button type="button" class="btn btn-sm btn-outline-primary una-add" data-list="fl">
|
|
<i class="bi bi-plus-lg"></i> Add
|
|
</button>
|
|
</div>
|
|
<div class="una-hint px-3 pt-2 pb-2">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">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<span class="title-group"><i class="bi bi-filetype-xml"></i> Raw XML</span>
|
|
<span class="una-hint">Review changes shows the diff of this text against the file</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="una-hint mb-2">
|
|
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>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- tab-content -->
|
|
|
|
{# 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"><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="badge badge-success">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">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm una-up" title="Move up"><i class="bi bi-arrow-up"></i></button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm una-down" title="Move down"><i class="bi bi-arrow-down"></i></button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></button>
|
|
</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="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></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</label><input type="text" class="form-control form-control-sm" data-f="name"></div>
|
|
<div><label>Password</label><input type="text" class="form-control form-control-sm mono" data-f="password" autocomplete="off"></div>
|
|
<div><label>Group</label><input type="text" class="form-control form-control-sm" data-f="group" value="Administrators"></div>
|
|
<div><label>Display name</label><input type="text" class="form-control form-control-sm" data-f="display_name"></div>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-danger btn-sm una-remove" title="Remove"><i class="bi bi-trash"></i></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 %}
|