import { useEffect, useState } from 'react' import api from '../api/client' import { useSettingsStore } from '../stores/settings' import StatusBadge from './StatusBadge' import TerminalModal from './TerminalModal' import ScriptModal from './ScriptModal' import { copyToClipboard } from '../utils/clipboard' import { X, Cpu, MemoryStick, HardDrive, Clock, Network, Server, Monitor, Container, Database, FolderTree, Settings, Download, Trash2, Copy, Check, MonitorSmartphone, Terminal, Cable, ExternalLink, Plus, Unplug, Search, Shield, Play, FileCode, LayoutGrid, Archive, GitBranch, Wrench, RefreshCw, CalendarClock, Bot, } from 'lucide-react' const mainCategories = [ { id: 'uebersicht', label: 'Uebersicht' }, { id: 'virtualisierung', label: 'Virtualisierung' }, { id: 'speicher', label: 'Speicher' }, { id: 'system', label: 'System' }, ] const buildSubTabs = (hasPBS) => ({ virtualisierung: [ { id: 'vms', label: 'VMs', icon: Monitor }, { id: 'containers', label: 'Container', icon: Container }, ], speicher: [ { id: 'storage', label: 'Storage', icon: HardDrive }, { id: 'zfs', label: 'ZFS', icon: Database }, { id: 'backups', label: 'Backups', icon: Archive }, ...(hasPBS ? [{ id: 'pbs', label: 'Backup Server', icon: Server }] : []), ], system: [ { id: 'services', label: 'Dienste', icon: Settings }, { id: 'updates', label: 'Updates', icon: RefreshCw }, { id: 'tasks', label: 'Aufgaben', icon: CalendarClock }, { id: 'tunnel', label: 'Tunnel', icon: Cable }, { id: 'agent', label: 'Agent', icon: Bot }, ], }) export default function ProxmoxPanel({ agentId, customers, onClose, onReload }) { const [data, setData] = useState(null) const [mainTab, setMainTab] = useState('uebersicht') const [subTab, setSubTab] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { setLoading(true) setMainTab('uebersicht') setSubTab(null) api.getAgent(agentId).then(setData).finally(() => setLoading(false)) }, [agentId]) const handleMainTab = (id, subTabs) => { setMainTab(id) setSubTab(subTabs?.[id]?.[0]?.id ?? null) } useEffect(() => { if (!data) return const iv = setInterval(() => { api.getAgent(agentId).then(setData) }, 30000) return () => clearInterval(iv) }, [agentId, data]) if (loading) return
Laden...
if (!data) return
Nicht gefunden
const { agent, system_data: sys, status } = data const proxmox = sys?.proxmox const pbs = sys?.pbs const cust = agent.customer_id ? customers.find((c) => c.id === agent.customer_id) : null const subs = buildSubTabs(pbs?.available) const currentSubs = mainTab !== 'uebersicht' ? (subs[mainTab] || []) : [] const activeSubTab = currentSubs.find(s => s.id === subTab)?.id ?? currentSubs[0]?.id if (!proxmox?.available) { return
Keine Proxmox-Daten verfuegbar
} const renderContent = () => { if (!sys) return
Keine Systemdaten
if (mainTab === 'uebersicht') return const t = activeSubTab if (t === 'vms') return if (t === 'containers') return if (t === 'storage') return if (t === 'zfs') return if (t === 'backups') return if (t === 'pbs') return if (t === 'services') return if (t === 'updates') return if (t === 'tasks') return if (t === 'tunnel') return if (t === 'agent') return return null } return ( {/* Header */}

{agent.name}

{agent.hostname} · IP: {agent.ip} · PVE {proxmox.version || '—'}
{/* Haupt-Tabs */}
{mainCategories.map((cat) => ( ))}
{/* Content + optionale Sidebar */}
{/* Sub-Navigation links — nur wenn nicht Uebersicht */} {mainTab !== 'uebersicht' && currentSubs.length > 0 && (
{mainCategories.find(c => c.id === mainTab)?.label}
{currentSubs.map((item) => { const Icon = item.icon const isActive = activeSubTab === item.id return ( ) })}
)} {/* Hauptinhalt */}
{renderContent()}
) } function CustomerAssign({ agent, customers, current, onReload }) { const [editing, setEditing] = useState(false) const [saving, setSaving] = useState(false) const handleChange = async (e) => { const val = e.target.value setSaving(true) try { if (val === '') { await api.unassignCustomer(agent.id) } else { await api.assignCustomer(agent.id, parseInt(val)) } if (onReload) onReload() } catch (err) { console.error(err) } finally { setSaving(false) setEditing(false) } } return (
{!editing ? ( ) : ( )}
) } function Panel({ onClose, children }) { return ( <>
{children}
) } function MetricCard({ icon: Icon, label, value, sub, bar, barColor }) { return (
{Icon && } {label}
{value}
{sub &&
{sub}
} {bar !== undefined && bar !== null && (
)}
) } function StatusCard({ icon: Icon, value, label }) { return (
{Icon && }
{value}
{label}
) } function LinuxTasksTab({ agentId, agentName }) { const [tasks, setTasks] = useState([]) const [total, setTotal] = useState(0) const [loading, setLoading] = useState(true) const [showDialog, setShowDialog] = useState(false) const [editTask, setEditTask] = useState(null) const [creating, setCreating] = useState(false) const [error, setError] = useState('') const defaultForm = { name: '', action: 'update', schedule_type: 'monthly', schedule_time: '02:00', scheduled_at: '', schedule_weekday: 1, schedule_monthday: 1, reboot: true, health_check: true, health_check_timeout: 600, security_only: false, webhook_url: '', active: true, script: '', script_timeout: 300, } const [form, setForm] = useState(defaultForm) const templates = [ { label: 'Wöchentlich alle Updates', form: { name: 'Wöchentliches Debian-Update', action: 'update', schedule_type: 'weekly', schedule_time: '02:00', schedule_weekday: 0, reboot: true, health_check: true, health_check_timeout: 600, security_only: false, active: true } }, { label: 'Monatlich alle Updates', form: { name: 'Monatliches Debian-Update', action: 'update', schedule_type: 'monthly', schedule_time: '02:00', schedule_monthday: 1, reboot: true, health_check: true, health_check_timeout: 600, security_only: false, active: true } }, { label: 'Wöchentlich nur Security', form: { name: 'Wöchentliche Security-Updates', action: 'update', schedule_type: 'weekly', schedule_time: '03:00', schedule_weekday: 3, reboot: false, health_check: true, health_check_timeout: 300, security_only: true, active: true } }, { label: 'Täglich Security (kein Reboot)', form: { name: 'Tägliche Security-Updates', action: 'update', schedule_type: 'daily', schedule_time: '04:00', reboot: false, health_check: false, security_only: true, active: true } }, { label: 'Update + Webhook', form: { name: 'Monatliches Update + Servicebericht', action: 'update', schedule_type: 'monthly', schedule_time: '02:00', schedule_monthday: 1, reboot: true, health_check: true, health_check_timeout: 600, security_only: false, webhook_url: '', active: true } }, { label: 'ZFS Scrub (monatlich)', form: { name: 'Monatlicher ZFS Pool Scrub', action: 'zpoolscrub', schedule_type: 'monthly', schedule_time: '01:37', schedule_monthday: 20, reboot: false, health_check: false, active: true } }, { label: 'ZFS Scrub (wöchentlich)', form: { name: 'Wöchentlicher ZFS Pool Scrub', action: 'zpoolscrub', schedule_type: 'weekly', schedule_time: '01:00', schedule_weekday: 0, reboot: false, health_check: false, active: true } }, { label: 'Custom Script', form: { name: 'Benutzerdefiniertes Script', action: 'script', schedule_type: 'monthly', schedule_time: '03:00', schedule_monthday: 1, reboot: false, health_check: false, script: '#!/bin/bash\n\n', script_timeout: 300, active: true } }, ] const loadTasks = () => { api.getTasks({ agent_id: agentId, limit: 50 }) .then(r => { setTasks(r.data || []); setTotal(r.total || 0) }) .catch(() => setTasks([])) .finally(() => setLoading(false)) } useEffect(() => { loadTasks() }, [agentId]) useEffect(() => { const iv = setInterval(loadTasks, 15000); return () => clearInterval(iv) }, [agentId]) const handleCreate = async () => { setCreating(true); setError('') try { const data = { agent_id: agentId, name: form.name, action: form.action, schedule_type: form.schedule_type, schedule_time: form.schedule_time, active: form.active, reboot: form.reboot, health_check: form.health_check, health_check_timeout: parseInt(form.health_check_timeout) || 600, security_only: form.security_only || false, script: form.script || '', script_timeout: parseInt(form.script_timeout) || 300, } if (form.schedule_type === 'once' && form.scheduled_at) data.scheduled_at = new Date(form.scheduled_at).toISOString() if (form.schedule_type === 'weekly') data.schedule_weekday = parseInt(form.schedule_weekday) if (form.schedule_type === 'monthly') data.schedule_monthday = parseInt(form.schedule_monthday) if (form.webhook_url) data.webhook_url = form.webhook_url await api.createTask(data) setShowDialog(false); setForm(defaultForm); loadTasks() } catch (e) { setError(e.message) } finally { setCreating(false) } } const handleSave = async () => { setCreating(true); setError('') try { const data = { name: form.name, schedule_type: form.schedule_type, schedule_time: form.schedule_time, active: form.active, reboot: form.reboot, health_check: form.health_check, health_check_timeout: parseInt(form.health_check_timeout) || 600, security_only: form.security_only || false, webhook_url: form.webhook_url, script: form.script || '', script_timeout: parseInt(form.script_timeout) || 300, } if (form.schedule_type === 'weekly') data.schedule_weekday = parseInt(form.schedule_weekday) if (form.schedule_type === 'monthly') data.schedule_monthday = parseInt(form.schedule_monthday) await api.updateTask(editTask.id, data) setShowDialog(false); setEditTask(null); setForm(defaultForm); loadTasks() } catch (e) { setError(e.message) } finally { setCreating(false) } } const weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'] const fmtInterval = (t) => { if (t.schedule_type === 'daily') return `Täglich ${t.schedule_time}` if (t.schedule_type === 'weekly') return `Wöchentlich ${weekdays[t.schedule_weekday || 0]} ${t.schedule_time}` if (t.schedule_type === 'monthly') return `Monatlich ${t.schedule_monthday || 1}. ${t.schedule_time}` return 'Einmalig' } const fmtDate = (d) => d ? new Date(d).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' }) : '—' const inputCls = 'w-full bg-gray-700 border border-gray-600 rounded px-3 py-2 text-sm text-white focus:outline-none focus:border-orange-500' const labelCls = 'block text-xs text-gray-400 mb-1' const statusColor = (s) => { if (s === 'completed') return 'text-emerald-400' if (s === 'failed' || s === 'error') return 'text-red-400' if (s === 'running') return 'text-blue-400' if (s === 'completed_with_warning') return 'text-yellow-400' return 'text-gray-400' } return (
{total} Job(s)
{/* Modal */} {showDialog && (
setShowDialog(false)}>
e.stopPropagation()}>

{editTask ? `Job bearbeiten` : `Neuer Job — ${agentName}`}

{/* Vorlagen */} {!editTask && (

Vorlage wählen

{templates.map((tpl, i) => ( ))}
)}
setForm({...form, name: e.target.value})} placeholder="z.B. Monatliches Update" className={inputCls} />
setForm({...form, schedule_time: e.target.value})} className={inputCls} />
{form.schedule_type === 'weekly' && (
)} {form.schedule_type === 'monthly' && (
setForm({...form, schedule_monthday: e.target.value})} className={inputCls} />
)} {/* Aktion wählen */}
{/* Script-Eingabe (nur bei action=script) */} {form.action === 'script' && (