Files
pulse/server/pbsUtils.js
T
courtmanr@gmail.com 8b1051a999 feat: enhance backup management with interactive calendar and improved failure tracking
- Add comprehensive PBS task failure detection and processing
- Implement interactive backup calendar heatmap with detail cards
- Enhance backup filtering with guest type and health status options
- Improve keyboard navigation and search functionality across tabs
- Add backup detail card component for granular backup information
- Update test coverage for enhanced backup data fetching
- Clean up debug logging and improve console output clarity

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-29 16:23:49 +01:00

164 lines
6.2 KiB
JavaScript

/**
* Processes a list of raw PBS tasks into structured summaries and recent task lists.
* @param {Array} allTasks - Array of raw task objects from the PBS API.
* @returns {Object} - Object containing structured task data (backupTasks, verificationTasks, etc.).
*/
function processPbsTasks(allTasks) {
// Ensure input is an array; return empty structure if not
if (!Array.isArray(allTasks)) {
console.warn('[PBS Utils] processPbsTasks received non-array input:', allTasks);
return {
backupTasks: { recentTasks: [], summary: { ok: 0, failed: 0, total: 0 } },
verificationTasks: { recentTasks: [], summary: { ok: 0, failed: 0, total: 0 } },
syncTasks: { recentTasks: [], summary: { ok: 0, failed: 0, total: 0 } },
pruneTasks: { recentTasks: [], summary: { ok: 0, failed: 0, total: 0 } },
aggregatedPbsTaskSummary: { total: 0, ok: 0, failed: 0 },
};
}
const taskTypeMap = {
backup: 'backup',
verify: 'verify',
verificationjob: 'verify',
verify_group: 'verify',
'verify-group': 'verify',
verification: 'verify',
sync: 'sync',
syncjob: 'sync',
'sync-job': 'sync',
garbage_collection: 'pruneGc',
'garbage-collection': 'pruneGc',
prune: 'pruneGc',
prunejob: 'pruneGc',
'prune-job': 'pruneGc',
gc: 'pruneGc'
};
const taskResults = categorizeAndCountTasks(allTasks, taskTypeMap);
// Warn if unmapped task types found (useful for production troubleshooting)
const unmappedTypes = new Set();
allTasks.forEach(task => {
const taskType = task.worker_type || task.type;
if (!taskTypeMap[taskType]) {
unmappedTypes.add(taskType);
}
});
if (unmappedTypes.size > 0) {
// console.warn('WARN: [pbsUtils] Unmapped PBS task types found:', Array.from(unmappedTypes));
}
const createDetailedTask = (task) => ({
upid: task.upid,
node: task.node,
type: task.worker_type || task.type,
id: task.worker_id || task.id || task.guest, // Include guest as fallback for ID
status: task.status,
startTime: task.starttime,
endTime: task.endtime,
duration: task.endtime && task.starttime ? task.endtime - task.starttime : null,
user: task.user,
exitCode: task.exitcode,
exitStatus: task.exitstatus,
saved: task.saved || false,
guest: task.guest || task.worker_id,
pbsBackupRun: task.pbsBackupRun,
// Add guest identification fields
guestId: task.guestId,
guestType: task.guestType
});
const sortTasksDesc = (a, b) => (b.startTime || 0) - (a.startTime || 0);
const getRecentTasksList = (taskList, detailedTaskFn, sortFn, count = 20) => {
if (!taskList) return [];
const nowSec = Date.now() / 1000;
const thirtyDays = 30 * 24 * 60 * 60;
const recent = taskList.filter(task => {
// Include tasks without a starttime and tasks within last 30 days
if (task.starttime == null) return true;
return (nowSec - task.starttime) <= thirtyDays;
});
return recent.map(detailedTaskFn).sort(sortFn).slice(0, count);
};
const recentBackupTasks = getRecentTasksList(taskResults.backup.list, createDetailedTask, sortTasksDesc);
const recentVerifyTasks = getRecentTasksList(taskResults.verify.list, createDetailedTask, sortTasksDesc);
const recentSyncTasks = getRecentTasksList(taskResults.sync.list, createDetailedTask, sortTasksDesc);
const recentPruneGcTasks = getRecentTasksList(taskResults.pruneGc.list, createDetailedTask, sortTasksDesc);
// Helper function to create the summary object
const createSummary = (category) => ({
ok: category.ok,
failed: category.failed,
total: category.ok + category.failed,
lastOk: category.lastOk || null, // Use null if 0
lastFailed: category.lastFailed || null // Use null if 0
});
return {
backupTasks: {
recentTasks: recentBackupTasks,
summary: createSummary(taskResults.backup)
},
verificationTasks: {
recentTasks: recentVerifyTasks,
summary: createSummary(taskResults.verify)
},
syncTasks: {
recentTasks: recentSyncTasks,
summary: createSummary(taskResults.sync)
},
pruneTasks: {
recentTasks: recentPruneGcTasks,
summary: createSummary(taskResults.pruneGc)
}
};
}
function categorizeAndCountTasks(allTasks, taskTypeMap) {
const results = {
backup: { list: [], ok: 0, failed: 0, lastOk: 0, lastFailed: 0 },
verify: { list: [], ok: 0, failed: 0, lastOk: 0, lastFailed: 0 },
sync: { list: [], ok: 0, failed: 0, lastOk: 0, lastFailed: 0 },
pruneGc: { list: [], ok: 0, failed: 0, lastOk: 0, lastFailed: 0 }
};
if (!allTasks || !Array.isArray(allTasks)) {
return results;
}
allTasks.forEach(task => {
const taskType = task.worker_type || task.type;
const categoryKey = taskTypeMap[taskType];
if (categoryKey) {
const category = results[categoryKey];
category.list.push(task);
// Fixed status handling - be more specific about what counts as failed
const status = task.status || 'NO_STATUS';
const isRunning = status.includes('running') || status.includes('queued');
const isCompleted = status && !isRunning;
if (isCompleted) {
if (status === 'OK') {
category.ok++;
if (task.endtime && task.endtime > category.lastOk) category.lastOk = task.endtime;
} else {
// Everything that's not OK or running is considered failed
// This includes: WARNING, WARNINGS:..., errors, connection errors, etc.
category.failed++;
if (task.endtime && task.endtime > category.lastFailed) category.lastFailed = task.endtime;
}
}
}
});
return results;
}
module.exports = { processPbsTasks };