mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 22:07:24 +00:00
Fixed UI guideline compliance for modules missing sidebar: - secubox-cloner, wazuh, ossec, vault, magicmirror - secubox-mmpm, redroid, rezapp, vm, ndpid, p2p Changes per module: - Added <nav class="sidebar" id="sidebar"></nav> - Added body class="crt-light" - Added sidebar.js script reference (ndpid) Also added: - REPORT-2026-04-10.md: Board/financer status report - scripts/ui-screenshot-capture.py: Playwright UI capture - scripts/ui-fix-checker.sh: UI guideline compliance checker UI checker now passes 120/120 modules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
351 lines
14 KiB
Python
Executable File
351 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
SecuBox UI Screenshot Capture & Guideline Checker
|
|
==================================================
|
|
Captures screenshots from all SecuBox modules and verifies UI compliance.
|
|
|
|
Usage:
|
|
pip install playwright
|
|
playwright install chromium
|
|
python scripts/ui-screenshot-capture.py [--base-url URL] [--output-dir DIR]
|
|
|
|
Requirements:
|
|
- Running SecuBox instance at https://localhost:9443/
|
|
- playwright package installed
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, field
|
|
|
|
try:
|
|
from playwright.async_api import async_playwright, Page
|
|
except ImportError:
|
|
print("ERROR: playwright not installed. Run: pip install playwright && playwright install chromium")
|
|
sys.exit(1)
|
|
|
|
|
|
# UI Guidelines - Expected elements per the design docs
|
|
UI_GUIDELINES = {
|
|
"required_elements": {
|
|
"sidebar": "nav.sidebar, #sidebar",
|
|
"main_content": ".main-content, main",
|
|
"theme_css": "link[href*='crt-light'], link[href*='crt-system']",
|
|
},
|
|
"color_palette_light": {
|
|
"tube_light": "#e8f5e9",
|
|
"tube_pale": "#c8e6c9",
|
|
"p31_peak": "#00dd44",
|
|
"p31_mid": "#009933",
|
|
},
|
|
"color_palette_dark": {
|
|
"tube_black": "#0a0e14",
|
|
"tube_deep": "#1a1f2e",
|
|
"p31_peak": "#33ff66",
|
|
},
|
|
"module_colors": {
|
|
"boot": "#803018",
|
|
"wall": "#9A6010",
|
|
"mind": "#3D35A0",
|
|
"root": "#0A5840",
|
|
"mesh": "#104A88",
|
|
"auth": "#C04E24",
|
|
}
|
|
}
|
|
|
|
# All modules from UI-GUIDE.md
|
|
MODULES = [
|
|
# Dashboard
|
|
{"name": "Dashboard", "path": "/", "category": "dashboard"},
|
|
{"name": "Portal", "path": "/portal/", "category": "dashboard"},
|
|
{"name": "System Hub", "path": "/system/", "category": "dashboard"},
|
|
{"name": "SOC", "path": "/soc/", "category": "dashboard"},
|
|
|
|
# Security
|
|
{"name": "Users", "path": "/users/", "category": "security"},
|
|
{"name": "WireGuard VPN", "path": "/wireguard/", "category": "security"},
|
|
{"name": "CrowdSec", "path": "/crowdsec/", "category": "security"},
|
|
{"name": "WAF", "path": "/waf/", "category": "security"},
|
|
{"name": "MITM Proxy", "path": "/mitmproxy/", "category": "security"},
|
|
{"name": "Hardening", "path": "/hardening/", "category": "security"},
|
|
{"name": "Auth Guardian", "path": "/auth/", "category": "security"},
|
|
{"name": "Vortex Firewall", "path": "/vortex-firewall/", "category": "security"},
|
|
{"name": "NAC", "path": "/nac/", "category": "security"},
|
|
{"name": "Tor", "path": "/tor/", "category": "security"},
|
|
{"name": "ZKP", "path": "/zkp/", "category": "security"},
|
|
|
|
# Network
|
|
{"name": "DNS", "path": "/dns/", "category": "network"},
|
|
{"name": "Vortex DNS", "path": "/vortex-dns/", "category": "network"},
|
|
{"name": "Traffic Shaping", "path": "/traffic/", "category": "network"},
|
|
{"name": "Network Modes", "path": "/netmodes/", "category": "network"},
|
|
{"name": "DPI", "path": "/dpi/", "category": "network"},
|
|
{"name": "QoS", "path": "/qos/", "category": "network"},
|
|
{"name": "Virtual Hosts", "path": "/vhost/", "category": "network"},
|
|
{"name": "CDN Cache", "path": "/cdn/", "category": "network"},
|
|
{"name": "HAProxy", "path": "/haproxy/", "category": "network"},
|
|
{"name": "Exposure", "path": "/exposure/", "category": "network"},
|
|
{"name": "Mesh DNS", "path": "/meshname/", "category": "network"},
|
|
|
|
# Monitoring
|
|
{"name": "Metrics", "path": "/metrics/", "category": "monitoring"},
|
|
{"name": "Netdata", "path": "/netdata/", "category": "monitoring"},
|
|
{"name": "Media Flow", "path": "/mediaflow/", "category": "monitoring"},
|
|
{"name": "Device Intel", "path": "/device-intel/", "category": "monitoring"},
|
|
|
|
# Publishing
|
|
{"name": "Droplet", "path": "/droplet/", "category": "publishing"},
|
|
{"name": "MetaBlogizer", "path": "/metablogizer/", "category": "publishing"},
|
|
{"name": "Publish", "path": "/publish/", "category": "publishing"},
|
|
|
|
# Applications
|
|
{"name": "Mail", "path": "/mail/", "category": "applications"},
|
|
{"name": "Webmail", "path": "/webmail/", "category": "applications"},
|
|
{"name": "Streamlit", "path": "/streamlit/", "category": "applications"},
|
|
{"name": "C3Box", "path": "/c3box/", "category": "applications"},
|
|
{"name": "Gitea", "path": "/gitea/", "category": "applications"},
|
|
{"name": "Nextcloud", "path": "/nextcloud/", "category": "applications"},
|
|
{"name": "StreamForge", "path": "/streamforge/", "category": "applications"},
|
|
{"name": "Ollama", "path": "/ollama/", "category": "applications"},
|
|
{"name": "Jellyfin", "path": "/jellyfin/", "category": "applications"},
|
|
{"name": "Lyrion", "path": "/lyrion/", "category": "applications"},
|
|
{"name": "Hexo", "path": "/hexo/", "category": "applications"},
|
|
{"name": "Webradio", "path": "/webradio/", "category": "applications"},
|
|
{"name": "Torrent", "path": "/torrent/", "category": "applications"},
|
|
{"name": "Newsbin", "path": "/newsbin/", "category": "applications"},
|
|
{"name": "Domoticz", "path": "/domoticz/", "category": "applications"},
|
|
{"name": "GoToSocial", "path": "/gotosocial/", "category": "applications"},
|
|
{"name": "Simplex", "path": "/simplex/", "category": "applications"},
|
|
{"name": "PhotoPrism", "path": "/photoprism/", "category": "applications"},
|
|
{"name": "HomeAssistant", "path": "/homeassistant/", "category": "applications"},
|
|
{"name": "Matrix", "path": "/matrix/", "category": "applications"},
|
|
{"name": "Jitsi", "path": "/jitsi/", "category": "applications"},
|
|
{"name": "PeerTube", "path": "/peertube/", "category": "applications"},
|
|
{"name": "VoIP", "path": "/voip/", "category": "applications"},
|
|
|
|
# System
|
|
{"name": "APT Repo", "path": "/repo/", "category": "system"},
|
|
{"name": "Backup", "path": "/backup/", "category": "system"},
|
|
{"name": "Watchdog", "path": "/watchdog/", "category": "system"},
|
|
{"name": "Roadmap", "path": "/roadmap/", "category": "system"},
|
|
{"name": "Admin", "path": "/admin/", "category": "system"},
|
|
{"name": "Console", "path": "/console/", "category": "system"},
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class UICheckResult:
|
|
"""Result of a UI guideline check"""
|
|
module: str
|
|
path: str
|
|
screenshot_path: str
|
|
has_sidebar: bool = False
|
|
has_main_content: bool = False
|
|
has_theme_css: bool = False
|
|
page_title: str = ""
|
|
status_code: int = 0
|
|
errors: list = field(default_factory=list)
|
|
warnings: list = field(default_factory=list)
|
|
|
|
@property
|
|
def passed(self) -> bool:
|
|
return self.has_sidebar and self.has_main_content and self.status_code == 200
|
|
|
|
|
|
class UIScreenshotCapture:
|
|
"""Captures screenshots and validates UI guidelines"""
|
|
|
|
def __init__(self, base_url: str = "https://localhost:9443", output_dir: str = "docs/screenshots/audit"):
|
|
self.base_url = base_url.rstrip("/")
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.results: list[UICheckResult] = []
|
|
|
|
async def capture_all(self, headless: bool = True):
|
|
"""Capture screenshots from all modules"""
|
|
async with async_playwright() as p:
|
|
browser = await p.chromium.launch(headless=headless)
|
|
context = await browser.new_context(
|
|
viewport={"width": 1920, "height": 1080},
|
|
ignore_https_errors=True, # For self-signed certs
|
|
)
|
|
page = await context.new_page()
|
|
|
|
print(f"SecuBox UI Screenshot Capture")
|
|
print(f"==============================")
|
|
print(f"Base URL: {self.base_url}")
|
|
print(f"Output: {self.output_dir}")
|
|
print(f"Modules: {len(MODULES)}")
|
|
print()
|
|
|
|
for i, module in enumerate(MODULES, 1):
|
|
result = await self._capture_module(page, module, i, len(MODULES))
|
|
self.results.append(result)
|
|
|
|
await browser.close()
|
|
|
|
self._generate_report()
|
|
|
|
async def _capture_module(self, page: Page, module: dict, index: int, total: int) -> UICheckResult:
|
|
"""Capture screenshot and check guidelines for a single module"""
|
|
name = module["name"]
|
|
path = module["path"]
|
|
url = f"{self.base_url}{path}"
|
|
|
|
# Sanitize filename
|
|
filename = path.strip("/").replace("/", "-") or "index"
|
|
screenshot_path = self.output_dir / f"{filename}.png"
|
|
|
|
result = UICheckResult(
|
|
module=name,
|
|
path=path,
|
|
screenshot_path=str(screenshot_path),
|
|
)
|
|
|
|
print(f"[{index}/{total}] {name} ({path})...", end=" ", flush=True)
|
|
|
|
try:
|
|
response = await page.goto(url, wait_until="networkidle", timeout=30000)
|
|
result.status_code = response.status if response else 0
|
|
|
|
# Wait for dynamic content
|
|
await page.wait_for_timeout(1000)
|
|
|
|
# Check required elements
|
|
result.has_sidebar = await self._element_exists(page, UI_GUIDELINES["required_elements"]["sidebar"])
|
|
result.has_main_content = await self._element_exists(page, UI_GUIDELINES["required_elements"]["main_content"])
|
|
result.has_theme_css = await self._element_exists(page, UI_GUIDELINES["required_elements"]["theme_css"])
|
|
|
|
# Get page title
|
|
result.page_title = await page.title()
|
|
|
|
# Capture screenshot
|
|
await page.screenshot(path=screenshot_path, full_page=False)
|
|
|
|
# Check for specific issues
|
|
if not result.has_sidebar:
|
|
result.errors.append("Missing sidebar navigation")
|
|
if not result.has_main_content:
|
|
result.errors.append("Missing main content container")
|
|
if not result.has_theme_css:
|
|
result.warnings.append("Missing CRT theme CSS")
|
|
|
|
status = "OK" if result.passed else "FAIL"
|
|
print(f"{status}")
|
|
|
|
except Exception as e:
|
|
result.errors.append(f"Error: {str(e)}")
|
|
print(f"ERROR: {e}")
|
|
|
|
return result
|
|
|
|
async def _element_exists(self, page: Page, selector: str) -> bool:
|
|
"""Check if element exists using CSS selector"""
|
|
try:
|
|
element = await page.query_selector(selector)
|
|
return element is not None
|
|
except:
|
|
return False
|
|
|
|
def _generate_report(self):
|
|
"""Generate markdown report of results"""
|
|
report_path = self.output_dir / "UI-AUDIT-REPORT.md"
|
|
|
|
passed = sum(1 for r in self.results if r.passed)
|
|
failed = len(self.results) - passed
|
|
|
|
with open(report_path, "w") as f:
|
|
f.write(f"# SecuBox UI Audit Report\n\n")
|
|
f.write(f"**Generated:** {datetime.now().isoformat()}\n")
|
|
f.write(f"**Base URL:** {self.base_url}\n")
|
|
f.write(f"**Total Modules:** {len(self.results)}\n")
|
|
f.write(f"**Passed:** {passed}\n")
|
|
f.write(f"**Failed:** {failed}\n\n")
|
|
|
|
f.write("---\n\n")
|
|
f.write("## Summary\n\n")
|
|
f.write("| Module | Path | Sidebar | Content | Theme | Status |\n")
|
|
f.write("|--------|------|---------|---------|-------|--------|\n")
|
|
|
|
for r in self.results:
|
|
sidebar = "OK" if r.has_sidebar else "MISS"
|
|
content = "OK" if r.has_main_content else "MISS"
|
|
theme = "OK" if r.has_theme_css else "WARN"
|
|
status = "PASS" if r.passed else "**FAIL**"
|
|
f.write(f"| {r.module} | `{r.path}` | {sidebar} | {content} | {theme} | {status} |\n")
|
|
|
|
f.write("\n---\n\n")
|
|
f.write("## Issues Found\n\n")
|
|
|
|
issues_found = False
|
|
for r in self.results:
|
|
if r.errors or r.warnings:
|
|
issues_found = True
|
|
f.write(f"### {r.module} (`{r.path}`)\n\n")
|
|
for error in r.errors:
|
|
f.write(f"- **ERROR:** {error}\n")
|
|
for warning in r.warnings:
|
|
f.write(f"- **WARNING:** {warning}\n")
|
|
f.write("\n")
|
|
|
|
if not issues_found:
|
|
f.write("No issues found.\n\n")
|
|
|
|
f.write("---\n\n")
|
|
f.write("## Screenshots\n\n")
|
|
|
|
by_category = {}
|
|
for r in self.results:
|
|
module = next((m for m in MODULES if m["path"] == r.path), {})
|
|
cat = module.get("category", "other")
|
|
if cat not in by_category:
|
|
by_category[cat] = []
|
|
by_category[cat].append(r)
|
|
|
|
for category, results in by_category.items():
|
|
f.write(f"### {category.title()}\n\n")
|
|
for r in results:
|
|
filename = Path(r.screenshot_path).name
|
|
f.write(f"#### {r.module}\n")
|
|
f.write(f"\n\n")
|
|
|
|
print(f"\nReport saved to: {report_path}")
|
|
|
|
# Also save JSON for programmatic use
|
|
json_path = self.output_dir / "ui-audit-results.json"
|
|
with open(json_path, "w") as f:
|
|
json.dump([{
|
|
"module": r.module,
|
|
"path": r.path,
|
|
"screenshot": r.screenshot_path,
|
|
"passed": r.passed,
|
|
"has_sidebar": r.has_sidebar,
|
|
"has_main_content": r.has_main_content,
|
|
"has_theme_css": r.has_theme_css,
|
|
"status_code": r.status_code,
|
|
"errors": r.errors,
|
|
"warnings": r.warnings,
|
|
} for r in self.results], f, indent=2)
|
|
|
|
print(f"JSON data saved to: {json_path}")
|
|
|
|
|
|
async def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Capture SecuBox UI screenshots")
|
|
parser.add_argument("--base-url", default="https://localhost:9443", help="Base URL")
|
|
parser.add_argument("--output-dir", default="docs/screenshots/audit", help="Output directory")
|
|
parser.add_argument("--headed", action="store_true", help="Run browser in headed mode")
|
|
args = parser.parse_args()
|
|
|
|
capture = UIScreenshotCapture(base_url=args.base_url, output_dir=args.output_dir)
|
|
await capture.capture_all(headless=not args.headed)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|