shopfloor menu: data-driven from menu.json (picker + webapp editor)
Replace the hardcoded GEA Shopfloor PC-type sub-menu with a data-driven one:
- menu.json on the enrollment share lists the shopfloor items {key=PCTYPE, label, hint, enabled}; key must match a shopfloor-setup/gea-shopfloor-* handler dir.
- select-shopfloor-type.ps1 renders it in WinPE and writes the chosen PCTYPE (mirrors the CMM bay picker); startnet.cmd runs it and falls back to the baked-in menu if the share/picker is unavailable.
- Webapp /shopfloor-menu editor: reorder/rename/hide/add items; the PC-type is a dropdown of existing handler dirs (can't wire a choice to a non-existent type); writes menu.json. Nav link under Tools.
Kills the duplicated-knowledge problem (menu list was hardcoded in startnet AND the handler dirs AND site-config); add a PC-type = drop in the handler dir + it appears in the menu.
This commit is contained in:
@@ -1189,6 +1189,83 @@ def startnet_diff():
|
||||
return jsonify({"error": "provide snapshot_id or content"}), 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routes - Shopfloor boot-menu (data-driven menu.json)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SHOPFLOOR_SETUP_DIR = os.path.join(config.ENROLLMENT_SHARE, "shopfloor-setup")
|
||||
SHOPFLOOR_MENU_JSON = os.path.join(SHOPFLOOR_SETUP_DIR, "menu.json")
|
||||
|
||||
|
||||
def read_shopfloor_menu():
|
||||
"""The shopfloor PC-type menu items from menu.json (list; [] if absent)."""
|
||||
try:
|
||||
with open(SHOPFLOOR_MENU_JSON) as fh:
|
||||
return json.load(fh).get("shopfloor", [])
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return []
|
||||
|
||||
|
||||
def available_pctypes():
|
||||
"""gea-shopfloor-* handler dirs on the share - the only valid menu keys."""
|
||||
try:
|
||||
return sorted(
|
||||
d for d in os.listdir(SHOPFLOOR_SETUP_DIR)
|
||||
if d.startswith("gea-shopfloor-")
|
||||
and os.path.isdir(os.path.join(SHOPFLOOR_SETUP_DIR, d))
|
||||
)
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
@app.route("/shopfloor-menu", methods=["GET", "POST"])
|
||||
def shopfloor_menu():
|
||||
if request.method == "POST":
|
||||
try:
|
||||
items = json.loads(request.form.get("items", "[]"))
|
||||
except json.JSONDecodeError:
|
||||
flash("Invalid menu payload.", "danger")
|
||||
return redirect(url_for("shopfloor_menu"))
|
||||
valid = set(available_pctypes())
|
||||
clean = []
|
||||
for it in items:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
key = (it.get("key") or "").strip()
|
||||
label = (it.get("label") or "").strip()
|
||||
if not key or not label:
|
||||
continue
|
||||
if key not in valid:
|
||||
flash(f"Skipped '{label}': {key} has no shopfloor-setup handler.", "warning")
|
||||
continue
|
||||
clean.append({
|
||||
"key": key,
|
||||
"label": label,
|
||||
"hint": (it.get("hint") or "").strip(),
|
||||
"enabled": bool(it.get("enabled", True)),
|
||||
})
|
||||
if not clean:
|
||||
flash("Refused to save an empty menu.", "danger")
|
||||
return redirect(url_for("shopfloor_menu"))
|
||||
try:
|
||||
os.makedirs(SHOPFLOOR_SETUP_DIR, exist_ok=True)
|
||||
with open(SHOPFLOOR_MENU_JSON, "w") as fh:
|
||||
json.dump({"shopfloor": clean}, fh, indent=2)
|
||||
audit("SHOPFLOOR_MENU_SAVE", f"{len(clean)} items")
|
||||
flash("Shopfloor boot menu saved - WinPE reads it at next boot.", "success")
|
||||
except OSError as exc:
|
||||
flash(f"Could not write menu.json: {exc}", "danger")
|
||||
return redirect(url_for("shopfloor_menu"))
|
||||
|
||||
return render_template(
|
||||
"shopfloor_menu.html",
|
||||
items=read_shopfloor_menu(),
|
||||
available=available_pctypes(),
|
||||
image_types=config.IMAGE_TYPES,
|
||||
friendly_names=config.FRIENDLY_NAMES,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routes - Audit Log
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user