feat: replace UI mockups with real netOrk screenshots
The homepage showed seven hand-built JSX imitations of the netOrk UI. They are gone; every image is now a screenshot of netOrk v0.28.0 itself, taken from an anonymized copy of a production database (scripts/demo) with scripts/screenshots/capture.py and published as WebP (~630 KB for all eight). - Hero: the device inventory. Walkthrough: device detail, VLANs, the Security tab, the vulnerability triage queue (replacing the config-diff row), the dashboard and service checks (new row 6). NIS2: the audit log, filtered to what people did. - Copy follows the images: row 3 describes the security assessment, row 4 the triage queue; row 2 no longer claims corrections are always automatic; 18 widgets. Alt texts in both languages. - Also fixed on the homepage: the NIS2 teaser for Art. 21 (2e) and the container list of a deployment (three worker pools, plus Flower, registry, APT cache and the Signal gateway). - Demo tooling hardened on the real dump: secrets inside JSON (Wi-Fi keys), reverse DNS zones, glued identifiers, tens of thousands of CrowdSec addresses, a schema newer than the release (anonymize, then downgrade), MFA-enforcing roles, and click steps for view filters. - DESIGN.md: real screenshots only. PAGES.md: the six rows as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
38834100d1
commit
011f816fc9
@@ -17,6 +17,7 @@ taking screenshots cannot change anything on the instance.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -26,6 +27,7 @@ import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from playwright.sync_api import Page, sync_playwright
|
||||
|
||||
from shots import SHOTS
|
||||
@@ -80,7 +82,10 @@ def token() -> str:
|
||||
body = urllib.parse.urlencode({"username": USER, "password": PASSWORD}).encode()
|
||||
try:
|
||||
with urllib.request.urlopen(f"{BASE}/api/v1/auth/token", body) as res:
|
||||
return json.load(res)["access_token"]
|
||||
tok = json.load(res).get("access_token")
|
||||
if not tok:
|
||||
sys.exit(f"Login as {USER} needs MFA; the demo copy should have none (anonymize.py)")
|
||||
return tok
|
||||
except urllib.error.URLError as e:
|
||||
sys.exit(f"Login as {USER} at {BASE} failed: {e} (is scripts/demo/up.sh start running?)")
|
||||
state = json.loads(STATE.read_text())
|
||||
@@ -118,6 +123,15 @@ def settle(page: Page) -> None:
|
||||
page.wait_for_timeout(800)
|
||||
|
||||
|
||||
def publish(png: bytes, path: Path, width: int) -> None:
|
||||
"""Scale the 2x capture down to its published width and store it as WebP."""
|
||||
img = Image.open(io.BytesIO(png)).convert("RGB")
|
||||
if img.width > width:
|
||||
img = img.resize((width, round(img.height * width / img.width)), Image.LANCZOS)
|
||||
img.save(path, "WEBP", quality=85, method=6)
|
||||
print(f" -> {path.name} {img.width}x{img.height}, {path.stat().st_size // 1024} KB")
|
||||
|
||||
|
||||
def capture(variables: dict[str, str], only: set[str], mask: bool) -> None:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
terms = mask_terms()
|
||||
@@ -160,6 +174,9 @@ def capture(variables: dict[str, str], only: set[str], mask: bool) -> None:
|
||||
if got_it.is_visible():
|
||||
got_it.click()
|
||||
page.wait_for_timeout(300)
|
||||
for sel in shot.clicks:
|
||||
page.locator(sel).first.click()
|
||||
settle(page)
|
||||
masks = [page.locator(s) for s in shot.mask]
|
||||
if mask:
|
||||
masks += [page.get_by_text(PUBLIC_IPV4), page.get_by_text(EMAIL)]
|
||||
@@ -167,8 +184,9 @@ def capture(variables: dict[str, str], only: set[str], mask: bool) -> None:
|
||||
clip = None
|
||||
if shot.height:
|
||||
clip = {"x": 0, "y": 0, "width": VIEWPORT["width"], "height": shot.height}
|
||||
page.screenshot(path=OUT / f"{shot.name}.png", full_page=shot.full_page,
|
||||
clip=clip, mask=masks, mask_color="#334155", animations="disabled")
|
||||
png = page.screenshot(full_page=shot.full_page, clip=clip, mask=masks,
|
||||
mask_color="#334155", animations="disabled")
|
||||
publish(png, OUT / f"{shot.name}.webp", shot.width)
|
||||
browser.close()
|
||||
if blocked:
|
||||
print("Blocked non-GET requests (nothing was sent):")
|
||||
|
||||
Reference in New Issue
Block a user