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:
parent
4bbc125a67
commit
9f7330e217
48 changed files with 6390 additions and 1461 deletions
98
server/config.py
Normal file
98
server/config.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"""Centralized configuration via environment variables."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnraidServer:
|
||||
name: str
|
||||
host: str
|
||||
api_key: str = ""
|
||||
port: int = 80
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
# --- Database (PostgreSQL) ---
|
||||
db_host: str = "10.10.10.10"
|
||||
db_port: int = 5433
|
||||
db_name: str = "openclaw"
|
||||
db_user: str = "sam"
|
||||
db_password: str = "sam"
|
||||
|
||||
# --- Weather ---
|
||||
weather_location: str = "Leverkusen"
|
||||
weather_location_secondary: str = "Rab,Croatia"
|
||||
weather_cache_ttl: int = 1800 # 30 min
|
||||
|
||||
# --- Home Assistant ---
|
||||
ha_url: str = "https://homeassistant.daddelolymp.de"
|
||||
ha_token: str = ""
|
||||
ha_cache_ttl: int = 30
|
||||
|
||||
# --- Vikunja Tasks ---
|
||||
vikunja_url: str = "http://10.10.10.10:3456/api/v1"
|
||||
vikunja_token: str = ""
|
||||
vikunja_cache_ttl: int = 60
|
||||
|
||||
# --- Unraid Servers ---
|
||||
unraid_servers: List[UnraidServer] = field(default_factory=list)
|
||||
unraid_cache_ttl: int = 15
|
||||
|
||||
# --- News ---
|
||||
news_cache_ttl: int = 300 # 5 min
|
||||
news_max_age_hours: int = 48
|
||||
|
||||
# --- Server ---
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8080
|
||||
debug: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "Settings":
|
||||
s = cls()
|
||||
s.db_host = os.getenv("DB_HOST", s.db_host)
|
||||
s.db_port = int(os.getenv("DB_PORT", str(s.db_port)))
|
||||
s.db_name = os.getenv("DB_NAME", s.db_name)
|
||||
s.db_user = os.getenv("DB_USER", s.db_user)
|
||||
s.db_password = os.getenv("DB_PASSWORD", s.db_password)
|
||||
|
||||
s.weather_location = os.getenv("WEATHER_LOCATION", s.weather_location)
|
||||
s.weather_location_secondary = os.getenv(
|
||||
"WEATHER_LOCATION_SECONDARY", s.weather_location_secondary
|
||||
)
|
||||
|
||||
s.ha_url = os.getenv("HA_URL", s.ha_url)
|
||||
s.ha_token = os.getenv("HA_TOKEN", s.ha_token)
|
||||
|
||||
s.vikunja_url = os.getenv("VIKUNJA_URL", s.vikunja_url)
|
||||
s.vikunja_token = os.getenv("VIKUNJA_TOKEN", s.vikunja_token)
|
||||
|
||||
s.debug = os.getenv("DEBUG", "").lower() in ("1", "true", "yes")
|
||||
|
||||
# Parse UNRAID_SERVERS JSON
|
||||
raw = os.getenv("UNRAID_SERVERS", "[]")
|
||||
try:
|
||||
servers_data = json.loads(raw)
|
||||
s.unraid_servers = [
|
||||
UnraidServer(
|
||||
name=srv.get("name", f"Server {i+1}"),
|
||||
host=srv.get("host", ""),
|
||||
api_key=srv.get("api_key", ""),
|
||||
port=int(srv.get("port", 80)),
|
||||
)
|
||||
for i, srv in enumerate(servers_data)
|
||||
if srv.get("host")
|
||||
]
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
s.unraid_servers = []
|
||||
|
||||
return s
|
||||
|
||||
|
||||
settings = Settings.from_env()
|
||||
Loading…
Add table
Add a link
Reference in a new issue