Web-MVP (M1): FastAPI-API, Testoberflaeche, tische.ini-Upload, zwei neue Beispiele
- web/main.py: Session-basierte REST-API um den GA-Kern (POST /sitzung, .../tische, .../tische-datei, .../personen, .../berechnen, GET .../ergebnis[.svg]); Sessions in-memory mit 24h-TTL - web/static/index.html: einfache Browser-Testoberflaeche (Tisch-Tabelle mit Presets, tische.ini-Upload, CSV-Gaesteliste, Kennzahlen, SVG) - bin/run_web.bat/.sh + run_web_helper.ps1: start/stop/status inkl. Aufraeumen verwaister uvicorn-Reload-Kindprozesse (Zombie-Sockets) - work/hochzeit: 120 Hochzeitsgaeste, Ehrentafel, Kindertisch/Teenager- Tisch fest gesetzt, Kinderalter normalverteilt, ~85% Auslastung - work/innenhoefe: 300 Gaeste in zwei trapezfoermigen Innenhoefen mit 4er-Tischen, 12 VIPs vor der Buehne, Umfeld.svg mit Tanzflaeche/Band - Roadmap: Konzeptentscheidungen 1-11 (Zielgruppe, Einmalzahlung, MoR, Hetzner, Zwei-Marken-Strategie Sitz&Platz/SeatWonder), Namensrecherche im Anhang, M1-Fortschritt und GA-Lasttest-Datenpunkt dokumentiert Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
@echo off
|
||||
REM ================================================================
|
||||
REM Verwaltet die lokale Web-Testinstanz (FastAPI/uvicorn, M1)
|
||||
REM
|
||||
REM run_web.bat start - startet den Server im Hintergrund
|
||||
REM run_web.bat stop - beendet den Server (inkl. Reload-Kindprozesse)
|
||||
REM run_web.bat status - zeigt ob und unter welcher PID er laeuft
|
||||
REM run_web.bat - startet im Vordergrund (Strg+C zum Beenden)
|
||||
REM
|
||||
REM http://localhost:8000 Testoberflaeche
|
||||
REM http://localhost:8000/docs interaktive API-Doku (Swagger)
|
||||
REM
|
||||
REM Die Prozess-/Portlogik liegt in run_web_helper.ps1 (cmd-Escaping
|
||||
REM von mehrzeiligen PowerShell-Kommandos ist zu fehleranfaellig).
|
||||
REM ================================================================
|
||||
|
||||
call "%~dp0setenv.bat" > nul
|
||||
|
||||
set WEB_PORT=8000
|
||||
set HELFER=powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0run_web_helper.ps1"
|
||||
|
||||
if /I "%~1"=="start" goto :start
|
||||
if /I "%~1"=="stop" goto :stop
|
||||
if /I "%~1"=="status" goto :status
|
||||
if "%~1"=="" goto :vordergrund
|
||||
echo Unbekannter Parameter "%~1" - erlaubt: start ^| stop ^| status
|
||||
exit /b 1
|
||||
|
||||
REM ----------------------------------------------------------------
|
||||
:vordergrund
|
||||
call :venv_pruefen || exit /b 1
|
||||
echo Starte Web-Testinstanz im Vordergrund (Strg+C zum Beenden) ...
|
||||
python -m uvicorn main:app --reload --app-dir "%PLATZ%\web" --port %WEB_PORT%
|
||||
exit /b %errorlevel%
|
||||
|
||||
REM ----------------------------------------------------------------
|
||||
:start
|
||||
call :venv_pruefen || exit /b 1
|
||||
%HELFER% check %WEB_PORT%
|
||||
if not errorlevel 1 (
|
||||
echo Server laeuft bereits auf Port %WEB_PORT% - erst "run_web.bat stop".
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%PLATZ%\log" mkdir "%PLATZ%\log"
|
||||
echo Starte Web-Testinstanz im Hintergrund ...
|
||||
start "platz-web" /MIN cmd /c "call "%~dp0setenv.bat" > nul && call "%PLATZ%\.venv\Scripts\activate.bat" && python -m uvicorn main:app --reload --app-dir "%PLATZ%\web" --port %WEB_PORT% > "%PLATZ%\log\run_web.log" 2>&1"
|
||||
REM kurz warten und pruefen, ob der Server wirklich hochkommt
|
||||
REM (ping statt timeout: funktioniert auch ohne interaktive Konsole)
|
||||
ping -n 4 127.0.0.1 > nul
|
||||
%HELFER% check %WEB_PORT%
|
||||
if errorlevel 1 (
|
||||
echo FEHLER: Server ist nicht gestartet - siehe %PLATZ%\log\run_web.log
|
||||
exit /b 1
|
||||
)
|
||||
echo Server laeuft: http://localhost:%WEB_PORT% ^(Logdatei: log\run_web.log^)
|
||||
exit /b 0
|
||||
|
||||
REM ----------------------------------------------------------------
|
||||
:stop
|
||||
echo Beende alle uvicorn-Prozesse dieses Projekts auf Port %WEB_PORT% ...
|
||||
%HELFER% stop %WEB_PORT%
|
||||
exit /b 0
|
||||
|
||||
REM ----------------------------------------------------------------
|
||||
:status
|
||||
%HELFER% status %WEB_PORT%
|
||||
exit /b %errorlevel%
|
||||
|
||||
REM ----------------------------------------------------------------
|
||||
:venv_pruefen
|
||||
if exist "%PLATZ%\.venv\Scripts\activate.bat" (
|
||||
call "%PLATZ%\.venv\Scripts\activate.bat"
|
||||
exit /b 0
|
||||
)
|
||||
echo FEHLER: kein .venv gefunden - bitte zuerst bin\install_py.bat ausfuehren
|
||||
exit /b 1
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# ================================================================
|
||||
# Verwaltet die lokale Web-Testinstanz (FastAPI/uvicorn, M1)
|
||||
#
|
||||
# run_web.sh start - startet den Server im Hintergrund
|
||||
# run_web.sh stop - beendet den Server (inkl. Reload-Kindprozesse)
|
||||
# run_web.sh status - zeigt ob und unter welcher PID er laeuft
|
||||
# run_web.sh - startet im Vordergrund (Strg+C zum Beenden)
|
||||
#
|
||||
# http://localhost:8000 Testoberflaeche
|
||||
# http://localhost:8000/docs interaktive API-Doku (Swagger)
|
||||
# ================================================================
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/setenv.sh" > /dev/null
|
||||
|
||||
WEB_PORT=8000
|
||||
PIDDATEI="$PLATZ/log/run_web.pid"
|
||||
LOGDATEI="$PLATZ/log/run_web.log"
|
||||
|
||||
venv_pruefen() {
|
||||
if [ -f "$PLATZ/.venv/bin/activate" ]; then
|
||||
source "$PLATZ/.venv/bin/activate"
|
||||
else
|
||||
echo "FEHLER: kein .venv gefunden - bitte zuerst bin/install_py.sh ausfuehren"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
lauft_pid() {
|
||||
# gibt die PID des Servers aus, wenn er laeuft (Rueckgabe 0), sonst 1
|
||||
if [ -f "$PIDDATEI" ]; then
|
||||
local pid
|
||||
pid=$(cat "$PIDDATEI")
|
||||
if kill -0 "$pid" 2> /dev/null; then
|
||||
echo "$pid"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$PIDDATEI"
|
||||
fi
|
||||
# Fallback: nach dem Prozess suchen (z.B. PID-Datei verloren)
|
||||
pgrep -f "uvicorn main:app.*--port $WEB_PORT" | head -1 && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
start)
|
||||
venv_pruefen || exit 1
|
||||
if pid=$(lauft_pid); then
|
||||
echo "Server laeuft bereits (PID $pid) - erst 'run_web.sh stop'."
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$PLATZ/log"
|
||||
echo "Starte Web-Testinstanz im Hintergrund ..."
|
||||
nohup python -m uvicorn main:app --reload \
|
||||
--app-dir "$PLATZ/web" --port "$WEB_PORT" \
|
||||
> "$LOGDATEI" 2>&1 &
|
||||
echo $! > "$PIDDATEI"
|
||||
sleep 3
|
||||
if pid=$(lauft_pid); then
|
||||
echo "Server laeuft: http://localhost:$WEB_PORT (PID $pid, Log: log/run_web.log)"
|
||||
else
|
||||
echo "FEHLER: Server ist nicht gestartet - siehe $LOGDATEI"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
gestoppt=0
|
||||
if pid=$(lauft_pid); then
|
||||
# ganze Prozessgruppe beenden (uvicorn-Reloader + Kindprozesse)
|
||||
pkill -TERM -P "$pid" 2> /dev/null && gestoppt=1
|
||||
kill "$pid" 2> /dev/null && gestoppt=1
|
||||
fi
|
||||
# Nachzuegler, deren Elternprozess schon weg ist
|
||||
pkill -f "uvicorn main:app.*--port $WEB_PORT" 2> /dev/null && gestoppt=1
|
||||
rm -f "$PIDDATEI"
|
||||
if [ "$gestoppt" = "1" ]; then echo "Beendet."; else echo "Es lief nichts."; fi
|
||||
;;
|
||||
status)
|
||||
if pid=$(lauft_pid); then
|
||||
echo "LAEUFT: http://localhost:$WEB_PORT (PID $pid)"
|
||||
else
|
||||
echo "GESTOPPT: kein Server auf Port $WEB_PORT"
|
||||
fi
|
||||
;;
|
||||
"")
|
||||
venv_pruefen || exit 1
|
||||
echo "Starte Web-Testinstanz im Vordergrund (Strg+C zum Beenden) ..."
|
||||
exec python -m uvicorn main:app --reload \
|
||||
--app-dir "$PLATZ/web" --port "$WEB_PORT"
|
||||
;;
|
||||
*)
|
||||
echo "Unbekannter Parameter '$1' - erlaubt: start | stop | status"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,86 @@
|
||||
# ================================================================
|
||||
# PowerShell-Helfer fuer run_web.bat (start/stop/status)
|
||||
# Aufruf: powershell -NoProfile -ExecutionPolicy Bypass
|
||||
# -File run_web_helper.ps1 <status|stop|check> <port>
|
||||
#
|
||||
# "check" liefert nur den Exitcode (0 = Server lauscht, 1 = nicht)
|
||||
# und keine Ausgabe - fuer die Pruefung nach dem Start.
|
||||
#
|
||||
# Verwaiste Socket-Eintraege (Listener, deren Prozess nicht mehr
|
||||
# existiert - passiert nach abgestuerzten Reload-Kindprozessen)
|
||||
# werden ueberall ignoriert.
|
||||
# ================================================================
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][ValidateSet('status', 'stop', 'check')]
|
||||
[string]$Aktion,
|
||||
[int]$Port = 8000
|
||||
)
|
||||
|
||||
function Lebende-Listener([int]$p) {
|
||||
try {
|
||||
Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction Stop |
|
||||
Where-Object { Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue }
|
||||
} catch { @() }
|
||||
}
|
||||
|
||||
function Uvicorn-Prozesse {
|
||||
Get-CimInstance Win32_Process -Filter "Name like 'python%'" |
|
||||
Where-Object { $_.CommandLine -match 'uvicorn main:app' }
|
||||
}
|
||||
|
||||
switch ($Aktion) {
|
||||
|
||||
'check' {
|
||||
if (Lebende-Listener $Port) { exit 0 } else { exit 1 }
|
||||
}
|
||||
|
||||
'status' {
|
||||
$l = Lebende-Listener $Port | Select-Object -First 1
|
||||
if ($l) {
|
||||
"LAEUFT: http://localhost:$Port (Listener-PID $($l.OwningProcess))"
|
||||
foreach ($e in Uvicorn-Prozesse) { " uvicorn-Prozess: PID $($e.ProcessId)" }
|
||||
exit 0
|
||||
} else {
|
||||
"GESTOPPT: kein Server auf Port $Port"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
'stop' {
|
||||
$gefunden = $false
|
||||
$alle = Get-CimInstance Win32_Process -Filter "Name like 'python%'"
|
||||
$reloaderPids = @()
|
||||
foreach ($e in ($alle | Where-Object { $_.CommandLine -match 'uvicorn main:app' })) {
|
||||
$gefunden = $true
|
||||
$reloaderPids += $e.ProcessId
|
||||
# erst die Kindprozesse (Reload-Worker), dann den Reloader selbst
|
||||
$alle | Where-Object { $_.ParentProcessId -eq $e.ProcessId } | ForEach-Object {
|
||||
Stop-Process -Id $_.ProcessId -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
Stop-Process -Id $e.ProcessId -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
# Zombie-Jagd: uvicorn-Reload-Worker sind multiprocessing-Spawn-Kinder
|
||||
# ("spawn_main(parent_pid=N, ...)"). Stirbt ihr Reloader unsanft,
|
||||
# ueberleben sie MIT geerbtem Server-Socket und bedienen den Port
|
||||
# unsichtbar weiter (weder "uvicorn" in der Kommandozeile noch
|
||||
# Eintrag als Socket-Besitzer!). Erkennung: spawn_main-Prozess,
|
||||
# dessen parent_pid tot oder ein eben beendeter Reloader ist.
|
||||
foreach ($k in ($alle | Where-Object { $_.CommandLine -match 'multiprocessing.spawn.*spawn_main' })) {
|
||||
if ($k.CommandLine -match 'parent_pid=(\d+)') {
|
||||
$eltern = [int]$Matches[1]
|
||||
$elternTot = -not (Get-Process -Id $eltern -ErrorAction SilentlyContinue)
|
||||
if ($elternTot -or ($reloaderPids -contains $eltern)) {
|
||||
$gefunden = $true
|
||||
Stop-Process -Id $k.ProcessId -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
# Nachzuegler: alles Lebende, das den Port noch haelt
|
||||
Lebende-Listener $Port | ForEach-Object {
|
||||
$gefunden = $true
|
||||
Stop-Process -Id $_.OwningProcess -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($gefunden) { 'Beendet.' } else { 'Es lief nichts.' }
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user