Imaging dashboard
- services/imaging_log_tail.py: parses dnsmasq leases, Apache access log,
Samba per-host log files, and dnsmasq syslog (DHCP/TFTP). Synthesizes
inferred sessions keyed by MAC for bays that have only touched the boot
chain but not yet pushed to /imaging/status. Active window 90 min.
- imaging_status.list_sessions() merges inferred sessions into the dashboard
list. Real client-pushed sessions win for the same MAC.
- imaging_status: stage_history field tracks every stage transition (capped
30); sidecar .log file per serial records every log_lines push uncapped
(read_full_log() caps detail-page response to 1 MB).
- delete_session/delete_all_sessions clean up sidecar .log too.
- New SSE endpoint /imaging/stream emits a session-list hash every 5s.
Client fetches /imaging/tiles (HTML partial) on hash change and swaps
#imaging-tiles innerHTML. Polling fallback at 15s if SSE drops.
- Tile-swap preserves scroll, filter input, expanded state via localStorage,
and any LAPS input the operator is mid-pasting (swap skipped when a
laps-input is focused).
- imaging.html: removed 15s location.reload(). Added live-status dot in
header (gray idle / green SSE connected / red SSE lost).
- _imaging_tiles.html: shared partial used by both /imaging full render and
/imaging/tiles SSE refresh. Inferred bays render with yellow border +
log-inferred badge + no progress bar (stage_index inference is coarse).
- imaging_detail.html (new): per-bay forensics page at /imaging/session/
<serial>. Session metadata grid, stage timeline table, full sidecar log
with truncation indicator, Copy-support-summary button. Linked from each
client-pushed tile.
- qr-render.js exposes window.renderAllQRs() so the SSE swap can re-render
Intune device-ID QRs in the swapped-in tiles.
Image management
- services/image_registry.py: JSON registry of image types at
{SAMBA_SHARE}/image-registry.json. Bootstraps from baked-in
config.IMAGE_TYPES on first run. create/clone/delete/rename_friendly
mutate the file then call reload() which rewrites config.IMAGE_TYPES +
config.FRIENDLY_NAMES in place. Sidebar reflects on next request.
- app.py routes: /images/new, /images/<t>/clone, /images/<t>/delete (with
optional content-wipe checkbox), /images/<t>/rename.
- dashboard.html: + New image type button + Clone/Delete per row, all in
Bootstrap modals with confirmation copy.
- Clone copies Deploy/ tree but preserves symlinks to shared dirs (Out-of-
box Drivers, Operating Systems, Packages) so disk usage stays low.
- Delete with content checked unlinks symlinks (does not follow into shared
dirs).
Driver / package upload + orphan adoption
- services/images.py: upload_driver, adopt_orphan, remove_orphans,
upload_package. Filename sanitization blocks path traversal.
- app.py routes: /images/<t>/drivers/upload, /images/<t>/drivers/adopt,
/images/<t>/drivers/orphans/delete, /images/<t>/packages/upload.
- image_config.html: Upload .zip button + modal on Drivers section. Orphan
drivers card-footer rebuilt as interactive list with per-row Adopt inline
form (family + destinationDir inputs) and bulk select+delete.
- Upload .zip on Packages section with optional destinationDir field that
appends a packages.json entry.
Configuration
- config.py: new env vars DNSMASQ_LEASES, APACHE_ACCESS_LOG, SAMBA_LOG_DIR,
DNSMASQ_SYSLOG for the log-tailer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
3.2 KiB
Python
79 lines
3.2 KiB
Python
"""Configuration constants for the PXE webapp.
|
|
|
|
Reads from environment variables with sensible defaults that match the
|
|
Ansible playbook's deploy paths. Also defines the canonical image type
|
|
list and shared-directory taxonomy used by the import pipeline.
|
|
"""
|
|
|
|
import os
|
|
|
|
# --- Filesystem paths --------------------------------------------------------
|
|
SAMBA_SHARE = os.environ.get("SAMBA_SHARE", "/srv/samba/winpeapps")
|
|
CLONEZILLA_SHARE = os.environ.get("CLONEZILLA_SHARE", "/srv/samba/clonezilla")
|
|
BLANCCO_REPORTS = os.environ.get("BLANCCO_REPORTS", "/srv/samba/blancco-reports")
|
|
ENROLLMENT_SHARE = os.environ.get("ENROLLMENT_SHARE", "/srv/samba/enrollment")
|
|
ENROLLMENT_PPKG_DIR = os.environ.get(
|
|
"ENROLLMENT_PPKG_DIR", os.path.join(ENROLLMENT_SHARE, "ppkgs")
|
|
)
|
|
UPLOAD_DIR = os.environ.get("UPLOAD_DIR", "/home/pxe/image-upload")
|
|
SHARED_DIR = os.path.join(SAMBA_SHARE, "_shared")
|
|
WEB_ROOT = os.environ.get("WEB_ROOT", "/var/www/html")
|
|
BOOT_WIM = os.path.join(WEB_ROOT, "win11", "sources", "boot.wim")
|
|
AUDIT_LOG = os.environ.get("AUDIT_LOG", "/var/log/pxe-webapp-audit.log")
|
|
IMAGING_DIR = os.environ.get("IMAGING_DIR", "/var/log/pxe-imaging")
|
|
|
|
# Log sources used by services/imaging_log_tail.py to infer client progress
|
|
# when no /imaging/status push has arrived yet. Override per-host if any of
|
|
# these live elsewhere on the live PXE server.
|
|
DNSMASQ_LEASES = os.environ.get("DNSMASQ_LEASES", "/var/lib/misc/dnsmasq.leases")
|
|
APACHE_ACCESS_LOG = os.environ.get("APACHE_ACCESS_LOG", "/var/log/apache2/access.log")
|
|
SAMBA_LOG_DIR = os.environ.get("SAMBA_LOG_DIR", "/var/log/samba")
|
|
# dnsmasq log-dhcp + TFTP requests land in syslog by default. Used to spot
|
|
# very-early boot (TFTP bootloader fetch) before Apache sees anything.
|
|
DNSMASQ_SYSLOG = os.environ.get("DNSMASQ_SYSLOG", "/var/log/syslog")
|
|
|
|
# --- Flask -------------------------------------------------------------------
|
|
FLASK_SECRET_KEY = os.environ.get("FLASK_SECRET_KEY", "pxe-manager-dev-key-change-in-prod")
|
|
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 * 1024 # 16 GB max upload
|
|
|
|
# --- Image taxonomy ----------------------------------------------------------
|
|
# Subdirs inside Deploy/ shared across ALL image types.
|
|
SHARED_DEPLOY_GLOBAL = ["Out-of-box Drivers"]
|
|
|
|
# Subdirs inside Deploy/ shared within the same image family (by prefix).
|
|
SHARED_DEPLOY_SCOPED = {
|
|
"gea-": ["Operating Systems", "Packages"],
|
|
"ge-": ["Operating Systems", "Packages"],
|
|
}
|
|
|
|
# Sibling dirs at image root shared within the same image family.
|
|
SHARED_ROOT_DIRS = {
|
|
"gea-": ["Sources"],
|
|
"ge-": ["Sources"],
|
|
}
|
|
|
|
IMAGE_TYPES = [
|
|
"gea-standard",
|
|
"gea-engineer",
|
|
"gea-shopfloor",
|
|
"ge-standard",
|
|
"ge-engineer",
|
|
"ge-shopfloor-lockdown",
|
|
"ge-shopfloor-mce",
|
|
]
|
|
|
|
FRIENDLY_NAMES = {
|
|
"gea-standard": "GE Aerospace Standard",
|
|
"gea-engineer": "GE Aerospace Engineer",
|
|
"gea-shopfloor": "GE Aerospace Shop Floor",
|
|
"ge-standard": "GE Legacy Standard",
|
|
"ge-engineer": "GE Legacy Engineer",
|
|
"ge-shopfloor-lockdown": "GE Legacy Shop Floor Lockdown",
|
|
"ge-shopfloor-mce": "GE Legacy Shop Floor MCE",
|
|
}
|
|
|
|
# --- Unattend XML namespaces -------------------------------------------------
|
|
UNATTEND_NS = "urn:schemas-microsoft-com:unattend"
|
|
WCM_NS = "http://schemas.microsoft.com/WMIConfig/2002/State"
|
|
NSMAP = {None: UNATTEND_NS, "wcm": WCM_NS}
|