webapp: shopfloor sub-menu tab + nav rename (the edits dropped by a failed git add)
The prior 'nest shopfloor menu as a tab' commit only captured the shopfloor_menu.html deletion - the git add also named the just-removed file, which errored the add and staged nothing else. This commits the actual integration that was already deployed to preview and prod: startnet_editor GET passes shopfloor_items/shopfloor_available, the Shopfloor Sub-Menu tab pane + sf-prefixed JS in startnet_editor.html, /shopfloor-menu redirects into the tab, and the sidebar 'startnet.cmd' -> 'Boot Menu' rename.
This commit is contained in:
@@ -1026,6 +1026,8 @@ def startnet_editor():
|
|||||||
boot_menu=boot_menu,
|
boot_menu=boot_menu,
|
||||||
lint=lint,
|
lint=lint,
|
||||||
snapshots=snapshots,
|
snapshots=snapshots,
|
||||||
|
shopfloor_items=read_shopfloor_menu(),
|
||||||
|
shopfloor_available=available_pctypes(),
|
||||||
image_types=config.IMAGE_TYPES,
|
image_types=config.IMAGE_TYPES,
|
||||||
friendly_names=config.FRIENDLY_NAMES,
|
friendly_names=config.FRIENDLY_NAMES,
|
||||||
)
|
)
|
||||||
@@ -1225,7 +1227,7 @@ def shopfloor_menu():
|
|||||||
items = json.loads(request.form.get("items", "[]"))
|
items = json.loads(request.form.get("items", "[]"))
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
flash("Invalid menu payload.", "danger")
|
flash("Invalid menu payload.", "danger")
|
||||||
return redirect(url_for("shopfloor_menu"))
|
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
|
||||||
valid = set(available_pctypes())
|
valid = set(available_pctypes())
|
||||||
clean = []
|
clean = []
|
||||||
for it in items:
|
for it in items:
|
||||||
@@ -1246,7 +1248,7 @@ def shopfloor_menu():
|
|||||||
})
|
})
|
||||||
if not clean:
|
if not clean:
|
||||||
flash("Refused to save an empty menu.", "danger")
|
flash("Refused to save an empty menu.", "danger")
|
||||||
return redirect(url_for("shopfloor_menu"))
|
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
|
||||||
try:
|
try:
|
||||||
os.makedirs(SHOPFLOOR_SETUP_DIR, exist_ok=True)
|
os.makedirs(SHOPFLOOR_SETUP_DIR, exist_ok=True)
|
||||||
with open(SHOPFLOOR_MENU_JSON, "w") as fh:
|
with open(SHOPFLOOR_MENU_JSON, "w") as fh:
|
||||||
@@ -1255,15 +1257,9 @@ def shopfloor_menu():
|
|||||||
flash("Shopfloor boot menu saved - WinPE reads it at next boot.", "success")
|
flash("Shopfloor boot menu saved - WinPE reads it at next boot.", "success")
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
flash(f"Could not write menu.json: {exc}", "danger")
|
flash(f"Could not write menu.json: {exc}", "danger")
|
||||||
return redirect(url_for("shopfloor_menu"))
|
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
|
||||||
|
|
||||||
return render_template(
|
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
|
||||||
"shopfloor_menu.html",
|
|
||||||
items=read_shopfloor_menu(),
|
|
||||||
available=available_pctypes(),
|
|
||||||
image_types=config.IMAGE_TYPES,
|
|
||||||
friendly_names=config.FRIENDLY_NAMES,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -58,15 +58,9 @@
|
|||||||
<div class="nav-section">Tools</div>
|
<div class="nav-section">Tools</div>
|
||||||
<ul class="sidebar-nav nav flex-column">
|
<ul class="sidebar-nav nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if request.endpoint == 'startnet_editor' %}active{% endif %}"
|
<a class="nav-link {% if request.endpoint in ['startnet_editor', 'shopfloor_menu'] %}active{% endif %}"
|
||||||
href="{{ url_for('startnet_editor') }}">
|
href="{{ url_for('startnet_editor') }}">
|
||||||
<i class="bi bi-terminal"></i> startnet.cmd
|
<i class="bi bi-list-ol"></i> Boot Menu
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link {% if request.endpoint == 'shopfloor_menu' %}active{% endif %}"
|
|
||||||
href="{{ url_for('shopfloor_menu') }}">
|
|
||||||
<i class="bi bi-list-ol"></i> Shopfloor Menu
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
@@ -174,6 +174,11 @@
|
|||||||
<i class="bi bi-list-ol"></i> Boot Menu
|
<i class="bi bi-list-ol"></i> Boot Menu
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="tab-shopfloor-btn" data-bs-toggle="tab" data-bs-target="#tab-shopfloor" type="button" role="tab">
|
||||||
|
<i class="bi bi-diagram-3"></i> Shopfloor Sub-Menu
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="tab-raw-btn" data-bs-toggle="tab" data-bs-target="#tab-raw" type="button" role="tab">
|
<button class="nav-link" id="tab-raw-btn" data-bs-toggle="tab" data-bs-target="#tab-raw" type="button" role="tab">
|
||||||
<i class="bi bi-code-slash"></i> Raw
|
<i class="bi bi-code-slash"></i> Raw
|
||||||
@@ -300,6 +305,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============================ SHOPFLOOR SUB-MENU ======================== -->
|
||||||
|
<div class="tab-pane fade" id="tab-shopfloor" role="tabpanel">
|
||||||
|
<div class="card">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
The <strong>GEA Shopfloor</strong> choice drills into this data-driven sub-menu
|
||||||
|
(<span class="stn-mono">menu.json</span> on the enrollment share). Reorder / rename / hide / add entries -
|
||||||
|
no boot.wim edit. Each <strong>PC-type</strong> must be an existing
|
||||||
|
<span class="stn-mono">shopfloor-setup/gea-shopfloor-*</span> handler (dropdown); WinPE falls back to the
|
||||||
|
baked-in menu if the share/picker is unavailable.
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:0.75rem;">
|
||||||
|
<button type="button" class="pxe-btn pxe-btn-secondary pxe-btn-sm" id="sfAddBtn"><i class="bi bi-plus-lg"></i> Add item</button>
|
||||||
|
<button type="submit" form="sfForm" class="pxe-btn pxe-btn-primary pxe-btn-sm"><i class="bi bi-save"></i> Save shopfloor menu</button>
|
||||||
|
</div>
|
||||||
|
<form action="{{ url_for('shopfloor_menu') }}" method="post" id="sfForm">
|
||||||
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="items" id="sfItemsData" value="[]">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="data-table" id="sfTable" style="white-space:normal;">
|
||||||
|
<thead>
|
||||||
|
<tr><th style="width:3rem;">#</th><th>Label</th><th>PC-type (key)</th><th>Hint</th><th style="width:4rem;">Shown</th><th style="width:7rem;">Order</th><th style="width:3rem;"></th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="sfBody"><!-- rows injected by JS --></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ================================= RAW ================================= -->
|
<!-- ================================= RAW ================================= -->
|
||||||
<div class="tab-pane fade" id="tab-raw" role="tabpanel">
|
<div class="tab-pane fade" id="tab-raw" role="tabpanel">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -745,4 +779,42 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const SF_AVAIL = {{ shopfloor_available | tojson }};
|
||||||
|
const SF_ITEMS = {{ shopfloor_items | tojson }};
|
||||||
|
const body = document.getElementById('sfBody');
|
||||||
|
if (!body) return;
|
||||||
|
const opts = sel => SF_AVAIL.map(k=>`<option value="${k}" ${k===sel?'selected':''}>${k}</option>`).join('');
|
||||||
|
function row(it){
|
||||||
|
const tr=document.createElement('tr');
|
||||||
|
const key=it.key||(SF_AVAIL[0]||'');
|
||||||
|
tr.innerHTML =
|
||||||
|
`<td class="sf-n stn-mono"></td>`+
|
||||||
|
`<td><input class="form-control sf-label" value="${(it.label||'').replace(/"/g,'"')}" placeholder="Menu label"></td>`+
|
||||||
|
`<td><select class="form-select sf-key">${opts(key)}</select></td>`+
|
||||||
|
`<td><input class="form-control sf-hint" value="${(it.hint||'').replace(/"/g,'"')}" placeholder="(optional)"></td>`+
|
||||||
|
`<td style="text-align:center;"><input type="checkbox" class="sf-en" ${it.enabled===false?'':'checked'}></td>`+
|
||||||
|
`<td><button type="button" class="pxe-btn pxe-btn-sm pxe-btn-secondary sf-up" title="Up"><i class="bi bi-arrow-up"></i></button> `+
|
||||||
|
`<button type="button" class="pxe-btn pxe-btn-sm pxe-btn-secondary sf-down" title="Down"><i class="bi bi-arrow-down"></i></button></td>`+
|
||||||
|
`<td><button type="button" class="pxe-btn pxe-btn-sm pxe-btn-danger sf-rm" title="Remove"><i class="bi bi-trash"></i></button></td>`;
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
const renum=()=>[...body.rows].forEach((r,i)=>r.querySelector('.sf-n').textContent=i+1);
|
||||||
|
const add=it=>{body.appendChild(row(it||{}));renum();};
|
||||||
|
body.addEventListener('click',e=>{
|
||||||
|
const tr=e.target.closest('tr'); if(!tr)return;
|
||||||
|
if(e.target.closest('.sf-rm')){tr.remove();renum();}
|
||||||
|
else if(e.target.closest('.sf-up')&&tr.previousElementSibling){tr.parentNode.insertBefore(tr,tr.previousElementSibling);renum();}
|
||||||
|
else if(e.target.closest('.sf-down')&&tr.nextElementSibling){tr.parentNode.insertBefore(tr.nextElementSibling,tr);renum();}
|
||||||
|
});
|
||||||
|
document.getElementById('sfAddBtn').addEventListener('click',()=>add({}));
|
||||||
|
document.getElementById('sfForm').addEventListener('submit',()=>{
|
||||||
|
const items=[...body.rows].map(r=>({label:r.querySelector('.sf-label').value.trim(),key:r.querySelector('.sf-key').value,hint:r.querySelector('.sf-hint').value.trim(),enabled:r.querySelector('.sf-en').checked})).filter(x=>x.label&&x.key);
|
||||||
|
document.getElementById('sfItemsData').value=JSON.stringify(items);
|
||||||
|
});
|
||||||
|
(SF_ITEMS.length?SF_ITEMS:[]).forEach(add);
|
||||||
|
if(!body.rows.length) add({});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user