webapp: nest shopfloor menu as a tab in the Boot Menu editor; rename nav to Boot Menu
The shopfloor sub-menu editor is now a 'Shopfloor Sub-Menu' tab inside the startnet editor (it IS a sub-menu of the boot menu), not a separate page - sf-prefixed IDs avoid clashing with the top-level Boot Menu tab. /shopfloor-menu GET + save now redirect back into that tab; standalone shopfloor_menu.html removed. Sidebar 'startnet.cmd' renamed to 'Boot Menu'.
This commit is contained in:
@@ -1,99 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Shopfloor Boot Menu - PXE Server Manager{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-list-ol"></i> Shopfloor Boot Menu</h1>
|
||||
<div class="header-actions">
|
||||
<button type="button" class="btn btn-secondary" id="addRow"><i class="bi bi-plus-lg"></i> Add item</button>
|
||||
<button type="submit" form="menuForm" class="btn btn-primary"><i class="bi bi-save"></i> Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
This is the <strong>data-driven GEA Shopfloor sub-menu</strong> WinPE shows at imaging
|
||||
(<span class="mono">menu.json</span> on the enrollment share). Reorder, rename, or hide entries -
|
||||
no boot.wim edit. Each item's <strong>PC-type</strong> must be an existing
|
||||
<span class="mono">shopfloor-setup/gea-shopfloor-*</span> handler (dropdown below); WinPE falls back to the
|
||||
baked-in menu if the share/picker is unavailable.
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-container">
|
||||
<table class="data-table" id="menuTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:3rem;">#</th>
|
||||
<th>Label</th>
|
||||
<th>PC-type (key)</th>
|
||||
<th>Hint</th>
|
||||
<th style="width:5rem;">Shown</th>
|
||||
<th style="width:8rem;">Order</th>
|
||||
<th style="width:3rem;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="menuBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="menuForm" method="post" action="{{ url_for('shopfloor_menu') }}">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="items" id="itemsField">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const AVAILABLE = {{ available | tojson }};
|
||||
const ITEMS = {{ items | tojson }};
|
||||
|
||||
const body = document.getElementById('menuBody');
|
||||
|
||||
function optionList(sel) {
|
||||
return AVAILABLE.map(k => `<option value="${k}" ${k === sel ? 'selected' : ''}>${k}</option>`).join('');
|
||||
}
|
||||
|
||||
function makeRow(it) {
|
||||
const tr = document.createElement('tr');
|
||||
const key = it.key || (AVAILABLE[0] || '');
|
||||
tr.innerHTML =
|
||||
`<td class="rownum mono"></td>` +
|
||||
`<td><input class="form-control f-label" value="${(it.label || '').replace(/"/g, '"')}" placeholder="Menu label"></td>` +
|
||||
`<td><select class="form-select f-key">${optionList(key)}</select></td>` +
|
||||
`<td><input class="form-control f-hint" value="${(it.hint || '').replace(/"/g, '"')}" placeholder="(optional)"></td>` +
|
||||
`<td style="text-align:center;"><input type="checkbox" class="f-enabled" ${it.enabled === false ? '' : 'checked'}></td>` +
|
||||
`<td><button type="button" class="btn btn-sm btn-secondary mv-up" title="Up"><i class="bi bi-arrow-up"></i></button> ` +
|
||||
`<button type="button" class="btn btn-sm btn-secondary mv-down" title="Down"><i class="bi bi-arrow-down"></i></button></td>` +
|
||||
`<td><button type="button" class="btn btn-sm btn-danger rm" title="Remove"><i class="bi bi-trash"></i></button></td>`;
|
||||
return tr;
|
||||
}
|
||||
|
||||
function renumber() {
|
||||
[...body.rows].forEach((r, i) => r.querySelector('.rownum').textContent = i + 1);
|
||||
}
|
||||
|
||||
function addRow(it) { body.appendChild(makeRow(it || {})); renumber(); }
|
||||
|
||||
body.addEventListener('click', e => {
|
||||
const tr = e.target.closest('tr');
|
||||
if (!tr) return;
|
||||
if (e.target.closest('.rm')) { tr.remove(); renumber(); }
|
||||
else if (e.target.closest('.mv-up') && tr.previousElementSibling) { tr.parentNode.insertBefore(tr, tr.previousElementSibling); renumber(); }
|
||||
else if (e.target.closest('.mv-down') && tr.nextElementSibling) { tr.parentNode.insertBefore(tr.nextElementSibling, tr); renumber(); }
|
||||
});
|
||||
|
||||
document.getElementById('addRow').addEventListener('click', () => addRow({}));
|
||||
|
||||
document.getElementById('menuForm').addEventListener('submit', () => {
|
||||
const items = [...body.rows].map(r => ({
|
||||
label: r.querySelector('.f-label').value.trim(),
|
||||
key: r.querySelector('.f-key').value,
|
||||
hint: r.querySelector('.f-hint').value.trim(),
|
||||
enabled: r.querySelector('.f-enabled').checked,
|
||||
})).filter(x => x.label && x.key);
|
||||
document.getElementById('itemsField').value = JSON.stringify(items);
|
||||
});
|
||||
|
||||
(ITEMS.length ? ITEMS : []).forEach(addRow);
|
||||
if (!body.rows.length) addRow({});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user