Sicherheits-Header, Werkzeuge und Dokumentation
Sicherheit: - .htaccess im Projektstamm mit X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy und einer Content-Security-Policy; da alle Assets lokal liegen, erlaubt sie nur noch die eigene Herkunft (Ausnahme: hCaptcha, falls aktiviert) - includes/, tools/, tests/, updater/storage und uploads/ schützen sich über eigene .htaccess-Dateien – auch bei Installation im Unterordner - Docker: AllowOverride All, damit diese Regeln überhaupt greifen, und ein Volume für uploads/, damit Logos ein Image-Update überstehen Werkzeuge: - tools/screenshots.py erzeugt alle Bilder in docs/screenshots aus der Demo-Instanz; tools/README.md beschreibt beides - Einstellungs-Tabs sind per ?tab=… direkt verlinkbar (serverseitig, also auch ohne JavaScript) Dokumentation: Readme um Markenfarben, Bild-Upload, lokale Assets, Sicherheits-Header (inkl. Nginx-Entsprechung) und einen Abschnitt "Entwicklung" ergänzt; Screenshots neu erzeugt, Version 2.6.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e28527ed91
commit
36e06ac817
30 changed files with 307 additions and 36 deletions
2
tools/.htaccess
Normal file
2
tools/.htaccess
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Diese Dateien werden nur serverseitig eingebunden und nie direkt ausgeliefert.
|
||||
Require all denied
|
||||
42
tools/README.md
Normal file
42
tools/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Werkzeuge
|
||||
|
||||
## Demo-Instanz (`tools/demo/build.py`)
|
||||
|
||||
Baut aus dem Projekt eine lauffähige Kopie **ohne Datenbank**: `Database` und
|
||||
`Auth` werden durch Stubs mit festen Beispieldaten ersetzt (`tools/demo/overlay/`).
|
||||
Das Original bleibt unverändert.
|
||||
|
||||
```bash
|
||||
python3 tools/demo/build.py /tmp/uvt-demo
|
||||
php -S 127.0.0.1:8123 -t /tmp/uvt-demo
|
||||
```
|
||||
|
||||
Nützlich für:
|
||||
|
||||
* **Screenshots** für die Dokumentation – immer mit denselben Daten
|
||||
* **Smoke-Test** – jede Seite einmal rendern, ohne MySQL aufzusetzen
|
||||
|
||||
Demo-spezifische Zustände werden über Query-Parameter erreicht:
|
||||
|
||||
| Parameter | Wirkung |
|
||||
|---|---|
|
||||
| `?demo=result` / `?demo=bulk` | Voucher-Ergebnis bzw. Bulk-Liste auf `index.php` |
|
||||
| `?demo=new` | frisch erzeugter Schlüssel auf `admin/api_keys.php` |
|
||||
| `?anon=1` | nicht angemeldet (für die Login-Seite) |
|
||||
| `?theme=dark` | erzwingt den Dark Mode |
|
||||
| `?brand=custom` / `?brand=nopanel` | Beispiel-Branding der Login-Seite |
|
||||
|
||||
## Screenshots (`tools/screenshots.py`)
|
||||
|
||||
Rendert die Bilder aus `docs/screenshots/` neu. Voraussetzung ist ein
|
||||
headless Chromium; der Pfad kommt aus `$CHROME_BIN` oder wird in den
|
||||
üblichen Verzeichnissen gesucht.
|
||||
|
||||
```bash
|
||||
python3 tools/demo/build.py /tmp/uvt-demo
|
||||
php -S 127.0.0.1:8123 -t /tmp/uvt-demo &
|
||||
CHROME_BIN=/usr/bin/chromium python3 tools/screenshots.py
|
||||
```
|
||||
|
||||
Die Skripte sind Hilfsmittel für die Entwicklung – im Betrieb werden sie nicht
|
||||
benötigt und sind per `.htaccess` nicht über HTTP erreichbar.
|
||||
78
tools/screenshots.py
Executable file
78
tools/screenshots.py
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Erzeugt die Screenshots in docs/screenshots aus der laufenden Demo-Instanz.
|
||||
|
||||
Voraussetzung:
|
||||
python3 tools/demo/build.py /tmp/uvt-demo
|
||||
php -S 127.0.0.1:8123 -t /tmp/uvt-demo &
|
||||
|
||||
Aufruf:
|
||||
python3 tools/screenshots.py [basis-url]
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT = os.path.join(ROOT, 'docs', 'screenshots')
|
||||
SCALE = 2
|
||||
|
||||
# (Datei, Pfad, Breite, Hoehe) – Breite/Hoehe in CSS-Pixeln, Ausgabe in 2x
|
||||
SHOTS = [
|
||||
('login.png', 'login.php?anon=1', 1200, 850),
|
||||
('login-branding.png', 'login.php?anon=1&brand=custom', 1200, 850),
|
||||
('voucher-form.png', 'index.php', 1200, 880),
|
||||
('voucher-result.png', 'index.php?demo=result', 1200, 900),
|
||||
('bulk-vouchers.png', 'index.php?demo=bulk', 1200, 800),
|
||||
('admin-dashboard.png', 'admin/index.php', 1200, 1400),
|
||||
('admin-dashboard-dark.png', 'admin/index.php?theme=dark', 1200, 1400),
|
||||
('vouchers.png', 'admin/vouchers.php', 1200, 1120),
|
||||
('settings.png', 'admin/settings.php', 1200, 950),
|
||||
('settings-login.png', 'admin/settings.php?tab=login', 1200, 1000),
|
||||
('settings-branding.png', 'admin/settings.php?tab=branding', 1200, 900),
|
||||
('api-keys.png', 'admin/api_keys.php?demo=new', 1200, 830),
|
||||
('integrations.png', 'admin/integrations.php', 1200, 900),
|
||||
('two-factor.png', 'admin/security.php', 1200, 900),
|
||||
('updater.png', 'admin/update.php', 1200, 780),
|
||||
('updater-available.png', 'admin/update.php?demo=available', 1200, 780),
|
||||
('mobile-vouchers.png', 'admin/users.php', 430, 860),
|
||||
('maintenance.png', 'updater/templates/maintenance.html', 1200, 700),
|
||||
]
|
||||
|
||||
CANDIDATES = [
|
||||
os.environ.get('CHROME_BIN', ''),
|
||||
'/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome',
|
||||
'/opt/pw-browsers/chromium', shutil.which('chromium') or '',
|
||||
]
|
||||
|
||||
|
||||
def find_browser() -> str:
|
||||
for path in CANDIDATES:
|
||||
if path and os.path.isfile(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
raise SystemExit('Kein Chromium gefunden – Pfad ueber CHROME_BIN setzen.')
|
||||
|
||||
|
||||
def main() -> int:
|
||||
base = (sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8123').rstrip('/')
|
||||
browser = find_browser()
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
for name, path, width, height in SHOTS:
|
||||
target = os.path.join(OUT, name)
|
||||
subprocess.run([
|
||||
browser, '--headless', '--no-sandbox', '--disable-gpu', '--hide-scrollbars',
|
||||
'--force-color-profile=srgb', '--disable-lcd-text', '--font-render-hinting=none',
|
||||
'--virtual-time-budget=4000', '--force-prefers-reduced-motion',
|
||||
'--force-device-scale-factor=%d' % SCALE,
|
||||
'--window-size=%d,%d' % (width, height),
|
||||
'--screenshot=%s' % target,
|
||||
'%s/%s' % (base, path),
|
||||
], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
print('%-28s %s' % (name, 'ok' if os.path.exists(target) else 'FEHLER'))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue