refactor: complete rewrite as React+FastAPI dashboard

Replace monolithic Jinja2 template with modern stack:

Backend (FastAPI):
- Modular router/service architecture
- Async PostgreSQL (asyncpg) for news from n8n pipeline
- Live Unraid server stats (2 servers via API)
- Home Assistant, Vikunja tasks, weather (wttr.in)
- WebSocket broadcast for real-time updates (15s)
- TTL cache per endpoint, all config via ENV vars

Frontend (React + Vite + TypeScript):
- Glassmorphism dark theme with Tailwind CSS
- Responsive grid: mobile/tablet/desktop/ultrawide
- Weather cards, hourly forecast, news with category tabs
- Server stats (CPU ring, RAM bar, Docker list)
- Home Assistant controls, task management
- Live clock, WebSocket connection indicator

Infrastructure:
- Multi-stage Dockerfile (node:22-alpine + python:3.11-slim)
- docker-compose with full ENV configuration
- Kaniko CI/CD pipeline for GitLab registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam 2026-03-02 01:48:51 +01:00
parent 4bbc125a67
commit 9f7330e217
48 changed files with 6390 additions and 1461 deletions

47
server/routers/tasks.py Normal file
View file

@ -0,0 +1,47 @@
"""Vikunja tasks router."""
from __future__ import annotations
import logging
from typing import Any, Dict
from fastapi import APIRouter
from server.cache import cache
from server.config import settings
from server.services.vikunja_service import fetch_tasks
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api", tags=["tasks"])
CACHE_KEY = "tasks"
@router.get("/tasks")
async def get_tasks() -> Dict[str, Any]:
"""Return Vikunja task data.
The exact shape depends on what ``fetch_tasks`` returns; on failure an
error stub is returned instead::
{ "error": true, "message": "..." }
"""
# --- cache hit? -----------------------------------------------------------
cached = await cache.get(CACHE_KEY)
if cached is not None:
return cached
# --- cache miss -----------------------------------------------------------
try:
data: Dict[str, Any] = await fetch_tasks(
settings.vikunja_url,
settings.vikunja_token,
)
except Exception as exc:
logger.exception("Failed to fetch Vikunja tasks")
return {"error": True, "message": str(exc)}
await cache.set(CACHE_KEY, data, settings.vikunja_cache_ttl)
return data