Files
pulse/server/dataFetcherFixes.js
T
rcourtman 4452b27655 fix: resolve circular reference errors in alert system
- Fixed undefined guest.type errors in generateAlertMessage by adding proper null checks
- Separated notification status from alert objects to prevent circular references during serialization
- Added comprehensive error handling with fallback data when alert serialization fails
- Improved alert state management to track notification status separately
- Fixed alert display issues in UI by ensuring proper data structure
- Added debugging helpers for troubleshooting serialization issues
- Cleaned up console logging and removed development artifacts

These changes resolve the "Converting circular structure to JSON" errors that were
preventing alerts from being properly displayed and managed in the UI.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-09 16:15:56 +01:00

176 lines
5.4 KiB
JavaScript

/**
* Fixes for dataFetcher.js to address ground truth discrepancies
*/
/**
* Deduplicates guests across multiple endpoints
* @param {Array} guests - Array of VM or container objects
* @returns {Array} Deduplicated array
*/
function deduplicateGuests(guests) {
const seen = new Map();
return guests.filter(guest => {
// Create a unique key based on VMID and node
// This prevents counting the same guest multiple times
const key = `${guest.vmid}-${guest.node}`;
if (seen.has(key)) {
return false;
}
seen.set(key, true);
return true;
});
}
/**
* Fixed version of fetchPveDiscoveryData with deduplication
*/
async function fetchPveDiscoveryDataFixed(currentApiClients) {
const pveEndpointIds = Object.keys(currentApiClients);
let allNodes = [], allVms = [], allContainers = [];
if (pveEndpointIds.length === 0) {
return { nodes: [], vms: [], containers: [] };
}
const pvePromises = pveEndpointIds.map(endpointId => {
if (!currentApiClients[endpointId]) {
console.error(`[DataFetcher] No client found for endpoint: ${endpointId}`);
return Promise.resolve({ nodes: [], vms: [], containers: [] });
}
const { client: apiClientInstance, config } = currentApiClients[endpointId];
return fetchDataForPveEndpoint(endpointId, apiClientInstance, config);
});
const pveResults = await Promise.all(pvePromises);
// Aggregate results from all endpoints
pveResults.forEach(result => {
if (result) {
allNodes.push(...(result.nodes || []));
allVms.push(...(result.vms || []));
allContainers.push(...(result.containers || []));
}
});
// Deduplicate guests before returning
const deduplicatedVms = deduplicateGuests(allVms);
const deduplicatedContainers = deduplicateGuests(allContainers);
return {
nodes: allNodes,
vms: deduplicatedVms,
containers: deduplicatedContainers
};
}
/**
* Improved PBS backup counting that correctly identifies backup runs
*/
function improvedBackupCounting(pbsData) {
const backupsByGuest = new Map();
const backupsByDate = new Map();
if (!pbsData || !pbsData[0]?.datastores) {
return { totalBackups: 0, uniqueBackupRuns: 0, backupsByGuest };
}
pbsData[0].datastores.forEach(ds => {
(ds.snapshots || []).forEach(snap => {
const guestKey = `${snap['backup-type']}/${snap['backup-id']}`;
const dateKey = new Date(snap['backup-time'] * 1000).toISOString().split('T')[0];
const runKey = `${guestKey}:${dateKey}`;
// Count by guest
if (!backupsByGuest.has(guestKey)) {
backupsByGuest.set(guestKey, []);
}
backupsByGuest.get(guestKey).push(snap);
// Count unique backup runs (one per guest per day)
backupsByDate.set(runKey, snap);
});
});
return {
totalBackups: Array.from(backupsByGuest.values()).reduce((sum, backups) => sum + backups.length, 0),
uniqueBackupRuns: backupsByDate.size,
backupsByGuest
};
}
/**
* Validates backup ages and identifies missing backups
*/
function validateBackupAgesImproved(pbsData, expectedGuests) {
const now = Date.now();
const twentyFourHours = 24 * 60 * 60 * 1000;
const results = {
guestsWithRecentBackups: new Set(),
guestsWithoutRecentBackups: new Set(),
backupAgesByGuest: new Map(),
issues: []
};
// Initialize all expected guests as missing
expectedGuests.forEach(guestId => {
results.guestsWithoutRecentBackups.add(guestId);
});
if (pbsData && pbsData[0]?.datastores) {
pbsData[0].datastores.forEach(ds => {
(ds.snapshots || []).forEach(snap => {
const guestId = snap['backup-id'];
const backupTime = snap['backup-time'] * 1000;
const ageMs = now - backupTime;
// Track most recent backup for each guest
if (!results.backupAgesByGuest.has(guestId) ||
backupTime > results.backupAgesByGuest.get(guestId).time) {
results.backupAgesByGuest.set(guestId, {
time: backupTime,
ageHours: ageMs / (1000 * 60 * 60),
ageReadable: formatAge(ageMs)
});
}
// Check if backup is recent (within 24 hours)
if (ageMs < twentyFourHours) {
results.guestsWithRecentBackups.add(guestId);
results.guestsWithoutRecentBackups.delete(guestId);
}
});
});
}
// Identify specific issues
if (results.guestsWithoutRecentBackups.has('102')) {
results.issues.push('VM 102 has no recent backup despite being in backup job');
}
return results;
}
/**
* Format age in human-readable format
*/
function formatAge(ageMs) {
const hours = Math.floor(ageMs / (1000 * 60 * 60));
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days}d ${hours % 24}h`;
}
return `${hours}h`;
}
module.exports = {
deduplicateGuests,
fetchPveDiscoveryDataFixed,
improvedBackupCounting,
validateBackupAgesImproved,
formatAge
};