@@ -425,11 +425,25 @@ PulseApp.ui.backups = (() => {
425
425
function _determineGuestBackupStatus ( guest , guestSnapshots , guestTasks , dayBoundaries , threeDaysAgo , sevenDaysAgo ) {
426
426
const guestId = String ( guest . vmid ) ;
427
427
428
- // Get guest snapshots from pveBackups
428
+ // Get guest snapshots from pveBackups - use node-aware filtering
429
429
const pveBackups = PulseApp . state . get ( 'pveBackups' ) || { } ;
430
430
const allSnapshots = pveBackups . guestSnapshots || [ ] ;
431
431
const guestSnapshotCount = allSnapshots
432
- . filter ( snap => parseInt ( snap . vmid , 10 ) === parseInt ( guest . vmid , 10 ) )
432
+ . filter ( snap => {
433
+ // Match vmid
434
+ if ( parseInt ( snap . vmid , 10 ) !== parseInt ( guest . vmid , 10 ) ) return false ;
435
+
436
+ // For VM/CT snapshots, match by node/endpoint if available
437
+ if ( guest . node && snap . node ) {
438
+ return snap . node === guest . node ;
439
+ }
440
+ if ( guest . endpointId && snap . endpointId ) {
441
+ return snap . endpointId === guest . endpointId ;
442
+ }
443
+
444
+ // Fallback: include if no node info available
445
+ return true ;
446
+ } )
433
447
. length ;
434
448
435
449
// Use pre-filtered data instead of filtering large arrays
@@ -558,10 +572,22 @@ PulseApp.ui.backups = (() => {
558
572
// Check for VM/CT snapshots on this day (if we have that data)
559
573
const pveBackups = PulseApp . state . get ( 'pveBackups' ) || { } ;
560
574
const allSnapshots = pveBackups . guestSnapshots || [ ] ;
561
- const guestDaySnapshots = allSnapshots . filter ( snap =>
562
- parseInt ( snap . vmid , 10 ) === parseInt ( guestId , 10 ) &&
563
- snap . snaptime >= day . start && snap . snaptime < day . end
564
- ) ;
575
+ const guestDaySnapshots = allSnapshots . filter ( snap => {
576
+ // Match vmid and time
577
+ if ( parseInt ( snap . vmid , 10 ) !== parseInt ( guestId , 10 ) ) return false ;
578
+ if ( ! ( snap . snaptime >= day . start && snap . snaptime < day . end ) ) return false ;
579
+
580
+ // Match by node/endpoint if available
581
+ if ( guest . node && snap . node ) {
582
+ return snap . node === guest . node ;
583
+ }
584
+ if ( guest . endpointId && snap . endpointId ) {
585
+ return snap . endpointId === guest . endpointId ;
586
+ }
587
+
588
+ // Fallback: include if no node info available
589
+ return true ;
590
+ } ) ;
565
591
566
592
if ( guestDaySnapshots . length > 0 ) {
567
593
backupTypes . add ( 'snapshot' ) ;
@@ -729,7 +755,13 @@ PulseApp.ui.backups = (() => {
729
755
730
756
// Calendar date filter - only show guests that had backups on the selected date
731
757
if ( calendarDateFilter && calendarDateFilter . guestIds && calendarDateFilter . guestIds . length > 0 ) {
732
- const guestIdMatch = calendarDateFilter . guestIds . includes ( item . guestId . toString ( ) ) ;
758
+ // Create unique key for this guest item
759
+ const nodeIdentifier = item . node || item . endpointId || '' ;
760
+ const itemUniqueKey = nodeIdentifier ? `${ item . guestId } -${ nodeIdentifier } ` : item . guestId . toString ( ) ;
761
+
762
+ // Check if this guest's unique key or simple vmid is in the calendar filter
763
+ const guestIdMatch = calendarDateFilter . guestIds . includes ( itemUniqueKey ) ||
764
+ calendarDateFilter . guestIds . includes ( item . guestId . toString ( ) ) ;
733
765
if ( ! guestIdMatch ) return false ;
734
766
}
735
767
@@ -1043,7 +1075,17 @@ PulseApp.ui.backups = (() => {
1043
1075
if ( pveBackups ?. storageBackups && Array . isArray ( pveBackups . storageBackups ) ) {
1044
1076
pveBackups . storageBackups . forEach ( backup => {
1045
1077
pveStorageBackups . push ( {
1046
- ...backup ,
1078
+ 'backup-time' : backup . ctime ,
1079
+ backupType : _extractBackupTypeFromVolid ( backup . volid , backup . vmid ) ,
1080
+ backupVMID : backup . vmid ,
1081
+ vmid : backup . vmid , // Ensure vmid is preserved for filtering
1082
+ size : backup . size ,
1083
+ protected : backup . protected ,
1084
+ storage : backup . storage ,
1085
+ volid : backup . volid ,
1086
+ format : backup . format ,
1087
+ node : backup . node ,
1088
+ endpointId : backup . endpointId ,
1047
1089
source : 'pve'
1048
1090
} ) ;
1049
1091
} ) ;
@@ -1084,8 +1126,12 @@ PulseApp.ui.backups = (() => {
1084
1126
backupTasks : [ ...pbsBackupTasks , ...pveBackupTasks ]
1085
1127
} ;
1086
1128
1087
- // Create calendar respecting current table filters
1088
- const filteredGuestIds = filteredBackupStatus . map ( guest => guest . guestId . toString ( ) ) ;
1129
+ // Create calendar respecting current table filters - use unique guest identifiers
1130
+ const filteredGuestIds = filteredBackupStatus . map ( guest => {
1131
+ // Create unique identifier including node/endpoint to handle guests with same vmid on different nodes
1132
+ const nodeIdentifier = guest . node || guest . endpointId || '' ;
1133
+ return nodeIdentifier ? `${ guest . guestId } -${ nodeIdentifier } ` : guest . guestId . toString ( ) ;
1134
+ } ) ;
1089
1135
// Get detail card for callback
1090
1136
const detailCardContainer = document . getElementById ( 'backup-detail-card' ) ;
1091
1137
let onDateSelect = null ;
@@ -1146,7 +1192,27 @@ PulseApp.ui.backups = (() => {
1146
1192
const itemDateKey = utcDate . toISOString ( ) . split ( 'T' ) [ 0 ] ;
1147
1193
1148
1194
const vmid = item . vmid || item [ 'backup-id' ] || item . backupVMID ;
1149
- return vmid == guestId && itemDateKey === dateKey ;
1195
+ if ( vmid != guestId || itemDateKey !== dateKey ) return false ;
1196
+
1197
+ // For PBS backups (centralized), don't filter by node
1198
+ if ( source === 'pbsSnapshots' ) return true ;
1199
+
1200
+ // For PVE backups and snapshots (node-specific), match node/endpoint
1201
+ const itemNode = item . node ;
1202
+ const itemEndpoint = item . endpointId ;
1203
+
1204
+ // Match by node if available
1205
+ if ( guest . node && itemNode ) {
1206
+ return itemNode === guest . node ;
1207
+ }
1208
+
1209
+ // Match by endpointId if available
1210
+ if ( guest . endpointId && itemEndpoint ) {
1211
+ return itemEndpoint === guest . endpointId ;
1212
+ }
1213
+
1214
+ // If no node/endpoint info available, include it (fallback)
1215
+ return true ;
1150
1216
} ) ;
1151
1217
1152
1218
if ( dayBackups . length > 0 ) {
@@ -1162,13 +1228,37 @@ PulseApp.ui.backups = (() => {
1162
1228
let hasFailures = false ;
1163
1229
if ( backupData . backupTasks ) {
1164
1230
const dayTasks = backupData . backupTasks . filter ( task => {
1165
- if ( ! task . starttime || task . vmid != guestId ) return false ;
1231
+ if ( ! task . starttime ) return false ;
1232
+
1233
+ // Match vmid
1234
+ const taskVmid = task . vmid || task . guestId ;
1235
+ if ( taskVmid != guestId ) return false ;
1166
1236
1167
1237
const date = new Date ( task . starttime * 1000 ) ;
1168
1238
const utcDate = new Date ( Date . UTC ( date . getUTCFullYear ( ) , date . getUTCMonth ( ) , date . getUTCDate ( ) ) ) ;
1169
1239
const taskDateKey = utcDate . toISOString ( ) . split ( 'T' ) [ 0 ] ;
1170
1240
1171
- return taskDateKey === dateKey && task . status !== 'OK' ;
1241
+ if ( taskDateKey !== dateKey || task . status === 'OK' ) return false ;
1242
+
1243
+ // For PBS tasks (centralized), don't filter by node
1244
+ if ( task . source === 'pbs' ) return true ;
1245
+
1246
+ // For PVE tasks (node-specific), match node/endpoint
1247
+ const taskNode = task . node ;
1248
+ const taskEndpoint = task . endpointId ;
1249
+
1250
+ // Match by node if available
1251
+ if ( guest . node && taskNode ) {
1252
+ return taskNode === guest . node ;
1253
+ }
1254
+
1255
+ // Match by endpointId if available
1256
+ if ( guest . endpointId && taskEndpoint ) {
1257
+ return taskEndpoint === guest . endpointId ;
1258
+ }
1259
+
1260
+ // If no node/endpoint info available, include it (fallback)
1261
+ return true ;
1172
1262
} ) ;
1173
1263
1174
1264
hasFailures = dayTasks . length > 0 ;
@@ -1431,8 +1521,12 @@ PulseApp.ui.backups = (() => {
1431
1521
1432
1522
// Create and display calendar heatmap with detail card
1433
1523
if ( calendarContainer && PulseApp . ui . calendarHeatmap && PulseApp . ui . backupDetailCard ) {
1434
- // Get filtered guest IDs for calendar filtering
1435
- const filteredGuestIds = filteredBackupStatus . map ( guest => guest . guestId . toString ( ) ) ;
1524
+ // Get filtered guest IDs for calendar filtering - use unique guest identifiers
1525
+ const filteredGuestIds = filteredBackupStatus . map ( guest => {
1526
+ // Create unique identifier including node/endpoint to handle guests with same vmid on different nodes
1527
+ const nodeIdentifier = guest . node || guest . endpointId || '' ;
1528
+ return nodeIdentifier ? `${ guest . guestId } -${ nodeIdentifier } ` : guest . guestId . toString ( ) ;
1529
+ } ) ;
1436
1530
1437
1531
// Get detail card container
1438
1532
const detailCardContainer = document . getElementById ( 'backup-detail-card' ) ;
@@ -1465,8 +1559,20 @@ PulseApp.ui.backups = (() => {
1465
1559
if ( dateData ) {
1466
1560
// Apply current table filters to the selected date's data
1467
1561
const filteredDateBackups = dateData . backups . filter ( backup => {
1468
- // Find the guest in filteredBackupStatus
1469
- const guestInFiltered = filteredBackupStatus . find ( g => g . guestId . toString ( ) === backup . vmid . toString ( ) ) ;
1562
+ // Find the guest in filteredBackupStatus using unique identification
1563
+ const guestInFiltered = filteredBackupStatus . find ( g => {
1564
+ // First try exact vmid match for simple cases
1565
+ if ( g . guestId . toString ( ) === backup . vmid . toString ( ) ) {
1566
+ // If backup has node info, ensure it matches
1567
+ if ( backup . node || backup . endpointId ) {
1568
+ return ( backup . node && g . node === backup . node ) ||
1569
+ ( backup . endpointId && g . endpointId === backup . endpointId ) ||
1570
+ ( ! backup . node && ! backup . endpointId ) ;
1571
+ }
1572
+ return true ;
1573
+ }
1574
+ return false ;
1575
+ } ) ;
1470
1576
return guestInFiltered !== undefined ;
1471
1577
} ) ;
1472
1578
@@ -1499,8 +1605,16 @@ PulseApp.ui.backups = (() => {
1499
1605
1500
1606
// Update calendar date filter for table
1501
1607
if ( dateData && dateData . backups && dateData . backups . length > 0 ) {
1502
- // Extract guest IDs from the selected date's backup data
1503
- const guestIds = dateData . backups . map ( backup => backup . vmid . toString ( ) ) ;
1608
+ // Extract unique guest identifiers from the selected date's backup data
1609
+ const guestIds = dateData . backups . map ( backup => {
1610
+ // Use unique key if available, fall back to vmid
1611
+ if ( backup . uniqueKey ) {
1612
+ return backup . uniqueKey ;
1613
+ }
1614
+ // Create unique key from available data
1615
+ const nodeIdentifier = backup . node || backup . endpointId || '' ;
1616
+ return nodeIdentifier ? `${ backup . vmid } -${ nodeIdentifier } ` : backup . vmid . toString ( ) ;
1617
+ } ) ;
1504
1618
PulseApp . state . set ( 'calendarDateFilter' , {
1505
1619
date : dateData . date ,
1506
1620
guestIds : guestIds
0 commit comments