From 67651a5aeb2c7dc229f1eb32c3a2f2eb4819758b Mon Sep 17 00:00:00 2001 From: cynfo3000 Date: Fri, 6 Mar 2026 23:34:43 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20PBS=20Datastores=20=E2=80=94=20Backup-A?= =?UTF-8?q?nzahl,=20neues=20Layout=20mit=20GC/Verify/Prune=20Buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent-linux/collector/pbs.go | 20 +++- frontend/src/components/ProxmoxPanel.jsx | 138 ++++++++++++++++------- 2 files changed, 109 insertions(+), 49 deletions(-) diff --git a/agent-linux/collector/pbs.go b/agent-linux/collector/pbs.go index e21340f..5387169 100644 --- a/agent-linux/collector/pbs.go +++ b/agent-linux/collector/pbs.go @@ -16,12 +16,12 @@ type PBSInfo struct { } type PBSDatastore struct { - Name string `json:"name"` - Path string `json:"path,omitempty"` - Total int64 `json:"total"` - Used int64 `json:"used"` - Available int64 `json:"available"` - Namespaces int `json:"namespaces,omitempty"` + Name string `json:"name"` + Path string `json:"path,omitempty"` + Total int64 `json:"total"` + Used int64 `json:"used"` + Available int64 `json:"available"` + BackupCount int `json:"backup_count"` } type PBSTask struct { @@ -87,6 +87,14 @@ func CollectPBS() *PBSInfo { if avail, ok := d["avail"].(float64); ok { ds.Available = int64(avail) } + // Backup-Anzahl ermitteln + if snapOut, err := exec.Command("/usr/sbin/proxmox-backup-manager", "snapshot", "list", ds.Name, "--output-format", "json").Output(); err == nil { + var snaps []interface{} + if json.Unmarshal(snapOut, &snaps) == nil { + ds.BackupCount = len(snaps) + } + } + pbs.Datastores = append(pbs.Datastores, ds) } } diff --git a/frontend/src/components/ProxmoxPanel.jsx b/frontend/src/components/ProxmoxPanel.jsx index a7aec11..7ba4da8 100644 --- a/frontend/src/components/ProxmoxPanel.jsx +++ b/frontend/src/components/ProxmoxPanel.jsx @@ -127,7 +127,7 @@ export default function ProxmoxPanel({ agentId, customers, onClose, onReload }) ) : tab === 'updates' ? ( ) : tab === 'pbs' ? ( - + ) : tab === 'backups' ? ( ) : tab === 'agent' ? ( @@ -952,7 +952,11 @@ function formatUptime(seconds) { if (d > 0) return `${d}d ${h}h` return `${h}h ${m}m` } -function PBSTab({ pbs }) { + +function PBSTab({ pbs, agentId }) { + const [loading, setLoading] = React.useState({}) + const [feedback, setFeedback] = React.useState({}) + if (!pbs?.available) return
Keine PBS-Daten verfuegbar
const formatBytes = (b) => { @@ -979,59 +983,109 @@ function PBSTab({ pbs }) { return 'text-yellow-400' } + const pbsAction = async (datastore, action) => { + const key = `${datastore}-${action}` + const cmds = { + gc: `/usr/sbin/proxmox-backup-manager garbage-collection start ${datastore}`, + verify: `/usr/sbin/proxmox-backup-manager verify ${datastore}`, + prune: `/usr/sbin/proxmox-backup-manager prune-datastore ${datastore}`, + } + setLoading(l => ({ ...l, [key]: true })) + setFeedback(f => ({ ...f, [key]: null })) + try { + await api.execCommand(agentId, cmds[action], 60) + setFeedback(f => ({ ...f, [key]: 'ok' })) + } catch (e) { + setFeedback(f => ({ ...f, [key]: 'err' })) + } finally { + setLoading(l => ({ ...l, [key]: false })) + setTimeout(() => setFeedback(f => ({ ...f, [key]: null })), 4000) + } + } + return (
{/* Header */} -
-
-
Proxmox Backup Server
- {pbs.version &&
Version {pbs.version}
} -
+
+
Proxmox Backup Server
+ {pbs.version &&
Version {pbs.version}
}
{/* Datastores */} {pbs.datastores?.length > 0 && ( - <> -

Datastores

-
- {pbs.datastores.map((ds) => { - const usedPct = ds.total > 0 ? (ds.used / ds.total * 100) : 0 - const barColor = usedPct > 90 ? 'bg-red-500' : usedPct > 70 ? 'bg-yellow-500' : 'bg-green-500' - const gc = pbs.gc?.find(g => g.datastore === ds.name) +
+

Datastores

+ {pbs.datastores.map((ds) => { + const usedPct = ds.total > 0 ? (ds.used / ds.total * 100) : 0 + const barColor = usedPct > 90 ? 'bg-red-500' : usedPct > 70 ? 'bg-yellow-500' : 'bg-green-500' + const gc = pbs.gc?.find(g => g.datastore === ds.name) + const mkKey = (a) => `${ds.name}-${a}` + const ActionBtn = ({ action, label, cls }) => { + const k = mkKey(action) + const fb = feedback[k] return ( -
-
-
- {ds.name} - {ds.path && {ds.path}} + + ) + } + return ( +
+ {/* Titel + Backup-Count */} +
+
+
+ + +
- {usedPct.toFixed(1)}% + {ds.name}
-
-
-
-
- Belegt: {formatBytes(ds.used)} / {formatBytes(ds.total)} - Frei: {formatBytes(ds.available)} -
- {gc && ( -
- GC: {gc.status || '—'} - {gc.last_run > 0 && Letzter Lauf: {formatTs(gc.last_run)}} - {gc.duration > 0 && Dauer: {formatDuration(gc.duration)}} -
+ {ds.backup_count > 0 && ( + {ds.backup_count} Backups )}
- ) - })} -
- + + {/* Speicher-Balken */} +
+
+ {formatBytes(ds.used)} / {formatBytes(ds.total)} + {usedPct.toFixed(1)}% +
+
+
+
+
+ + {/* GC-Status */} + {gc && gc.status && ( +
+ GC: {gc.status} + {gc.last_run > 0 && Letzter Lauf: {formatTs(gc.last_run)}} + {gc.duration > 0 && ({formatDuration(gc.duration)})} +
+ )} + + {/* Aktions-Buttons */} +
+ + + +
+
+ ) + })} +
)} {/* Tasks */} {pbs.tasks?.length > 0 && ( - <> -

Letzte Tasks

+
+

Letzte Tasks

@@ -1039,24 +1093,22 @@ function PBSTab({ pbs }) { - {pbs.tasks.map((t, i) => ( - + - ))}
Typ Start DauerBenutzer Status
{t.type || '—'} {formatTs(t.start_time)} {formatDuration(t.duration)}{t.user || '—'} {t.status || '—'}
- +
)}
)