Clarify 7-day history column with detailed tooltips and legend

- Add descriptive tooltip to 7-Day History column header
- Enhance individual dot tooltips to show specific backup activity
- Show backup type (PBS/PVE), location, and success/failure details
- Add visual legend below table showing color meanings
- Include date and activity details in each day's tooltip
- Clear distinction between PBS, PVE, and snapshot activity
- Improved user understanding of backup activity timeline
This commit is contained in:
courtmanr@gmail.com
2025-05-28 14:00:52 +01:00
parent b1f2d4ebd7
commit b15651dfd5
2 changed files with 85 additions and 26 deletions
+25 -2
View File
@@ -796,7 +796,7 @@
<th scope="col" class="sticky top-0 bg-gray-100 dark:bg-gray-700 z-10 sortable p-1 px-2 cursor-pointer select-none whitespace-nowrap text-center" data-sort="pbsBackups">PBS Backups</th>
<th scope="col" class="sticky top-0 bg-gray-100 dark:bg-gray-700 z-10 sortable p-1 px-2 cursor-pointer select-none whitespace-nowrap text-center" data-sort="pveBackups">PVE Backups</th>
<th scope="col" class="sticky top-0 bg-gray-100 dark:bg-gray-700 z-10 p-1 px-2 text-center select-none whitespace-nowrap">Snapshots</th>
<th scope="col" class="sticky top-0 bg-gray-100 dark:bg-gray-700 z-10 p-1 px-2 text-center select-none whitespace-nowrap">7-Day History</th>
<th scope="col" class="sticky top-0 bg-gray-100 dark:bg-gray-700 z-10 p-1 px-2 text-center select-none whitespace-nowrap" title="Backup Activity History - Shows backup success (green), failure (red), or no backup activity (gray) for the last 7 days">7-Day History</th>
</tr>
</thead>
<tbody id="backups-overview-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
@@ -804,7 +804,30 @@
</div>
<p id="backups-no-data-message" class="text-gray-500 dark:text-gray-400 p-4 text-center hidden">No backup data found matching filters.</p>
</div>
<span id="backups-status-text" class="text-xs text-gray-500 dark:text-gray-400">Loading backup status...</span>
<div class="flex flex-wrap items-center gap-4 justify-between">
<span id="backups-status-text" class="text-xs text-gray-500 dark:text-gray-400">Loading backup status...</span>
<div class="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-3">
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-purple-500 rounded-full"></span>PBS
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-orange-500 rounded-full"></span>PVE
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-blue-500 rounded-full"></span>Snapshots
</span>
<span class="mx-2"></span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-green-500 rounded-full"></span>Success
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-red-500 rounded-full"></span>Failed
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 bg-gray-300 dark:bg-gray-600 rounded-full"></span>No Activity
</span>
</div>
</div>
</div>
</div>
<div id="custom-tooltip" class="custom-tooltip absolute hidden z-50 px-2 py-1 text-xs text-white bg-gray-900/90 rounded shadow-lg pointer-events-none transition-opacity duration-100"></div>
+60 -24
View File
@@ -219,35 +219,71 @@ PulseApp.ui.backups = (() => {
displayTimestamp = null;
}
// Optimized 7-day backup status calculation using pre-calculated boundaries
const last7DaysBackupStatus = dayBoundaries.map(day => {
// Enhanced 7-day backup status calculation with detailed activity info
const last7DaysBackupStatus = dayBoundaries.map((day, index) => {
let dailyStatus = 'none';
let activityDetails = [];
// Check tasks for this day - using pre-filtered guest tasks
if (guestTasks) {
const failedTaskOnThisDay = guestTasks.find(task =>
const failedTasksOnThisDay = guestTasks.filter(task =>
task.startTime >= day.start && task.startTime < day.end && task.status !== 'OK'
);
const successfulTaskOnThisDay = guestTasks.find(task =>
const successfulTasksOnThisDay = guestTasks.filter(task =>
task.startTime >= day.start && task.startTime < day.end && task.status === 'OK'
);
if (failedTaskOnThisDay) {
// Add task details
successfulTasksOnThisDay.forEach(task => {
const source = task.source === 'pbs' ? 'PBS' : 'PVE';
const location = task.source === 'pbs' ? task.pbsInstanceName : 'Local';
activityDetails.push(`${source} backup${location ? ` (${location})` : ''}`);
});
failedTasksOnThisDay.forEach(task => {
const source = task.source === 'pbs' ? 'PBS' : 'PVE';
const location = task.source === 'pbs' ? task.pbsInstanceName : 'Local';
activityDetails.push(`${source} backup failed${location ? ` (${location})` : ''}`);
});
if (failedTasksOnThisDay.length > 0) {
dailyStatus = 'failed';
} else if (successfulTaskOnThisDay) {
} else if (successfulTasksOnThisDay.length > 0) {
dailyStatus = 'ok';
} else if (guestSnapshots) {
// Check snapshots as fallback - using pre-filtered guest snapshots
const snapshotOnThisDay = guestSnapshots.some(
snap => snap['backup-time'] >= day.start && snap['backup-time'] < day.end
);
if (snapshotOnThisDay) {
dailyStatus = 'ok';
}
}
}
return dailyStatus;
// Check for backup storage activity (snapshots/backups created)
if (guestSnapshots && dailyStatus === 'none') {
const snapshotsOnThisDay = guestSnapshots.filter(
snap => snap['backup-time'] >= day.start && snap['backup-time'] < day.end
);
if (snapshotsOnThisDay.length > 0) {
snapshotsOnThisDay.forEach(snap => {
if (snap.source === 'pbs') {
activityDetails.push(`✓ PBS backup stored (${snap.pbsInstanceName})`);
} else if (snap.source === 'pve') {
activityDetails.push(`✓ PVE backup stored (${snap.storage || 'Local'})`);
}
});
dailyStatus = 'ok';
}
}
// Create day label for tooltip
const dayDate = new Date(day.start * 1000);
const dayLabel = dayDate.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
});
return {
status: dailyStatus,
details: activityDetails.length > 0 ? activityDetails.join('\n') : 'No backup activity',
date: dayLabel
};
});
// Calculate separate PBS and PVE backup information
@@ -389,24 +425,24 @@ PulseApp.ui.backups = (() => {
: 'ct-icon bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 px-1.5 py-0.5 font-medium';
const typeIcon = `<span class="type-icon inline-block rounded text-xs align-middle ${typeIconClass}">${guestStatus.guestType}</span>`;
// Generate 7-day backup dots
let sevenDayDots = '<div class="flex space-x-0.5" title="Last 7 days backup history (oldest to newest)">';
// Generate 7-day backup dots with enhanced tooltips
let sevenDayDots = '<div class="flex space-x-0.5">';
if (guestStatus.last7DaysBackupStatus && guestStatus.last7DaysBackupStatus.length === 7) {
guestStatus.last7DaysBackupStatus.forEach(status => {
guestStatus.last7DaysBackupStatus.forEach(dayInfo => {
let dotClass = 'bg-gray-300 dark:bg-gray-600'; // Default for 'none'
let dotTitle = 'No backup';
if (status === 'ok') {
let dotTitle = `${dayInfo.date}: ${dayInfo.details}`;
if (dayInfo.status === 'ok') {
dotClass = 'bg-green-500';
dotTitle = 'Successful backup';
} else if (status === 'failed') {
} else if (dayInfo.status === 'failed') {
dotClass = 'bg-red-500';
dotTitle = 'Failed backup';
}
sevenDayDots += `<span class="w-2 h-2 ${dotClass} rounded-full" title="${dotTitle}"></span>`;
});
} else {
for (let i = 0; i < 7; i++) {
sevenDayDots += '<span class="w-2 h-2 bg-gray-200 dark:bg-gray-700 rounded-full" title="Data unavailable"></span>'; // Placeholder if data is missing
sevenDayDots += '<span class="w-2 h-2 bg-gray-200 dark:bg-gray-700 rounded-full" title="Data unavailable"></span>';
}
}
sevenDayDots += '</div>';