'use client' /** * Mac Mini Control Admin Page * * Headless Mac Mini Server Management * - Power Controls (Wake-on-LAN, Restart, Shutdown) * - Docker Container Management * - Ollama LLM Model Management * - System Status Monitoring */ import AdminLayout from '@/components/admin/AdminLayout' import { useEffect, useState, useCallback, useRef } from 'react' import type { MacMiniStatus, DownloadProgress } from './types' import { API_BASE } from './constants' import PowerControls from './_components/PowerControls' import InternetStatus from './_components/InternetStatus' import DockerSection from './_components/DockerSection' import OllamaSection from './_components/OllamaSection' export default function MacMiniControlPage() { const [status, setStatus] = useState(null) const [loading, setLoading] = useState(true) const [actionLoading, setActionLoading] = useState(null) const [error, setError] = useState(null) const [message, setMessage] = useState(null) const [downloadProgress, setDownloadProgress] = useState(null) const [modelInput, setModelInput] = useState('') const eventSourceRef = useRef(null) const fetchStatus = useCallback(async () => { setLoading(true) setError(null) try { const response = await fetch(`${API_BASE}/status`) const data = await response.json() if (!response.ok) throw new Error(data.detail || `HTTP ${response.status}`) setStatus(data) } catch (err) { setError(err instanceof Error ? err.message : 'Verbindungsfehler') setStatus({ online: false, ping: false, ssh: false, docker: false, ollama: false, internet: false, ip: '192.168.178.100', error: 'Verbindung fehlgeschlagen' }) } finally { setLoading(false) } }, []) useEffect(() => { fetchStatus() }, [fetchStatus]) useEffect(() => { const interval = setInterval(fetchStatus, 30000) return () => clearInterval(interval) }, [fetchStatus]) const performAction = async (action: string, endpoint: string, confirmMsg?: string) => { if (confirmMsg && !confirm(confirmMsg)) return setActionLoading(action) setError(null) setMessage(null) try { const response = await fetch(`${API_BASE}/${endpoint}`, { method: 'POST' }) const data = await response.json() if (!response.ok) throw new Error(data.detail || `${action} fehlgeschlagen`) return data } catch (err) { setError(err instanceof Error ? err.message : `Fehler bei ${action}`) return null } finally { setActionLoading(null) } } const wakeOnLan = async () => { const result = await performAction('wake', 'wake') if (result) { setMessage('Wake-on-LAN Paket gesendet') setTimeout(fetchStatus, 5000) setTimeout(fetchStatus, 15000) } } const restart = async () => { const result = await performAction('restart', 'restart', 'Mac Mini wirklich neu starten?') if (result) { setMessage('Neustart eingeleitet') setTimeout(fetchStatus, 30000) } } const shutdown = async () => { const result = await performAction('shutdown', 'shutdown', 'Mac Mini wirklich herunterfahren?') if (result) { setMessage('Shutdown eingeleitet') setTimeout(fetchStatus, 10000) } } const dockerUp = async () => { const result = await performAction('docker-up', 'docker/up') if (result) { setMessage('Docker Container werden gestartet...') setTimeout(fetchStatus, 5000) } } const dockerDown = async () => { const result = await performAction('docker-down', 'docker/down', 'Docker Container wirklich stoppen?') if (result) { setMessage('Docker Container werden gestoppt...') setTimeout(fetchStatus, 5000) } } const pullModel = async () => { if (!modelInput.trim()) return setActionLoading('pull') setError(null) setMessage(null) setDownloadProgress({ model: modelInput, status: 'starting', completed: 0, total: 0, percent: 0 }) try { if (eventSourceRef.current) eventSourceRef.current.close() const response = await fetch(`${API_BASE}/ollama/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: modelInput }) }) if (!response.ok) { const data = await response.json() throw new Error(data.detail || 'Model Pull fehlgeschlagen') } const reader = response.body?.getReader() const decoder = new TextDecoder() if (reader) { while (true) { const { done, value } = await reader.read() if (done) break const text = decoder.decode(value) const lines = text.split('\n').filter(line => line.trim()) for (const line of lines) { try { const data = JSON.parse(line) if (data.status === 'downloading' && data.total) { setDownloadProgress({ model: modelInput, status: data.status, completed: data.completed || 0, total: data.total, percent: Math.round((data.completed || 0) / data.total * 100) }) } else if (data.status === 'success') { setMessage(`Modell ${modelInput} erfolgreich heruntergeladen`) setDownloadProgress(null) setModelInput('') fetchStatus() } else if (data.error) { throw new Error(data.error) } } catch (e) { /* Skip parsing errors for incomplete chunks */ } } } } } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Model Download') setDownloadProgress(null) } finally { setActionLoading(null) } } return ( {/* Info */}

Mac Mini Headless Server

Der Mac Mini läuft ohne Monitor im LAN (192.168.178.100). Er hostet Docker-Container für das Backend, Ollama für lokale LLM-Verarbeitung und weitere Services. Wake-on-LAN ermöglicht das Remote-Einschalten.

) }