New Engineering Layer tab on the engineer image page. A site uploads Engineering_Install_v<ver>.exe (keeps prereqs) or a .zip of the whole extra_engineering payload. The upload is streamed to disk, checked (MZ header, no unsafe zip paths) and swapped in; the replaced payload is archived (two kept) with a Restore button. Install-EngineeringLayer.ps1 stays repo-owned and now installs the newest Engineering_Install_v*.exe. KB: Part 4 becomes Site Content (enrollment package, Engineering Layer, Blancco site settings, Shop Floor apps) plus a new Adding Shop Floor Apps section for preinstall.json/postppkg.json. WJ examples removed.
59 lines
2.4 KiB
Python
59 lines
2.4 KiB
Python
"""Engineer image > Engineering Layer tab: upload the Engineering Layer
|
|
installer (or the whole extra_engineering payload) the engineer unattend
|
|
installs. Logic in services/engineering_layer.py."""
|
|
|
|
from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for
|
|
|
|
import config
|
|
from services import engineering_layer as el
|
|
from services.audit import audit
|
|
|
|
bp = Blueprint("engineering_layer", __name__, url_prefix="/images")
|
|
|
|
|
|
def _back():
|
|
return redirect(url_for("engineering_layer.page", image_type=el.IMAGE))
|
|
|
|
|
|
@bp.route("/<image_type>/engineering-layer")
|
|
def page(image_type):
|
|
if image_type != el.IMAGE:
|
|
flash("The Engineering Layer belongs to the engineer image.", "warning")
|
|
return redirect(url_for("images.overview", image_type=image_type)) \
|
|
if image_type in config.IMAGE_TYPES else redirect(url_for("dashboard"))
|
|
return render_template(
|
|
"image_engineering_layer.html",
|
|
image_type=image_type,
|
|
friendly_name=config.FRIENDLY_NAMES.get(image_type, image_type),
|
|
active_tab="englayer",
|
|
st=el.status(),
|
|
)
|
|
|
|
|
|
@bp.route("/gea-engineer/engineering-layer/upload", methods=["POST"])
|
|
def upload():
|
|
"""Raw body (the page's XHR upload, name in ?name=) streams to disk."""
|
|
name = request.args.get("name", "")
|
|
try:
|
|
res = el.upload(request.stream, name, request.content_length)
|
|
except (ValueError, RuntimeError, OSError) as ex:
|
|
return jsonify({"ok": False, "error": str(ex)}), 400
|
|
audit("ENGINEERING_LAYER_UPLOAD",
|
|
f"{res['name']} ({res['size']} bytes): replaced {', '.join(res['replaced'])}; "
|
|
f"installer v{res['version']}; previous in archive {res['archived'] or '(none)'}")
|
|
flash(f"Engineering Layer updated from {res['name']}: installer v{res['version']}"
|
|
+ (f", previous kept as {res['archived']}." if res["archived"] else "."), "success")
|
|
return jsonify({"ok": True, **res})
|
|
|
|
|
|
@bp.route("/gea-engineer/engineering-layer/restore/<stamp>", methods=["POST"])
|
|
def restore(stamp):
|
|
try:
|
|
parts = el.restore(stamp)
|
|
except (ValueError, RuntimeError, OSError) as ex:
|
|
flash(f"Not restored: {ex}", "danger")
|
|
return _back()
|
|
audit("ENGINEERING_LAYER_RESTORE", f"{stamp}: {', '.join(parts)}")
|
|
flash(f"Restored the Engineering Layer from {stamp}; the replaced one is archived.", "success")
|
|
return _back()
|