'use client' /** * Night Mode - Dashboard-gesteuerte Nachtabschaltung * * Ermoeglicht das automatische Stoppen und Starten von Docker-Services * nach Zeitplan. Manuelles Starten/Stoppen ebenfalls moeglich. */ import { useEffect, useState, useCallback } from 'react' interface NightModeConfig { enabled: boolean shutdown_time: string startup_time: string last_action: string | null last_action_time: string | null excluded_services: string[] } interface NightModeStatus { config: NightModeConfig current_time: string next_action: string | null next_action_time: string | null time_until_next_action: string | null services_status: Record } interface ServicesInfo { all_services: string[] excluded_services: string[] status: Record } export default function NightModePage() { const [status, setStatus] = useState(null) const [services, setServices] = useState(null) const [loading, setLoading] = useState(true) const [actionLoading, setActionLoading] = useState(null) const [error, setError] = useState(null) const [successMessage, setSuccessMessage] = useState(null) // Lokale Konfiguration fuer Bearbeitung const [editMode, setEditMode] = useState(false) const [editConfig, setEditConfig] = useState(null) const fetchData = useCallback(async () => { setError(null) try { const [statusRes, servicesRes] = await Promise.all([ fetch('/api/admin/night-mode'), fetch('/api/admin/night-mode/services'), ]) if (statusRes.ok) { const data = await statusRes.json() setStatus(data) if (!editMode) { setEditConfig(data.config) } } else { const errData = await statusRes.json() setError(errData.error || 'Fehler beim Laden des Status') } if (servicesRes.ok) { setServices(await servicesRes.json()) } } catch (err) { setError(err instanceof Error ? err.message : 'Verbindung zum Night-Scheduler fehlgeschlagen') } finally { setLoading(false) } }, [editMode]) useEffect(() => { fetchData() }, [fetchData]) // Auto-Refresh alle 30 Sekunden useEffect(() => { const interval = setInterval(fetchData, 30000) return () => clearInterval(interval) }, [fetchData]) const saveConfig = async () => { if (!editConfig) return setActionLoading('save') setError(null) setSuccessMessage(null) try { const response = await fetch('/api/admin/night-mode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editConfig), }) if (!response.ok) { const errData = await response.json() throw new Error(errData.error || 'Fehler beim Speichern') } setEditMode(false) setSuccessMessage('Konfiguration gespeichert') setTimeout(() => setSuccessMessage(null), 3000) fetchData() } catch (err) { setError(err instanceof Error ? err.message : 'Speichern fehlgeschlagen') } finally { setActionLoading(null) } } const executeAction = async (action: 'start' | 'stop') => { setActionLoading(action) setError(null) setSuccessMessage(null) try { const response = await fetch('/api/admin/night-mode/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action }), }) if (!response.ok) { const errData = await response.json() throw new Error(errData.error || `Fehler bei ${action}`) } const data = await response.json() setSuccessMessage(data.message || `${action === 'start' ? 'Gestartet' : 'Gestoppt'}`) setTimeout(() => setSuccessMessage(null), 5000) fetchData() } catch (err) { setError(err instanceof Error ? err.message : `${action} fehlgeschlagen`) } finally { setActionLoading(null) } } const toggleEnabled = async () => { if (!editConfig) return const newConfig = { ...editConfig, enabled: !editConfig.enabled } setEditConfig(newConfig) setActionLoading('toggle') setError(null) try { const response = await fetch('/api/admin/night-mode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newConfig), }) if (!response.ok) { throw new Error('Fehler beim Umschalten') } setSuccessMessage(newConfig.enabled ? 'Nachtmodus aktiviert' : 'Nachtmodus deaktiviert') setTimeout(() => setSuccessMessage(null), 3000) fetchData() } catch (err) { setError(err instanceof Error ? err.message : 'Umschalten fehlgeschlagen') // Zuruecksetzen bei Fehler setEditConfig({ ...editConfig }) } finally { setActionLoading(null) } } const getServiceStatusColor = (state: string) => { const lower = state.toLowerCase() if (lower === 'running' || lower.includes('up')) { return 'bg-green-100 text-green-800' } if (lower === 'exited' || lower.includes('exit')) { return 'bg-slate-100 text-slate-600' } if (lower === 'paused' || lower.includes('pause')) { return 'bg-yellow-100 text-yellow-800' } return 'bg-slate-100 text-slate-600' } const runningCount = Object.values(status?.services_status || {}).filter( s => s.toLowerCase() === 'running' || s.toLowerCase().includes('up') ).length const stoppedCount = Object.values(status?.services_status || {}).filter( s => s.toLowerCase() === 'exited' || s.toLowerCase().includes('exit') ).length return (

Nachtabschaltung

Automatisches Stoppen und Starten von Docker-Services nach Zeitplan.

{/* Status Messages */} {error && (
{error}
)} {successMessage && (
{successMessage}
)} {loading ? (
) : ( <> {/* Haupt-Steuerung */}
{/* Toggle */}

Nachtmodus: {editConfig?.enabled ? 'Aktiv' : 'Inaktiv'}

{editConfig?.enabled ? `Abschaltung um ${editConfig.shutdown_time}, Start um ${editConfig.startup_time}` : 'Zeitgesteuerte Abschaltung ist deaktiviert'}

{/* Manuelle Aktionen */}
{/* Status-Karten */}
{status?.current_time || '--:--'}
Aktuelle Zeit
{status?.next_action === 'shutdown' ? ( ) : ( )}
{status?.next_action_time || '--:--'}
{status?.next_action === 'shutdown' ? 'Naechste Abschaltung' : 'Naechster Start'}
{status?.time_until_next_action || '-'}
Countdown
{runningCount} / {stoppedCount}
Aktiv / Gestoppt
{/* Zeitkonfiguration */}

Zeitkonfiguration

{editMode ? (
) : ( )}
{editMode ? ( setEditConfig(prev => prev ? { ...prev, shutdown_time: e.target.value } : null)} className="w-full px-4 py-3 text-xl font-mono border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500" /> ) : (
{editConfig?.shutdown_time || '22:00'}
)}
{editMode ? ( setEditConfig(prev => prev ? { ...prev, startup_time: e.target.value } : null)} className="w-full px-4 py-3 text-xl font-mono border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-500" /> ) : (
{editConfig?.startup_time || '06:00'}
)}
{/* Letzte Aktion */} {status?.config.last_action && (
Letzte Aktion:{' '} {status.config.last_action === 'startup' ? 'Gestartet' : 'Abgeschaltet'} {status.config.last_action_time && ( am {new Date(status.config.last_action_time).toLocaleString('de-DE')} )}
)}
{/* Service-Liste */}

Services

Gruen = wird verwaltet, Grau = ausgeschlossen (laeuft immer)

{services?.all_services.map(service => { const serviceStatus = status?.services_status[service] || 'unknown' const isExcluded = services.excluded_services.includes(service) return (
{isExcluded ? ( ) : ( )} {service}
{serviceStatus}
) })} {/* Auch excluded Services anzeigen, die nicht in all_services sind */} {services?.excluded_services .filter(s => !services.all_services.includes(s)) .map(service => (
{service}
excluded
))}
{/* Info Box */}

Hinweise zur Nachtabschaltung

  • Der night-scheduler und nginx bleiben immer aktiv
  • Services werden mit docker compose stop angehalten (Daten bleiben erhalten)
  • Bei manuellem Start/Stop wird die letzte Aktion gespeichert
  • Der Scheduler prueft jede Minute, ob eine Aktion faellig ist
)}
) }