"""Dashboard configuration loader - supports YAML config files."""
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
import json
import os
import yaml
# Base paths
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CONFIG_FILE = os.path.join(BASE_DIR, "config.yaml")
SECRETS_FILE = os.path.join(BASE_DIR, "secrets.yaml")
SETTINGS_FILE = os.path.join(BASE_DIR, "settings.json")
@dataclass
class Service:
name: str
url: str
ip: str
port: int
category: str
icon: str = "server"
favorite: bool = False
critical: bool = False
group: Optional[str] = None
health_check: Optional[Dict] = None
@dataclass
class HealthCheckConfig:
url: str
timeout: float = 5.0
# Icon SVG paths
SERVICE_ICONS = {
"shield": '',
"globe": '',
"archive": '',
"lock": '',
"film": '',
"star": '',
"video": '',
"tv": '',
"music": '',
"search": '',
"download": '',
"cog": '',
"chart": '',
"heartbeat": '',
"key": '',
"git": '',
"workflow": '',
"book": '',
"image": '',
"brain": '',
"home": '',
"camera": '',
"message": '',
"server": '',
"database": '',
}
# ============================================================
# Configuration Loading
# ============================================================
def load_yaml(path: str) -> Dict:
"""Load YAML file, return empty dict if not found."""
if os.path.exists(path):
try:
with open(path, 'r') as f:
return yaml.safe_load(f) or {}
except Exception as e:
print(f"Warning: Failed to load {path}: {e}")
return {}
def load_config() -> Dict:
"""Load main config and merge with secrets."""
config = load_yaml(CONFIG_FILE)
secrets = load_yaml(SECRETS_FILE)
# Merge secrets into config
if secrets:
for key in ['proxmox', 'pbs', 'opnsense', 'sabnzbd']:
if key in secrets and key in config:
config[key].update(secrets[key])
elif key in secrets:
config[key] = secrets[key]
return config
# Load configuration
_config = load_config()
# ============================================================
# Configuration Values (with defaults for backward compatibility)
# ============================================================
# Dashboard settings
DASHBOARD_TITLE = _config.get('dashboard', {}).get('title', 'Homelab Dashboard')
REFRESH_INTERVAL = _config.get('dashboard', {}).get('refresh_interval', 30)
# Proxmox configuration
_proxmox = _config.get('proxmox', {})
PROXMOX_ENABLED = _proxmox.get('enabled', True)
PROXMOX_NODES = _proxmox.get('nodes', [])
PROXMOX_API_TOKEN = _proxmox.get('api_token', '')
PROXMOX_API_SECRET = _proxmox.get('api_secret', '')
# PBS configuration
_pbs = _config.get('pbs', {})
PBS_ENABLED = _pbs.get('enabled', True)
PBS_URL = _pbs.get('url', '')
PBS_API_TOKEN = _pbs.get('api_token', '')
PBS_API_SECRET = _pbs.get('api_secret', '')
# OPNsense configuration
_opnsense = _config.get('opnsense', {})
OPNSENSE_URL = _opnsense.get('url', '')
OPNSENSE_API_KEY = _opnsense.get('api_key', '')
OPNSENSE_API_SECRET = _opnsense.get('api_secret', '')
# Prometheus configuration
PROMETHEUS_URL = _config.get('prometheus', {}).get('url', '')
# Camera configuration
_cameras = _config.get('cameras', {})
CAMERAS_ENABLED = _cameras.get('enabled', False)
GO2RTC_URL = _cameras.get('go2rtc_url', '')
CAMERAS = _cameras.get('streams', [])
# Sabnzbd configuration
_sabnzbd = _config.get('sabnzbd', {})
SABNZBD_URL = _sabnzbd.get('url', '')
SABNZBD_API_KEY = _sabnzbd.get('api_key', '')
# Uptime Kuma configuration
_uptime = _config.get('uptime_kuma', {})
UPTIME_KUMA_URL = _uptime.get('url', '')
UPTIME_KUMA_STATUS_PAGE = _uptime.get('status_page', 'default')
# Docker hosts
_docker = _config.get('docker', {})
DOCKER_ENABLED = _docker.get('enabled', False)
DOCKER_HOSTS = _docker.get('hosts', [])
# Categories
CATEGORIES = _config.get('categories', {
"Infrastructure": {"color": "blue", "icon": "server"},
"Media": {"color": "purple", "icon": "film"},
"Monitoring": {"color": "amber", "icon": "chart"},
"Apps": {"color": "emerald", "icon": "cog"},
"Home": {"color": "cyan", "icon": "home"},
})
# Service groups
SERVICE_GROUPS = _config.get('service_groups', {})
# Load services from config
def _load_services() -> List[Service]:
"""Load services from YAML config."""
services = []
for svc in _config.get('services', []):
health_check = None
if 'health_check' in svc:
health_check = svc['health_check']
services.append(Service(
name=svc['name'],
url=svc['url'],
ip=svc['ip'],
port=svc['port'],
category=svc['category'],
icon=svc.get('icon', 'server'),
favorite=svc.get('favorite', False),
critical=svc.get('critical', False),
group=svc.get('group'),
health_check=health_check,
))
return services
SERVICES = _load_services()
# Build SERVICE_CHECK_OVERRIDES from health_check configs
SERVICE_CHECK_OVERRIDES = {}
for svc in SERVICES:
if svc.health_check:
SERVICE_CHECK_OVERRIDES[svc.name] = (
svc.health_check.get('url', f"http://{svc.ip}:{svc.port}/"),
svc.health_check.get('timeout', 5.0)
)
# ============================================================
# Settings (user preferences, stored in settings.json)
# ============================================================
DEFAULT_SETTINGS = {
"refresh_interval": REFRESH_INTERVAL,
"theme": _config.get('dashboard', {}).get('theme', 'dark'),
"favorites": _config.get('favorites', []),
"collapsed_categories": [],
"show_response_times": _config.get('dashboard', {}).get('show_response_times', True),
"show_icons": _config.get('dashboard', {}).get('show_icons', True),
}
def load_settings():
try:
if os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, 'r') as f:
return {**DEFAULT_SETTINGS, **json.load(f)}
except:
pass
return DEFAULT_SETTINGS.copy()
def save_settings(settings):
try:
with open(SETTINGS_FILE, 'w') as f:
json.dump(settings, f, indent=2)
return True
except:
return False
# ============================================================
# Helper Functions
# ============================================================
def get_services_by_category():
categories = {}
for service in SERVICES:
if service.category not in categories:
categories[service.category] = []
categories[service.category].append(service)
return categories
def get_favorites():
settings = load_settings()
fav_names = settings.get("favorites", [])
return [s for s in SERVICES if s.name in fav_names or s.favorite]
def get_critical_services():
return [s for s in SERVICES if s.critical]
def get_service_icon(icon_name):
return SERVICE_ICONS.get(icon_name, SERVICE_ICONS["server"])
def reload_config():
"""Reload configuration from files (for runtime updates)."""
global _config, SERVICES, SERVICE_CHECK_OVERRIDES, CATEGORIES, SERVICE_GROUPS
global PROXMOX_NODES, PROXMOX_API_TOKEN, PROXMOX_API_SECRET
global PBS_URL, PBS_API_TOKEN, PBS_API_SECRET
global GO2RTC_URL, CAMERAS, DOCKER_HOSTS
_config = load_config()
SERVICES = _load_services()
# Rebuild overrides
SERVICE_CHECK_OVERRIDES.clear()
for svc in SERVICES:
if svc.health_check:
SERVICE_CHECK_OVERRIDES[svc.name] = (
svc.health_check.get('url', f"http://{svc.ip}:{svc.port}/"),
svc.health_check.get('timeout', 5.0)
)
# Update other config values
_proxmox = _config.get('proxmox', {})
PROXMOX_NODES = _proxmox.get('nodes', [])
PROXMOX_API_TOKEN = _proxmox.get('api_token', '')
PROXMOX_API_SECRET = _proxmox.get('api_secret', '')
_pbs = _config.get('pbs', {})
PBS_URL = _pbs.get('url', '')
PBS_API_TOKEN = _pbs.get('api_token', '')
PBS_API_SECRET = _pbs.get('api_secret', '')
_cameras = _config.get('cameras', {})
GO2RTC_URL = _cameras.get('go2rtc_url', '')
CAMERAS = _cameras.get('streams', [])
DOCKER_HOSTS = _config.get('docker', {}).get('hosts', [])
CATEGORIES = _config.get('categories', CATEGORIES)
SERVICE_GROUPS = _config.get('service_groups', {})