Feature: HTMX + Jinja2 Frontend ersetzt Next.js komplett

- Kein Node.js, kein npm, kein Build-Schritt mehr
- HTMX 2.0.4 + PicoCSS 2 vendored in backend/static/
- Jinja2 Templates für alle 9 Seiten (Dashboard, ZFS, Snapshots,
  Shares, Identities, Logs, Services, Navigator, Login)
- HTMX Fragments für Live-Updates (30s Polling Dashboard)
- JWT als httpOnly Cookie statt localStorage
- Disk Usage zeigt TB/PB korrekt (Jinja2 serverseitig formatiert)
- Update-safe: nur Python-Deps, keine npm-Abhängigkeiten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 18:45:46 +02:00
parent 654df5b98f
commit 5ecd143535
44 changed files with 1123 additions and 6129 deletions
+10 -14
View File
@@ -15,11 +15,12 @@ from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
# Add backend to path for imports
sys.path.insert(0, str(Path(__file__).parent))
from routers import auth, pools, datasets, snapshots, navigator, identities, shares, system
from routers import auth, pools, datasets, snapshots, navigator, identities, shares, system, pages
from services.zfs_runner import zfs_runner
# Configure logging
@@ -114,7 +115,11 @@ async def websocket_endpoint(websocket: WebSocket):
ws_clients.discard(websocket)
# Include routers (must be before static files mounting)
# Static assets (HTMX, PicoCSS — vendored)
_static_dir = os.path.join(os.path.dirname(__file__), "static")
app.mount("/static", StaticFiles(directory=_static_dir), name="static")
# Include API routers first
app.include_router(auth.router)
app.include_router(pools.router)
app.include_router(datasets.router)
@@ -124,6 +129,9 @@ app.include_router(identities.router)
app.include_router(shares.router)
app.include_router(system.router)
# HTML pages router last (catches /, /login, /zfs, /fragments/*)
app.include_router(pages.router)
# Health check endpoint (no auth required)
@@ -156,18 +164,6 @@ async def status_check():
}
# Root endpoint - API info only
@app.get("/")
async def root(request: Request):
"""API info"""
return {
"name": "ZMB Webui API",
"version": "1.0.0",
"docs": "/docs",
"frontend": str(request.base_url)
}
# Error handler
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):