feat: ProxmoxPanel mit verschachtelter Navigation wie AgentPanel
Haupt-Tabs: Uebersicht / Virtualisierung / Speicher / System Sub-Tabs: VMs+Container | Storage+ZFS+Backups+PBS | Dienste+Updates+Aufgaben+Tunnel+Agent
This commit is contained in:
parent
81669c59a5
commit
67e15040fb
@ -492,7 +492,7 @@ func (d *Database) writeMetrics(tx *sql.Tx, agentID string, ts time.Time, data *
|
||||
}
|
||||
|
||||
func (d *Database) GetAgents() ([]models.Agent, error) {
|
||||
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, agent_version, platform, registered_at, last_heartbeat, customer_id, update_requested FROM agents ORDER BY name")
|
||||
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, agent_version, platform, registered_at, last_heartbeat, customer_id, update_requested, last_api_key FROM agents ORDER BY name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -504,7 +504,7 @@ func (d *Database) GetAgents() ([]models.Agent, error) {
|
||||
var lastHB sql.NullTime
|
||||
var custID sql.NullInt64
|
||||
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.AgentVersion, &a.Platform, &a.RegisteredAt, &lastHB, &custID, &a.UpdateRequested); err != nil {
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.AgentVersion, &a.Platform, &a.RegisteredAt, &lastHB, &custID, &a.UpdateRequested, &a.LastAPIKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastHB.Valid {
|
||||
|
||||
@ -11,32 +11,51 @@ import {
|
||||
MonitorSmartphone, Terminal, Cable, ExternalLink, Plus, Unplug, Search, Shield, Play, FileCode,
|
||||
} from 'lucide-react'
|
||||
|
||||
const buildTabs = (hasPBS) => [
|
||||
{ id: 'overview', label: 'Uebersicht' },
|
||||
{ id: 'vms', label: 'Virtuelle Maschinen' },
|
||||
{ id: 'containers', label: 'Container' },
|
||||
{ id: 'storage', label: 'Storage' },
|
||||
{ id: 'zfs', label: 'ZFS' },
|
||||
...(hasPBS ? [{ id: 'pbs', label: 'Backup Server' }] : []),
|
||||
{ id: 'tunnel', label: 'Tunnel' },
|
||||
{ id: 'services', label: 'Dienste' },
|
||||
{ id: 'updates', label: 'Updates' },
|
||||
{ id: 'tasks', label: 'Aufgaben' },
|
||||
{ id: 'backups', label: 'Backups' },
|
||||
{ id: 'agent', label: 'Agent' },
|
||||
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' },
|
||||
{ id: 'containers', label: 'Container' },
|
||||
],
|
||||
speicher: [
|
||||
{ id: 'storage', label: 'Storage' },
|
||||
{ id: 'zfs', label: 'ZFS' },
|
||||
{ id: 'backups', label: 'Backups' },
|
||||
...(hasPBS ? [{ id: 'pbs', label: 'Backup Server' }] : []),
|
||||
],
|
||||
system: [
|
||||
{ id: 'services', label: 'Dienste' },
|
||||
{ id: 'updates', label: 'Updates' },
|
||||
{ id: 'tasks', label: 'Aufgaben' },
|
||||
{ id: 'tunnel', label: 'Tunnel' },
|
||||
{ id: 'agent', label: 'Agent' },
|
||||
],
|
||||
})
|
||||
|
||||
export default function ProxmoxPanel({ agentId, customers, onClose, onReload }) {
|
||||
const [data, setData] = useState(null)
|
||||
const [tab, setTab] = useState('overview')
|
||||
const [mainTab, setMainTab] = useState('uebersicht')
|
||||
const [subTab, setSubTab] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
setTab('overview')
|
||||
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(() => {
|
||||
@ -52,12 +71,32 @@ export default function ProxmoxPanel({ agentId, customers, onClose, onReload })
|
||||
const proxmox = sys?.proxmox
|
||||
const pbs = sys?.pbs
|
||||
const cust = agent.customer_id ? customers.find((c) => c.id === agent.customer_id) : null
|
||||
const tabs = buildTabs(pbs?.available)
|
||||
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 <Panel onClose={onClose}><div className="p-6 text-red-400">Keine Proxmox-Daten verfuegbar</div></Panel>
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
if (!sys) return <div className="text-gray-500">Keine Systemdaten</div>
|
||||
if (mainTab === 'uebersicht') return <OverviewTab sys={sys} proxmox={proxmox} />
|
||||
const t = activeSubTab
|
||||
if (t === 'vms') return <VmsTab proxmox={proxmox} agentId={agentId} />
|
||||
if (t === 'containers') return <ContainersTab proxmox={proxmox} agentId={agentId} />
|
||||
if (t === 'storage') return <StorageTab proxmox={proxmox} />
|
||||
if (t === 'zfs') return <ZfsTab proxmox={proxmox} />
|
||||
if (t === 'backups') return <BackupsTab sys={sys} />
|
||||
if (t === 'pbs') return <PBSTab pbs={pbs} agentId={agentId} sys={sys} />
|
||||
if (t === 'services') return <ServicesTab sys={sys} />
|
||||
if (t === 'updates') return <UpdatesTab sys={sys} />
|
||||
if (t === 'tasks') return <LinuxTasksTab agentId={agentId} agentName={data?.agent?.name || ''} />
|
||||
if (t === 'tunnel') return <TunnelTab agentId={agentId} agent={data?.agent} webguiPort={8006} />
|
||||
if (t === 'agent') return <AgentTab agent={data?.agent} status={data?.status} />
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel onClose={onClose}>
|
||||
{/* Header */}
|
||||
@ -92,53 +131,49 @@ export default function ProxmoxPanel({ agentId, customers, onClose, onReload })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mt-4 overflow-x-auto text-xs">
|
||||
{tabs.map((t) => (
|
||||
{/* Haupt-Tabs */}
|
||||
<div className="flex gap-1 mt-4 text-xs">
|
||||
{mainCategories.map((cat) => (
|
||||
<button
|
||||
key={t.id}
|
||||
onClick={() => setTab(t.id)}
|
||||
key={cat.id}
|
||||
onClick={() => handleMainTab(cat.id, subs)}
|
||||
className={`px-3 py-1.5 rounded-t whitespace-nowrap transition-colors ${
|
||||
tab === t.id
|
||||
mainTab === cat.id
|
||||
? 'bg-gray-900 text-orange-400 border-t border-x border-gray-700'
|
||||
: 'text-gray-400 hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
{t.label}
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sub-Tabs */}
|
||||
{currentSubs.length > 0 && (
|
||||
<div className="px-6 py-2 bg-gray-900/60 border-b border-gray-800 flex items-center gap-1 text-xs">
|
||||
<span className="text-gray-600 mr-2">
|
||||
{mainCategories.find(c => c.id === mainTab)?.label}
|
||||
</span>
|
||||
{currentSubs.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => setSubTab(s.id)}
|
||||
className={`px-2.5 py-1 rounded whitespace-nowrap transition-colors ${
|
||||
activeSubTab === s.id
|
||||
? 'bg-gray-700 text-white'
|
||||
: 'text-gray-500 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-4">
|
||||
{!sys ? (
|
||||
<div className="text-gray-500">Keine Systemdaten</div>
|
||||
) : tab === 'overview' ? (
|
||||
<OverviewTab sys={sys} proxmox={proxmox} />
|
||||
) : tab === 'vms' ? (
|
||||
<VmsTab proxmox={proxmox} agentId={agentId} />
|
||||
) : tab === 'containers' ? (
|
||||
<ContainersTab proxmox={proxmox} agentId={agentId} />
|
||||
) : tab === 'storage' ? (
|
||||
<StorageTab proxmox={proxmox} />
|
||||
) : tab === 'zfs' ? (
|
||||
<ZfsTab proxmox={proxmox} />
|
||||
) : tab === 'tunnel' ? (
|
||||
<TunnelTab agentId={agentId} agent={data?.agent} webguiPort={8006} />
|
||||
) : tab === 'services' ? (
|
||||
<ServicesTab sys={sys} />
|
||||
) : tab === 'updates' ? (
|
||||
<UpdatesTab sys={sys} />
|
||||
) : tab === 'tasks' ? (
|
||||
<LinuxTasksTab agentId={agentId} agentName={data?.agent?.name || ''} />
|
||||
) : tab === 'pbs' ? (
|
||||
<PBSTab pbs={pbs} agentId={agentId} sys={sys} />
|
||||
) : tab === 'backups' ? (
|
||||
<BackupsTab sys={sys} />
|
||||
) : tab === 'agent' ? (
|
||||
<AgentTab agent={data?.agent} status={data?.status} />
|
||||
) : null}
|
||||
{renderContent()}
|
||||
</div>
|
||||
</Panel>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user