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 }) {
| Typ |
Start |
Dauer |
- Benutzer |
Status |
{pbs.tasks.map((t, i) => (
-
+
| {t.type || '—'} |
{formatTs(t.start_time)} |
{formatDuration(t.duration)} |
- {t.user || '—'} |
{t.status || '—'} |
))}
- >
+
)}
)