diff --git a/frontend-modern/src/components/Backups/UnifiedBackups.tsx b/frontend-modern/src/components/Backups/UnifiedBackups.tsx index 0f203422a..a363f6a04 100644 --- a/frontend-modern/src/components/Backups/UnifiedBackups.tsx +++ b/frontend-modern/src/components/Backups/UnifiedBackups.tsx @@ -26,6 +26,8 @@ interface UnifiedBackup { namespace: string | null; verified: boolean | null; protected: boolean; + encrypted?: boolean; + owner?: string; } // Types for PBS backups - temporarily disabled to avoid unused warnings @@ -186,6 +188,13 @@ const UnifiedBackups: Component = () => { console.log(`PBS backup: vmid=${backup.vmid}, time=${backupTimeSeconds}, key=${backupKey}, verified=${backup.verified}`); } + // Check if any files have encryption + const isEncrypted = backup.files && Array.isArray(backup.files) && + backup.files.some((file: any) => { + if (typeof file === 'string') return false; + return file.crypt || file.encrypted || (file.filename && file.filename.includes('.enc')); + }); + unified.push({ backupType: 'remote', vmid: parseInt(backup.vmid) || 0, @@ -201,7 +210,9 @@ const UnifiedBackups: Component = () => { datastore: backup.datastore || null, namespace: backup.namespace || 'root', verified: backup.verified || false, - protected: backup.protected || false + protected: backup.protected || false, + encrypted: isEncrypted, + owner: backup.owner }); }); @@ -254,7 +265,8 @@ const UnifiedBackups: Component = () => { datastore: backup.isPBS ? backup.storage : null, namespace: backup.isPBS ? 'root' : null, verified: backup.verified || false, - protected: backup.protected || false + protected: backup.protected || false, + encrypted: backup.encryption ? true : false // Check encryption field from Proxmox API }); }); @@ -667,10 +679,12 @@ const UnifiedBackups: Component = () => { // Calculate average deduplication factor across all datastores const avgFactor = dedupFactors.reduce((sum, f) => sum + f, 0) / dedupFactors.length; - // Format as ratio - return avgFactor.toFixed(1) + ':1'; + // Format as multiplication factor + return avgFactor.toFixed(1) + 'x'; }); + + // Calculate backup frequency data for chart const chartData = createMemo(() => { const days = chartTimeRange(); @@ -1580,6 +1594,15 @@ const UnifiedBackups: Component = () => { > Node {sortKey() === 'node' && (sortDirection() === 'asc' ? '▲' : '▼')} + + handleSort('owner')} + style="width: 80px;" + > + Owner {sortKey() === 'owner' && (sortDirection() === 'asc' ? '▲' : '▼')} + + handleSort('backupTime')} @@ -1636,6 +1659,7 @@ const UnifiedBackups: Component = () => { { let cols = 6; // Base columns: Type, VMID, Node, Time, Backup, Details + if (backupTypeFilter() === 'all' || backupTypeFilter() === 'remote') cols++; // Add Owner column if (backupTypeFilter() !== 'snapshot') cols++; // Add Size column if (backupTypeFilter() === 'all' || backupTypeFilter() === 'remote') cols++; // Add Verified column if (backupTypeFilter() !== 'snapshot') cols++; // Add Location column @@ -1645,10 +1669,10 @@ const UnifiedBackups: Component = () => { {(item) => ( - + {item.name || '-'} - + { {item.type} - {item.vmid} - + {item.vmid} + {item.node} - + + + {item.owner ? item.owner.split('@')[0] : '-'} + + + {formatTime(item.backupTime * 1000)} - + {item.size ? formatBytes(item.size) : '-'} - - - {item.backupType === 'snapshot' ? 'Snapshot' : item.backupType === 'local' ? 'PVE' : 'PBS'} - + +
+ + {item.backupType === 'snapshot' ? 'Snapshot' : item.backupType === 'local' ? 'PVE' : 'PBS'} + + + + + + + + + + + + + + + +
- + {item.backupType === 'remote' ? ( item.verified ? ( @@ -1694,7 +1739,7 @@ const UnifiedBackups: Component = () => { - + {item.storage || (item.datastore && ( item.namespace && item.namespace !== 'root' ? `${item.datastore}/${item.namespace}` @@ -1703,7 +1748,7 @@ const UnifiedBackups: Component = () => { { const details = []; diff --git a/frontend-modern/src/types/api.ts b/frontend-modern/src/types/api.ts index ba118a384..6b3e1f08b 100644 --- a/frontend-modern/src/types/api.ts +++ b/frontend-modern/src/types/api.ts @@ -155,6 +155,7 @@ export interface PBSBackup { verified: boolean; comment: string; files: string[]; + owner?: string; } export interface PBSBackupJob { @@ -262,6 +263,7 @@ export interface StorageBackup { isPBS: boolean; verified: boolean; verification?: string; + encryption?: string; } export interface PVEBackups { diff --git a/frontend-modern/src/types/backups.ts b/frontend-modern/src/types/backups.ts index 3d9742377..88bff334d 100644 --- a/frontend-modern/src/types/backups.ts +++ b/frontend-modern/src/types/backups.ts @@ -21,6 +21,7 @@ export interface UnifiedBackup { // Common flags protected: boolean; + encrypted?: boolean; // UI specific instance?: string; diff --git a/internal/models/models.go b/internal/models/models.go index 90fb36ad6..220b22d1d 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -194,6 +194,7 @@ type PBSBackup struct { Verified bool `json:"verified"` Comment string `json:"comment,omitempty"` Files []string `json:"files,omitempty"` + Owner string `json:"owner,omitempty"` // User who created the backup } // PBSBackupJob represents a PBS backup job diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index eb6bdf6f8..e215e6c68 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -2668,6 +2668,7 @@ func (m *Monitor) pollPBSBackups(ctx context.Context, instanceName string, clien Verified: verified, Comment: snapshot.Comment, Files: fileNames, + Owner: snapshot.Owner, } allBackups = append(allBackups, backup)