Technician To-Do (sidebar link with a count, and a banner on every page while anything is open) lists the playbook steps the server cannot do: 1. wipes still to tick on the Onsite Bin RITM (ENT-045/159) 2. monthly evidence packs to upload to Box (ENT-056/158), Mark uploaded 3. drives that failed to wipe, to destroy (COD recorded) 4. reports Blancco could not upload to the cloud (Resolved) Marks are stored in the Blancco state file; the counts are cached for 60s and refreshed on every mark, ServiceNow tick and cloud-status post. Results are now judged per drive. Blancco keeps every attempt in the report, so a NIST Purge that failed followed by a successful Clear fallback on the same drive appeared as two entries, and the Reports page rolled the whole report up to Failed. 20 such wipes since May showed as Failed; they were wiped (the work instruction counts the fallback as a success). The CSV marks those rows "Fallback: ... then erased with ...". One genuine failure remains: HNMD1V3, Seagate Z500R4KG, 2026-08-11.
200 lines
8.5 KiB
HTML
200 lines
8.5 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 in ['startnet_editor', 'shopfloor_menu'] %}active{% endif %}"
|
|
href="{{ url_for('startnet_editor') }}">
|
|
<i class="bi bi-list-ol"></i> Boot 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 == 'blancco_todo' %}active{% endif %}"
|
|
href="{{ url_for('blancco_todo') }}">
|
|
<i class="bi bi-clipboard-check"></i> Technician To-Do
|
|
{% if blancco_todo.total %}<span class="badge badge-warning ms-1">{{ blancco_todo.total }}</span>{% endif %}
|
|
</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 %}
|
|
|
|
{% if blancco_todo.total and request.endpoint not in ('blancco_todo', 'imaging_tiles') %}
|
|
<div class="alert alert-warning d-flex flex-wrap align-items-center gap-2 py-2" role="alert">
|
|
<i class="bi bi-clipboard-check"></i>
|
|
<strong>Technician to-do:</strong>
|
|
{% set parts = [] %}
|
|
{% if blancco_todo.servicenow %}{% set _ = parts.append(blancco_todo.servicenow ~ ' wipe' ~ ('s' if blancco_todo.servicenow != 1) ~ ' to tick in ServiceNow') %}{% endif %}
|
|
{% if blancco_todo.evidence %}{% set _ = parts.append(blancco_todo.evidence ~ ' evidence pack' ~ ('s' if blancco_todo.evidence != 1) ~ ' to upload to Box') %}{% endif %}
|
|
{% if blancco_todo.failed %}{% set _ = parts.append(blancco_todo.failed ~ ' failed wipe' ~ ('s' if blancco_todo.failed != 1) ~ ' needing a Certificate of Destruction') %}{% endif %}
|
|
{% if blancco_todo.not_in_cloud %}{% set _ = parts.append(blancco_todo.not_in_cloud ~ ' report' ~ ('s' if blancco_todo.not_in_cloud != 1) ~ ' not in the Blancco cloud') %}{% endif %}
|
|
<span>{{ parts | join(' | ') }}</span>
|
|
<a href="{{ url_for('blancco_todo') }}" class="ms-auto pxe-btn pxe-btn-secondary btn-row-action">Open to-do</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% 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>
|