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.
185 lines
7.2 KiB
HTML
185 lines
7.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-bs-theme="light">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>{% block title %}PXE Server Manager{% endblock %}</title>
|
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
|
<link href="{{ url_for('static', filename='bootstrap.min.css') }}" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='bootstrap-icons.min.css') }}" rel="stylesheet">
|
|
<!-- pxe-theme.css loads AFTER bootstrap so its token+component rules win -->
|
|
<link href="{{ url_for('static', filename='pxe-theme.css') }}" rel="stylesheet">
|
|
<script>
|
|
// Set the theme before first paint to avoid an unstyled flash.
|
|
(function () {
|
|
var KEY = 'pxe-theme';
|
|
var stored = null;
|
|
try { stored = localStorage.getItem(KEY); } catch (e) {}
|
|
var theme = (stored === 'light' || stored === 'dark')
|
|
? stored
|
|
: (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
})();
|
|
</script>
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Sidebar -->
|
|
<nav class="sidebar d-flex flex-column">
|
|
<div class="sidebar-header">
|
|
<img class="sidebar-logo" src="{{ url_for('static', filename='ge-aerospace-logo.svg') }}" alt="GE Aerospace">
|
|
<div class="brand-title">PXE Manager</div>
|
|
</div>
|
|
|
|
<ul class="sidebar-nav nav flex-column mt-2">
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'dashboard' %}active{% endif %}"
|
|
href="{{ url_for('dashboard') }}">
|
|
<i class="bi bi-speedometer2"></i> Dashboard
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'images_import' %}active{% endif %}"
|
|
href="{{ url_for('images_import') }}">
|
|
<i class="bi bi-box-arrow-in-down"></i> Image Import
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'imaging_dashboard' %}active{% endif %}"
|
|
href="{{ url_for('imaging_dashboard') }}">
|
|
<i class="bi bi-activity"></i> Imaging Progress
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="nav-section">Tools</div>
|
|
<ul class="sidebar-nav nav flex-column">
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'startnet_editor' %}active{% endif %}"
|
|
href="{{ url_for('startnet_editor') }}">
|
|
<i class="bi bi-terminal"></i> startnet.cmd
|
|
</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>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'clonezilla_backups' %}active{% endif %}"
|
|
href="{{ url_for('clonezilla_backups') }}">
|
|
<i class="bi bi-hdd-stack"></i> Clonezilla Backups
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'blancco_reports' %}active{% endif %}"
|
|
href="{{ url_for('blancco_reports') }}">
|
|
<i class="bi bi-shield-check"></i> Blancco Reports
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'enrollment' %}active{% endif %}"
|
|
href="{{ url_for('enrollment') }}">
|
|
<i class="bi bi-pc-display"></i> Enrollment
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'audit_log' %}active{% endif %}"
|
|
href="{{ url_for('audit_log') }}">
|
|
<i class="bi bi-list-check"></i> Audit Log
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="nav-section">GE Aerospace Images</div>
|
|
<ul class="sidebar-nav nav flex-column">
|
|
{% for it in all_image_types if it.startswith('gea-') %}
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'unattend_editor' and image_type is defined and image_type == it %}active{% endif %}"
|
|
href="{{ url_for('unattend_editor', image_type=it) }}">
|
|
<i class="bi bi-windows"></i> {{ all_friendly_names[it] }}
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link nav-sublink {% if request.endpoint == 'image_config' and image_type is defined and image_type == it %}active{% endif %}"
|
|
href="{{ url_for('image_config', image_type=it) }}">
|
|
<i class="bi bi-gear"></i> Configuration
|
|
</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
<!-- Footer pinned to bottom -->
|
|
<div class="sidebar-footer mt-auto">
|
|
<button type="button" id="themeToggle" class="theme-toggle">
|
|
<i class="bi bi-moon-stars" id="themeToggleIcon"></i>
|
|
<span id="themeToggleLabel">Dark</span>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Main content -->
|
|
<div class="main-content">
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<script>
|
|
// Theme toggle: flips data-theme (and data-bs-theme) and persists to localStorage.
|
|
(function () {
|
|
var KEY = 'pxe-theme';
|
|
var btn = document.getElementById('themeToggle');
|
|
var icon = document.getElementById('themeToggleIcon');
|
|
var label = document.getElementById('themeToggleLabel');
|
|
|
|
function paintToggle(theme) {
|
|
// Label/icon advertise the theme you would switch TO.
|
|
if (theme === 'dark') {
|
|
if (icon) icon.className = 'bi bi-sun';
|
|
if (label) label.textContent = 'Light';
|
|
} else {
|
|
if (icon) icon.className = 'bi bi-moon-stars';
|
|
if (label) label.textContent = 'Dark';
|
|
}
|
|
}
|
|
|
|
function apply(theme) {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
try { localStorage.setItem(KEY, theme); } catch (e) {}
|
|
paintToggle(theme);
|
|
}
|
|
|
|
paintToggle(document.documentElement.getAttribute('data-theme') || 'light');
|
|
|
|
if (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var current = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
|
|
apply(current === 'dark' ? 'light' : 'dark');
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<script src="{{ url_for('static', filename='bootstrap.bundle.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='qrcode.min.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='qr-render.js') }}"></script>
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|