Some checks are pending
CI / PHP Lint (push) Waiting to run
CI / PHP Lint (pull_request) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / PHP Lint-1 (push) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (push) Waiting to run
Jede Display-Seite bringt jetzt ihr eigenes Erscheinungsbild mit – der Empfang sieht anders aus als der Tagungsraum nebenan: - eigenes Logo (leer = Logo aus den Einstellungen) - formatfüllendes Hintergrundbild mit einstellbarer Abdunklung (0–90 %), damit die Karte auf hellen Fotos lesbar bleibt - eigene Akzentfarbe für den Knopf (leer = Farbe aus dem Design-Tab) - Karte wahlweise hell oder dunkel; auf Fotos wirkt dunkel meist ruhiger Logo und Hintergrund lassen sich hochladen oder als URL hinterlegen; beim Löschen einer Display-Seite verschwinden die hochgeladenen Dateien mit. Nebenbei aufgeräumt: das Bildfeld (Vorschau + Upload + URL + Entfernen) lag als Funktion in admin/settings.php und wird jetzt von beiden Seiten genutzt – Ui::imageField() für die Darstellung, Upload::resolveField() für die Auswertung. Sicherheit: die Akzentfarbe landet in einem style-Attribut, deshalb wird sie sowohl beim Speichern als auch beim Ausgeben auf eine echte Hex-Farbe geprüft; ein Test hält das fest. Migration 0006, database.sql nachgezogen, 4 neue Tests (42 gesamt), Screenshots ergänzt, Version 2.8.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
4.9 KiB
Python
Executable file
123 lines
4.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""Baut eine Demo-Instanz des Voucher-Tools ohne Datenbank.
|
||
|
||
Die Demo dient zwei Zwecken:
|
||
* Screenshots fuer die Dokumentation reproduzierbar erzeugen
|
||
* Smoke-Test: rendert jede Seite einmal ohne echte Datenbank
|
||
|
||
Ablauf: Projekt in ein Zielverzeichnis kopieren, das Overlay darueberlegen
|
||
(Stubs fuer Database/Auth) und ein paar klar benannte Demo-Zustaende in die
|
||
Kopie patchen. Das Original bleibt unveraendert.
|
||
|
||
python3 tools/demo/build.py /tmp/uvt-demo
|
||
php -S 127.0.0.1:8123 -t /tmp/uvt-demo
|
||
"""
|
||
import os
|
||
import shutil
|
||
import sys
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
OVERLAY = os.path.join(ROOT, 'tools', 'demo', 'overlay')
|
||
# Nur auf oberster Ebene ueberspringen – assets/vendor gehoert dazu!
|
||
SKIP_TOP = {'.git', '.github', 'tools', 'docs', 'tests', 'vendor', 'node_modules'}
|
||
|
||
|
||
def copy_project(target: str) -> None:
|
||
if os.path.exists(target):
|
||
shutil.rmtree(target)
|
||
|
||
def ignore(directory, names):
|
||
if os.path.abspath(directory) == ROOT:
|
||
return [n for n in names if n in SKIP_TOP]
|
||
return []
|
||
|
||
shutil.copytree(ROOT, target, ignore=ignore)
|
||
for name in os.listdir(OVERLAY):
|
||
src = os.path.join(OVERLAY, name)
|
||
dst = os.path.join(target, name)
|
||
if os.path.isdir(src):
|
||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||
else:
|
||
shutil.copy2(src, dst)
|
||
|
||
|
||
def patch(path: str, anchor: str, replacement: str) -> None:
|
||
"""Ersetzt einen Anker in der Demo-Kopie. Fehlt der Anker, bricht der
|
||
Build ab – so faellt auf, wenn sich die Vorlage geaendert hat."""
|
||
with open(path, encoding='utf-8') as fh:
|
||
content = fh.read()
|
||
if anchor not in content:
|
||
raise SystemExit('Anker nicht gefunden in %s:\n%s' % (path, anchor[:80]))
|
||
with open(path, 'w', encoding='utf-8') as fh:
|
||
fh.write(content.replace(anchor, replacement, 1))
|
||
|
||
|
||
def apply_demo_states(target: str) -> None:
|
||
# Voucher-Ergebnis und Bulk-Ansicht ohne Controller-Zugriff zeigen
|
||
anchor = "$currentUser = $auth->isLoggedIn() ? $auth->getCurrentUser() : null;"
|
||
patch(os.path.join(target, 'index.php'), anchor, anchor + """
|
||
|
||
// --- Demo-Instanz: Zustaende ohne UniFi-Controller darstellen ---
|
||
if (($_GET['demo'] ?? '') === 'result') {
|
||
$voucherCreated = true;
|
||
$voucherCode = '4829-17364';
|
||
$voucherData = ['code' => '4829-17364', 'site_name' => 'Hauptstandort Nord', 'max_uses' => 2,
|
||
'expire_min' => 480, 'expiry_date' => '23.09.2026', 'expiry_time' => '08:00'];
|
||
}
|
||
if (($_GET['demo'] ?? '') === 'bulk') {
|
||
$bulkCreated = true;
|
||
$bulkVouchers = [];
|
||
foreach (['4829-17364', '5517-90422', '7731-64508', '2094-38177', '6640-52913'] as $code) {
|
||
$bulkVouchers[] = ['code' => $code, 'site_name' => 'Hauptstandort Nord', 'max_uses' => 2,
|
||
'expire_min' => 480, 'expiry_date' => '23.09.2026', 'expiry_time' => '08:00'];
|
||
}
|
||
}
|
||
""")
|
||
|
||
# Live-Voucher-Liste automatisch laden (sonst wartet sie auf Site-Auswahl)
|
||
patch(os.path.join(target, 'admin', 'vouchers.php'), "</script>\n</body>", """</script>
|
||
<script>
|
||
// Demo-Instanz: Site vorauswaehlen und Liste laden
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
const sel = document.getElementById('siteSelect');
|
||
if (sel) { sel.value = '1'; loadVouchers(false); }
|
||
});
|
||
</script>
|
||
</body>""")
|
||
|
||
# Frisch erzeugten API-Schluessel zeigen
|
||
patch(os.path.join(target, 'admin', 'api_keys.php'), '$keys = $db->fetchAll(',
|
||
"if (($_GET['demo'] ?? '') === 'new') { $newKey = 'uvt_3f9a2c7d41e8b60592af18cc4d7e0b3a95f2617c'; }\n$keys = $db->fetchAll(")
|
||
|
||
# Kiosk: ausgegebenen Code zeigen, ohne echten Controller (?demo=code)
|
||
patch(os.path.join(target, 'kiosk.php'),
|
||
"$headline = trim((string)($kiosk['headline'] ?? ''))",
|
||
'''if (($_GET['demo'] ?? '') === 'code' && $kiosk) {
|
||
$voucher = ['code' => '4829-17364', 'site_name' => $kiosk['site_name'], 'max_uses' => 2,
|
||
'expire_min' => 480, 'expiry_date' => '24.09.2026', 'expiry_time' => '08:00'];
|
||
}
|
||
|
||
$headline = trim((string)($kiosk['headline'] ?? ''))''')
|
||
|
||
# Kiosk-Verwaltung: Formular fuer den Screenshot geoeffnet zeigen
|
||
patch(os.path.join(target, 'admin', 'kiosks.php'),
|
||
'<div class="modal" id="kioskModal">',
|
||
'<div class="modal<?= ($_GET[\'demo\'] ?? \'\') === \'form\' ? \' active\' : \'\' ?>" id="kioskModal">')
|
||
|
||
# Theme per Query-Parameter erzwingen (fuer Dark-Mode-Screenshots)
|
||
ui = os.path.join(target, 'includes', 'Ui.php')
|
||
patch(ui, 'var s=localStorage.getItem("theme");',
|
||
'var s=new URLSearchParams(location.search).get("theme")||localStorage.getItem("theme");')
|
||
|
||
|
||
def main() -> int:
|
||
target = sys.argv[1] if len(sys.argv) > 1 else '/tmp/uvt-demo'
|
||
copy_project(target)
|
||
apply_demo_states(target)
|
||
print('Demo-Instanz gebaut: %s' % target)
|
||
print('Start: php -S 127.0.0.1:8123 -t %s' % target)
|
||
return 0
|
||
|
||
|
||
if __name__ == '__main__':
|
||
raise SystemExit(main())
|