diff --git a/admin-v2/app/(admin)/dashboard/page.tsx b/admin-v2/app/(admin)/dashboard/page.tsx
index 188f2e8..dbd3b99 100644
--- a/admin-v2/app/(admin)/dashboard/page.tsx
+++ b/admin-v2/app/(admin)/dashboard/page.tsx
@@ -6,6 +6,7 @@ import { getStoredRole, isCategoryVisibleForRole, RoleId } from '@/lib/roles'
import { CategoryCard } from '@/components/common/ModuleCard'
import { InfoNote } from '@/components/common/InfoBox'
import { ServiceStatus } from '@/components/common/ServiceStatus'
+import { NightModeWidget } from '@/components/dashboard/NightModeWidget'
import Link from 'next/link'
interface Stats {
@@ -111,7 +112,18 @@ export default function DashboardPage() {
))}
+ {/* Infrastructure & System Status */}
+
Infrastruktur
+
+ {/* Night Mode Widget */}
+
+
+ {/* System Status */}
+
+
+
{/* Recent Activity */}
+ Aktivitaet
{/* Recent DSR */}
@@ -127,9 +139,6 @@ export default function DashboardPage() {
-
- {/* System Status */}
-
{/* Info Box */}
diff --git a/admin-v2/components/dashboard/NightModeWidget.tsx b/admin-v2/components/dashboard/NightModeWidget.tsx
new file mode 100644
index 0000000..6de4459
--- /dev/null
+++ b/admin-v2/components/dashboard/NightModeWidget.tsx
@@ -0,0 +1,231 @@
+'use client'
+
+import { useEffect, useState, useCallback } from 'react'
+import Link from 'next/link'
+
+interface NightModeConfig {
+ enabled: boolean
+ shutdown_time: string
+ startup_time: string
+ last_action: string | null
+ last_action_time: string | null
+}
+
+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
+}
+
+export function NightModeWidget() {
+ const [status, setStatus] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [actionLoading, setActionLoading] = useState(null)
+ const [error, setError] = useState(null)
+
+ const fetchData = useCallback(async () => {
+ try {
+ const response = await fetch('/api/admin/night-mode')
+ if (response.ok) {
+ setStatus(await response.json())
+ setError(null)
+ } else {
+ setError('Nicht erreichbar')
+ }
+ } catch {
+ setError('Verbindung fehlgeschlagen')
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ fetchData()
+ const interval = setInterval(fetchData, 30000)
+ return () => clearInterval(interval)
+ }, [fetchData])
+
+ const toggleEnabled = async () => {
+ if (!status) return
+ setActionLoading('toggle')
+
+ try {
+ const response = await fetch('/api/admin/night-mode', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ...status.config, enabled: !status.config.enabled }),
+ })
+ if (response.ok) {
+ fetchData()
+ }
+ } catch {
+ // ignore
+ } finally {
+ setActionLoading(null)
+ }
+ }
+
+ const executeAction = async (action: 'start' | 'stop') => {
+ setActionLoading(action)
+ try {
+ const response = await fetch('/api/admin/night-mode/execute', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ action }),
+ })
+ if (response.ok) {
+ fetchData()
+ }
+ } catch {
+ // ignore
+ } finally {
+ setActionLoading(null)
+ }
+ }
+
+ const runningCount = Object.values(status?.services_status || {}).filter(
+ s => s.toLowerCase() === 'running' || s.toLowerCase().includes('up')
+ ).length
+ const totalCount = Object.keys(status?.services_status || {}).length
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ if (error) {
+ return (
+
+
+
+
+
+
Nachtabschaltung
+
{error}
+
+
+
+ Details
+
+
+
+ )
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
Nachtabschaltung
+
+ {status?.config.enabled
+ ? `${status.config.shutdown_time} - ${status.config.startup_time}`
+ : 'Deaktiviert'}
+
+
+
+
+ Einstellungen
+
+
+
+ {/* Content */}
+
+ {/* Status Row */}
+
+
+ {/* Toggle */}
+
+
+ {/* Countdown */}
+ {status?.config.enabled && status.time_until_next_action && (
+
+
+ {status.next_action === 'shutdown' ? '⏸' : '▶'} {status.time_until_next_action}
+
+
+ )}
+
+
+ {/* Service Count */}
+
+ {runningCount}
+ /{totalCount}
+ aktiv
+
+
+
+ {/* Action Buttons */}
+
+
+
+
+
+
+ )
+}