Skip to content

Commit 96934b6

Browse files
rcourtmanclaude
andcommitted
feat: implement consistent backup type filtering across detail card views
- Add comprehensive backup type filtering to detail card when filters are active - Show only filtered backup types (PBS/PVE/SNAP) instead of all types - Ensure calendar date selections respect active backup type filters - Implement three helper functions for different data structures - Clean up debug logging and improve scroll position handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aea8b79 commit 96934b6

File tree

2 files changed

+179
-32
lines changed

2 files changed

+179
-32
lines changed

src/public/js/ui/backup-detail-card.js

Lines changed: 172 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ PulseApp.ui.backupDetailCard = (() => {
6969
}
7070

7171
function getCompactOverview(backups, stats) {
72-
// Debug: Log what the detail card is receiving
73-
console.log('[Backup Health Debug] getCompactOverview received:', {
74-
backupsCount: backups.length,
75-
stats: stats
76-
});
7772

7873
// Calculate critical metrics
7974
const now = new Date();
@@ -294,6 +289,9 @@ PulseApp.ui.backupDetailCard = (() => {
294289
? (new Date() - mostRecent) / (1000 * 60 * 60 * 24)
295290
: Infinity;
296291
292+
// Get filtered backup types and counts based on active filter
293+
const filteredBackupData = getFilteredBackupData(guest, filterInfo);
294+
297295
return `
298296
<div class="grid grid-cols-12 gap-1 px-1 py-0.5 text-[11px] hover:bg-gray-50 dark:hover:bg-gray-700/30 rounded">
299297
<div class="col-span-5 flex items-center gap-1 min-w-0">
@@ -302,12 +300,10 @@ PulseApp.ui.backupDetailCard = (() => {
302300
<span class="truncate text-gray-700 dark:text-gray-300">${guest.guestName}</span>
303301
</div>
304302
<div class="col-span-3 flex items-center justify-center gap-1 text-[9px]">
305-
${guest.pbsBackups > 0 ? '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>' : ''}
306-
${guest.pveBackups > 0 ? '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>' : ''}
307-
${guest.snapshotCount > 0 ? '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>' : ''}
303+
${filteredBackupData.typeLabels}
308304
</div>
309305
<div class="col-span-2 text-right text-gray-600 dark:text-gray-400">
310-
${guest.pbsBackups + guest.pveBackups + guest.snapshotCount}
306+
${filteredBackupData.totalCount}
311307
</div>
312308
<div class="col-span-2 text-right font-medium ${getAgeColor(ageInDays)}">
313309
${formatAge(ageInDays)}
@@ -322,7 +318,7 @@ PulseApp.ui.backupDetailCard = (() => {
322318
}
323319

324320
function getSingleDateContent(data) {
325-
const { date, backups, stats } = data;
321+
const { date, backups, stats, filterInfo } = data;
326322

327323
if (!backups || backups.length === 0) {
328324
return getEmptyState(false);
@@ -344,38 +340,166 @@ PulseApp.ui.backupDetailCard = (() => {
344340
<span class="text-xs text-gray-500 dark:text-gray-400">${stats.totalGuests} guests</span>
345341
</div>
346342
<div class="flex items-center gap-3 mt-1 text-[10px]">
347-
${stats.pbsCount > 0 ? `<span class="text-purple-600 dark:text-purple-400">PBS: ${stats.pbsCount}</span>` : ''}
348-
${stats.pveCount > 0 ? `<span class="text-orange-600 dark:text-orange-400">PVE: ${stats.pveCount}</span>` : ''}
349-
${stats.snapshotCount > 0 ? `<span class="text-yellow-600 dark:text-yellow-400">Snap: ${stats.snapshotCount}</span>` : ''}
343+
${getFilteredStatsDisplay(stats, filterInfo)}
350344
</div>
351345
</div>
352346
353347
<!-- Guest List -->
354348
<div class="flex-1 overflow-y-auto">
355349
<div class="space-y-0.5">
356-
${sortedBackups.map(backup => `
357-
<div class="flex items-center justify-between px-1 py-0.5 text-[11px] hover:bg-gray-50 dark:hover:bg-gray-700/30 rounded">
358-
<div class="flex items-center gap-1 min-w-0">
359-
<span class="text-[9px] font-medium ${backup.type === 'VM' ? 'text-blue-600 dark:text-blue-400' : 'text-green-600 dark:text-green-400'}">${backup.type}</span>
360-
<span class="font-mono text-gray-600 dark:text-gray-400">${backup.vmid}</span>
361-
<span class="truncate text-gray-700 dark:text-gray-300">${backup.name}</span>
362-
</div>
363-
<div class="flex items-center gap-2 ml-2">
364-
<div class="flex items-center gap-1 text-[9px]">
365-
${backup.types.includes('pbsSnapshots') ? '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>' : ''}
366-
${backup.types.includes('pveBackups') ? '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>' : ''}
367-
${backup.types.includes('vmSnapshots') ? '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>' : ''}
350+
${sortedBackups.map(backup => {
351+
// Get filtered backup types and counts based on active filter
352+
const filteredBackupData = getFilteredSingleDateBackupData(backup, filterInfo);
353+
354+
return `
355+
<div class="flex items-center justify-between px-1 py-0.5 text-[11px] hover:bg-gray-50 dark:hover:bg-gray-700/30 rounded">
356+
<div class="flex items-center gap-1 min-w-0">
357+
<span class="text-[9px] font-medium ${backup.type === 'VM' ? 'text-blue-600 dark:text-blue-400' : 'text-green-600 dark:text-green-400'}">${backup.type}</span>
358+
<span class="font-mono text-gray-600 dark:text-gray-400">${backup.vmid}</span>
359+
<span class="truncate text-gray-700 dark:text-gray-300">${backup.name}</span>
360+
</div>
361+
<div class="flex items-center gap-2 ml-2">
362+
<div class="flex items-center gap-1 text-[9px]">
363+
${filteredBackupData.typeLabels}
364+
</div>
365+
<span class="text-gray-600 dark:text-gray-400">${filteredBackupData.backupCount}</span>
368366
</div>
369-
<span class="text-gray-600 dark:text-gray-400">${backup.backupCount}</span>
370367
</div>
371-
</div>
372-
`).join('')}
368+
`;
369+
}).join('')}
373370
</div>
374371
</div>
375372
</div>
376373
`;
377374
}
378375

376+
// Get filtered backup data based on active filter
377+
function getFilteredBackupData(guest, filterInfo) {
378+
if (!guest) return { typeLabels: '', totalCount: 0 };
379+
380+
const backupType = filterInfo?.backupType;
381+
382+
// If no specific backup type filter is active, show all types
383+
if (!backupType || backupType === 'all') {
384+
const typeLabels = [
385+
guest.pbsBackups > 0 ? '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>' : '',
386+
guest.pveBackups > 0 ? '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>' : '',
387+
guest.snapshotCount > 0 ? '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>' : ''
388+
].filter(label => label).join('');
389+
390+
return {
391+
typeLabels,
392+
totalCount: guest.pbsBackups + guest.pveBackups + guest.snapshotCount
393+
};
394+
}
395+
396+
// Show only the filtered backup type
397+
switch (backupType) {
398+
case 'pbs':
399+
return {
400+
typeLabels: guest.pbsBackups > 0 ? '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>' : '',
401+
totalCount: guest.pbsBackups
402+
};
403+
case 'pve':
404+
return {
405+
typeLabels: guest.pveBackups > 0 ? '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>' : '',
406+
totalCount: guest.pveBackups
407+
};
408+
case 'snapshots':
409+
return {
410+
typeLabels: guest.snapshotCount > 0 ? '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>' : '',
411+
totalCount: guest.snapshotCount
412+
};
413+
default:
414+
return {
415+
typeLabels: '',
416+
totalCount: 0
417+
};
418+
}
419+
}
420+
421+
// Get filtered backup data for single date view based on active filter
422+
function getFilteredSingleDateBackupData(backup, filterInfo) {
423+
if (!backup) return { typeLabels: '', backupCount: 0 };
424+
425+
const backupType = filterInfo?.backupType;
426+
427+
// If no specific backup type filter is active, show all types
428+
if (!backupType || backupType === 'all') {
429+
const typeLabels = [
430+
backup.types.includes('pbsSnapshots') ? '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>' : '',
431+
backup.types.includes('pveBackups') ? '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>' : '',
432+
backup.types.includes('vmSnapshots') ? '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>' : ''
433+
].filter(label => label).join('');
434+
435+
return {
436+
typeLabels,
437+
backupCount: backup.backupCount
438+
};
439+
}
440+
441+
// Show only the filtered backup type
442+
const typeMapping = {
443+
'pbs': 'pbsSnapshots',
444+
'pve': 'pveBackups',
445+
'snapshots': 'vmSnapshots'
446+
};
447+
448+
const targetType = typeMapping[backupType];
449+
if (backup.types.includes(targetType)) {
450+
switch (backupType) {
451+
case 'pbs':
452+
return {
453+
typeLabels: '<span class="text-purple-600 dark:text-purple-400 font-medium">PBS</span>',
454+
backupCount: backup.backupCount // Note: This shows total count, which may include other types
455+
};
456+
case 'pve':
457+
return {
458+
typeLabels: '<span class="text-orange-600 dark:text-orange-400 font-medium">PVE</span>',
459+
backupCount: backup.backupCount
460+
};
461+
case 'snapshots':
462+
return {
463+
typeLabels: '<span class="text-yellow-600 dark:text-yellow-400 font-medium">SNAP</span>',
464+
backupCount: backup.backupCount
465+
};
466+
}
467+
}
468+
469+
return {
470+
typeLabels: '',
471+
backupCount: 0
472+
};
473+
}
474+
475+
// Get filtered stats display for single date view
476+
function getFilteredStatsDisplay(stats, filterInfo) {
477+
if (!stats) return '';
478+
479+
const backupType = filterInfo?.backupType;
480+
481+
// If no specific backup type filter is active, show all stats
482+
if (!backupType || backupType === 'all') {
483+
return [
484+
stats.pbsCount > 0 ? `<span class="text-purple-600 dark:text-purple-400">PBS: ${stats.pbsCount}</span>` : '',
485+
stats.pveCount > 0 ? `<span class="text-orange-600 dark:text-orange-400">PVE: ${stats.pveCount}</span>` : '',
486+
stats.snapshotCount > 0 ? `<span class="text-yellow-600 dark:text-yellow-400">Snap: ${stats.snapshotCount}</span>` : ''
487+
].filter(stat => stat).join('');
488+
}
489+
490+
// Show only the filtered backup type stat
491+
switch (backupType) {
492+
case 'pbs':
493+
return stats.pbsCount > 0 ? `<span class="text-purple-600 dark:text-purple-400">PBS: ${stats.pbsCount}</span>` : '';
494+
case 'pve':
495+
return stats.pveCount > 0 ? `<span class="text-orange-600 dark:text-orange-400">PVE: ${stats.pveCount}</span>` : '';
496+
case 'snapshots':
497+
return stats.snapshotCount > 0 ? `<span class="text-yellow-600 dark:text-yellow-400">Snap: ${stats.snapshotCount}</span>` : '';
498+
default:
499+
return '';
500+
}
501+
}
502+
379503
// Helper functions
380504
function formatAge(ageInDays) {
381505
if (ageInDays === Infinity) return 'Never';
@@ -438,14 +562,34 @@ PulseApp.ui.backupDetailCard = (() => {
438562
const updateContent = () => {
439563
const newContent = !data ? getEmptyState(false) : getDetailContent(data);
440564

565+
// Find scrollable container and preserve scroll position
566+
const scrollableContainer = contentDiv.querySelector('.overflow-y-auto');
567+
const scrollTop = scrollableContainer ? scrollableContainer.scrollTop : 0;
568+
441569
if (!instant) {
442570
contentDiv.style.opacity = '0';
443571
setTimeout(() => {
444572
contentDiv.innerHTML = newContent;
445573
contentDiv.style.opacity = '1';
574+
575+
// Restore scroll position
576+
requestAnimationFrame(() => {
577+
const newScrollableContainer = contentDiv.querySelector('.overflow-y-auto');
578+
if (newScrollableContainer && scrollTop > 0) {
579+
newScrollableContainer.scrollTop = scrollTop;
580+
}
581+
});
446582
}, 150);
447583
} else {
448584
contentDiv.innerHTML = newContent;
585+
586+
// Restore scroll position for instant updates
587+
requestAnimationFrame(() => {
588+
const newScrollableContainer = contentDiv.querySelector('.overflow-y-auto');
589+
if (newScrollableContainer && scrollTop > 0) {
590+
newScrollableContainer.scrollTop = scrollTop;
591+
}
592+
});
449593
}
450594
};
451595

src/public/js/ui/backups.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,6 @@ PulseApp.ui.backups = (() => {
14731473
loadingMsg.classList.add('hidden');
14741474

14751475
// Debug: Log guest count
1476-
console.log('[Backup Health Debug] Total guests before backup status:', allGuests.length);
14771476

14781477
// Get PBS data array early as it's needed for guest backup status calculation
14791478
const pbsDataArray = PulseApp.state.get('pbsDataArray') || [];
@@ -1509,7 +1508,6 @@ PulseApp.ui.backups = (() => {
15091508
});
15101509

15111510
// Debug: Log backup status results
1512-
console.log('[Backup Health Debug] Backup status by guest count:', backupStatusByGuest.length);
15131511
const healthStats = {
15141512
'<24h': 0,
15151513
'1-7d': 0,
@@ -1530,7 +1528,6 @@ PulseApp.ui.backups = (() => {
15301528
else healthStats['>14d']++;
15311529
}
15321530
});
1533-
console.log('[Backup Health Debug] Health distribution:', healthStats);
15341531

15351532
const filteredBackupStatus = _filterBackupData(backupStatusByGuest, backupsSearchInput);
15361533

@@ -1712,6 +1709,13 @@ PulseApp.ui.backups = (() => {
17121709
backups: filteredDateBackups,
17131710
isCalendarFiltered: true, // Flag to indicate this is calendar-filtered data
17141711
calendarFilter: backupsFilterBackupType, // Include the filter type
1712+
filterInfo: {
1713+
search: backupsSearchInput ? backupsSearchInput.value : '',
1714+
guestType: PulseApp.state.get('backupsFilterGuestType') || 'all',
1715+
backupType: backupsFilterBackupType,
1716+
healthStatus: PulseApp.state.get('backupsFilterHealth') || 'all',
1717+
failuresOnly: PulseApp.state.get('backupsFilterFailures') || false
1718+
},
17151719
stats: {
17161720
totalGuests: filteredDateBackups.length,
17171721
pbsCount: filteredDateBackups.filter(b => b.types && b.types.includes('pbsSnapshots')).length,
@@ -2057,7 +2061,6 @@ PulseApp.ui.backups = (() => {
20572061

20582062
function _prepareMultiDateDetailData(filteredBackupStatus, backupData) {
20592063
// Debug: Log data being passed to detail card
2060-
console.log('[Backup Health Debug] _prepareMultiDateDetailData received guests:', filteredBackupStatus.length);
20612064

20622065
// Prepare data for multi-date detail view
20632066
const multiDateBackups = [];

0 commit comments

Comments
 (0)