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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -504,7 +504,7 @@ func (d *Database) GetAgents() ([]models.Agent, error) {
|
|||||||
var lastHB sql.NullTime
|
var lastHB sql.NullTime
|
||||||
var custID sql.NullInt64
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if lastHB.Valid {
|
if lastHB.Valid {
|
||||||
|
|||||||
@ -11,32 +11,51 @@ import {
|
|||||||
MonitorSmartphone, Terminal, Cable, ExternalLink, Plus, Unplug, Search, Shield, Play, FileCode,
|
MonitorSmartphone, Terminal, Cable, ExternalLink, Plus, Unplug, Search, Shield, Play, FileCode,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
const buildTabs = (hasPBS) => [
|
const mainCategories = [
|
||||||
{ id: 'overview', label: 'Uebersicht' },
|
{ id: 'uebersicht', label: 'Uebersicht' },
|
||||||
{ id: 'vms', label: 'Virtuelle Maschinen' },
|
{ id: 'virtualisierung', label: 'Virtualisierung' },
|
||||||
{ id: 'containers', label: 'Container' },
|
{ id: 'speicher', label: 'Speicher' },
|
||||||
{ id: 'storage', label: 'Storage' },
|
{ id: 'system', label: 'System' },
|
||||||
{ 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 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 }) {
|
export default function ProxmoxPanel({ agentId, customers, onClose, onReload }) {
|
||||||
const [data, setData] = useState(null)
|
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)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setTab('overview')
|
setMainTab('uebersicht')
|
||||||
|
setSubTab(null)
|
||||||
api.getAgent(agentId).then(setData).finally(() => setLoading(false))
|
api.getAgent(agentId).then(setData).finally(() => setLoading(false))
|
||||||
}, [agentId])
|
}, [agentId])
|
||||||
|
|
||||||
|
const handleMainTab = (id, subTabs) => {
|
||||||
|
setMainTab(id)
|
||||||
|
setSubTab(subTabs?.[id]?.[0]?.id ?? null)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
const iv = setInterval(() => {
|
const iv = setInterval(() => {
|
||||||
@ -52,12 +71,32 @@ export default function ProxmoxPanel({ agentId, customers, onClose, onReload })
|
|||||||
const proxmox = sys?.proxmox
|
const proxmox = sys?.proxmox
|
||||||
const pbs = sys?.pbs
|
const pbs = sys?.pbs
|
||||||
const cust = agent.customer_id ? customers.find((c) => c.id === agent.customer_id) : null
|
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) {
|
if (!proxmox?.available) {
|
||||||
return <Panel onClose={onClose}><div className="p-6 text-red-400">Keine Proxmox-Daten verfuegbar</div></Panel>
|
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 (
|
return (
|
||||||
<Panel onClose={onClose}>
|
<Panel onClose={onClose}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -92,53 +131,49 @@ export default function ProxmoxPanel({ agentId, customers, onClose, onReload })
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Haupt-Tabs */}
|
||||||
<div className="flex gap-1 mt-4 overflow-x-auto text-xs">
|
<div className="flex gap-1 mt-4 text-xs">
|
||||||
{tabs.map((t) => (
|
{mainCategories.map((cat) => (
|
||||||
<button
|
<button
|
||||||
key={t.id}
|
key={cat.id}
|
||||||
onClick={() => setTab(t.id)}
|
onClick={() => handleMainTab(cat.id, subs)}
|
||||||
className={`px-3 py-1.5 rounded-t whitespace-nowrap transition-colors ${
|
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'
|
? 'bg-gray-900 text-orange-400 border-t border-x border-gray-700'
|
||||||
: 'text-gray-400 hover:text-gray-200'
|
: 'text-gray-400 hover:text-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t.label}
|
{cat.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto p-6 space-y-4">
|
<div className="flex-1 overflow-y-auto p-6 space-y-4">
|
||||||
{!sys ? (
|
{renderContent()}
|
||||||
<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}
|
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user