diff --git a/src/public/app.js b/src/public/app.js
index 339e69a34..fc7ad09d1 100644
--- a/src/public/app.js
+++ b/src/public/app.js
@@ -121,20 +121,18 @@ document.addEventListener('DOMContentLoaded', function() {
let containersData = [];
let metricsData = [];
let dashboardData = [];
- // ---> CHANGE: pbsData becomes pbsDataArray
- // let pbsData = {}; // Add state for PBS data
- let pbsDataArray = []; // Holds data for multiple PBS instances
- // <--- END CHANGE
+ let pbsDataArray = [];
// Load saved sort state from localStorage or use defaults
const savedSortState = JSON.parse(localStorage.getItem('pulseSortState')) || {};
const sortState = {
nodes: { column: null, direction: 'asc', ...(savedSortState.nodes || {}) },
- main: { column: 'id', direction: 'asc', ...(savedSortState.main || {}) },
- // Add default sort states for PBS task tables
- pbsBackup: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsBackup || {}) },
- pbsVerify: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsVerify || {}) },
- pbsSync: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsSync || {}) },
- pbsPruneGc: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsPruneGc || {}) }
+ main: { column: 'id', direction: 'asc', ...(savedSortState.main || {}) }
+ // ---> REMOVE: PBS sort state <---
+ // pbsBackup: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsBackup || {}) },
+ // pbsVerify: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsVerify || {}) },
+ // pbsSync: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsSync || {}) },
+ // pbsPruneGc: { column: 'startTime', direction: 'desc', ...(savedSortState.pbsPruneGc || {}) }
+ // ---> END REMOVE <---
};
let groupByNode = true; // Default view
let filterGuestType = 'all'; // Default filter
@@ -401,44 +399,13 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
- // --- NEW PBS Task Sorting Function ---
+ // --- NEW PBS Task Sorting Function --- // ---> REMOVE FUNCTION <---
+ /*
function sortPbsTasks(data, column, direction) {
- if (!column || !data) return data || [];
- const dataToSort = [...data];
-
- return dataToSort.sort((a, b) => {
- let valueA = a ? a[column] : null;
- let valueB = b ? b[column] : null;
-
- // Handle specific types for comparison
- if (column === 'startTime' || column === 'endTime' || column === 'duration') {
- valueA = Number(valueA) || 0;
- valueB = Number(valueB) || 0;
- return direction === 'asc' ? valueA - valueB : valueB - valueA;
- } else if (column === 'id') { // Guest ID/Job ID/Datastore
- valueA = String(valueA || '').toLowerCase();
- valueB = String(valueB || '').toLowerCase();
- return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
- } else if (column === 'status') {
- // Simple status sort (OK > running > Error/Other > null/undefined)
- const getStatusValue = (status) => {
- if (status === 'OK') return 0;
- if (status === 'running') return 1;
- if (!status) return 3;
- return 2; // Other error statuses
- };
- valueA = getStatusValue(a?.status);
- valueB = getStatusValue(b?.status);
- return direction === 'asc' ? valueA - valueB : valueB - valueA;
- } else {
- // Default string comparison
- valueA = String(valueA || '').toLowerCase();
- valueB = String(valueB || '').toLowerCase();
- return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
- }
- });
+ // ... function content ...
}
- // --- END NEW PBS Task Sorting Function ---
+ */
+ // --- END NEW PBS Task Sorting Function --- // ---> END REMOVE <---
// --- Data Update/Display Functions ---
function updateNodesTable(nodes, skipSorting = false) {
@@ -1464,7 +1431,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
// --- End fetch version ---
- // --- Helper Functions for PBS Tab ---
+ // --- Helper Functions for PBS Tab --- // MOVED UP
const formatPbsTimestamp = (ts) => {
if (!ts) return 'Never';
try {
@@ -1553,8 +1520,8 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
- // --- NEW Function to Populate a PBS Task Table ---
- function populatePbsTaskTable(tbodyId, tasks, sortColumn, sortDirection) {
+ // --- NEW Function to Populate a PBS Task Table --- // MOVED UP & MODIFIED
+ function populatePbsTaskTable(tbodyId, tasks) { // Remove sortColumn, sortDirection
const tbody = document.getElementById(tbodyId);
if (!tbody) {
console.error(`PBS Task Table Body #${tbodyId} not found!`);
@@ -1562,14 +1529,17 @@ document.addEventListener('DOMContentLoaded', function() {
}
tbody.innerHTML = ''; // Clear previous content
- const sortedTasks = sortPbsTasks(tasks, sortColumn, sortDirection); // Assumes sortPbsTasks is moved up
+ // ---> REMOVE SORTING <---
+ // const sortedTasks = sortPbsTasks(tasks, sortColumn, sortDirection); // Assumes sortPbsTasks is moved up
+ const tasksToDisplay = tasks || []; // Use raw tasks or empty array
+ // ---> END REMOVE <---
- if (!sortedTasks || sortedTasks.length === 0) {
+ if (!tasksToDisplay || tasksToDisplay.length === 0) { // Use tasksToDisplay
tbody.innerHTML = `
+ // ---> REMOVE: max-h and overflow <---
+
+ // ---> END REMOVE <---
- | ${idColumnHeader} |
- Status |
- Start Time |
- Duration |
+ // ---> REMOVE: sortable classes <---
+ ${idColumnHeader} |
+ Status |
+ Start Time |
+ Duration |
+ // ---> END REMOVE <---
UPID |
@@ -1645,286 +1618,248 @@ document.addEventListener('DOMContentLoaded', function() {
`;
- const setupPbsSortListener = (tableId, sortStateKey, pbsInstance) => { // Pass pbsInstance here
- const tableElement = document.getElementById(tableId);
- if (!tableElement) {
- console.error(`PBS Task Table #${tableId} not found for sort listener setup!`);
- return;
- }
+ // ---> REMOVE: setupPbsSortListener function definition <---
+ /*
+ const setupPbsSortListener = (tableId, sortStateKey, pbsInstance) => {
+ // ... function content ...
+ };
+ */
+ // ---> END REMOVE <---
- tableElement.querySelectorAll('th.sortable').forEach(th => {
- if (th.dataset.listenerAttached === 'true') return;
-
- th.addEventListener('click', () => {
- const column = th.getAttribute('data-sort');
- if (!column) return;
-
- const currentSort = sortState[sortStateKey];
- if (currentSort.column === column) {
- currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
- } else {
- currentSort.column = column;
- currentSort.direction = 'asc';
- }
- let tasks;
- switch (sortStateKey) {
- case 'pbsBackup': tasks = pbsInstance.backupTasks?.recentTasks; break;
- case 'pbsVerify': tasks = pbsInstance.verificationTasks?.recentTasks; break;
- case 'pbsSync': tasks = pbsInstance.syncTasks?.recentTasks; break;
- case 'pbsPruneGc': tasks = pbsInstance.pruneTasks?.recentTasks; break;
- default: tasks = [];
- }
-
- const tbodyId = tableId.replace('-table-', '-tbody-');
- populatePbsTaskTable(tbodyId, tasks, currentSort.column, currentSort.direction);
- updateSortUI(tableId, th, sortStateKey); // Pass key on click
- });
- th.dataset.listenerAttached = 'true';
- });
-
- // Call for initial UI state:
- const currentSortConfig = sortState[sortStateKey];
- if (!currentSortConfig || !currentSortConfig.column) {
- console.warn(`Initial sort column not found for state key '${sortStateKey}'. Skipping initial sort UI update for table ${tableId}.`);
- return;
- }
- const initialHeader = tableElement.querySelector(`th[data-sort="${currentSortConfig.column}"]`);
- // ---> CONFIRM: Pass the sortStateKey explicitly <---
- if (initialHeader) updateSortUI(tableId, initialHeader, sortStateKey);
- // ---> END CONFIRM <---
- };
-
-
- // --- Create/Update content for each PBS instance ---
+ // Create/Update content for each PBS instance
pbsArray.forEach((pbsInstance, index) => {
- const instanceId = pbsInstance.pbsEndpointId || `instance-${index}`;
- const instanceName = pbsInstance.pbsInstanceName || `PBS Instance ${index + 1}`;
- const instanceElementId = `pbs-instance-${instanceId}`;
- currentInstanceIds.add(instanceElementId); // Track this ID
+ const instanceId = pbsInstance.pbsEndpointId || `instance-${index}`;
+ const instanceName = pbsInstance.pbsInstanceName || `PBS Instance ${index + 1}`;
+ const instanceElementId = `pbs-instance-${instanceId}`;
+ currentInstanceIds.add(instanceElementId);
- let instanceWrapper = document.getElementById(instanceElementId);
- let detailsContainer, dsTableBody, statusElement;
+ let instanceWrapper = document.getElementById(instanceElementId);
+ let detailsContainer, dsTableBody, statusElement;
- // Determine Status Text and Detail Visibility
- let statusText = 'Loading...';
- let showDetails = false;
- let statusColorClass = 'text-gray-600 dark:text-gray-400';
- switch (pbsInstance.status) {
- case 'configured':
- statusText = `Configured (${pbsInstance.nodeName || '...'}), attempting connection...`;
- statusColorClass = 'text-gray-600 dark:text-gray-400';
- break;
- case 'ok':
- statusText = `Status: OK (${pbsInstance.nodeName || 'Unknown Node'})`;
- statusColorClass = 'text-green-600 dark:text-green-400';
- showDetails = true;
- break;
- case 'error':
- statusText = `Error connecting (${pbsInstance.errorMessage || 'Check Pulse logs.'})`; // Try to show specific error
- statusColorClass = 'text-red-600 dark:text-red-400';
- break;
- // 'unconfigured' is handled by the empty array check earlier
- default:
- statusText = `Status: ${pbsInstance.status || 'Unknown'}`;
- break;
- }
+ // Determine Status Text and Detail Visibility
+ let statusText = 'Loading...';
+ let showDetails = false;
+ let statusColorClass = 'text-gray-600 dark:text-gray-400';
+ switch (pbsInstance.status) {
+ case 'configured':
+ statusText = `Configured (${pbsInstance.nodeName || '...'}), attempting connection...`;
+ statusColorClass = 'text-gray-600 dark:text-gray-400';
+ break;
+ case 'ok':
+ statusText = `Status: OK (${pbsInstance.nodeName || 'Unknown Node'})`;
+ statusColorClass = 'text-green-600 dark:text-green-400';
+ showDetails = true;
+ break;
+ case 'error':
+ statusText = `Error connecting (${pbsInstance.errorMessage || 'Check Pulse logs.'})`; // Try to show specific error
+ statusColorClass = 'text-red-600 dark:text-red-400';
+ break;
+ // 'unconfigured' is handled by the empty array check earlier
+ default:
+ statusText = `Status: ${pbsInstance.status || 'Unknown'}`;
+ break;
+ }
- if (instanceWrapper) {
- // --- Instance Exists: Update ---
- statusElement = instanceWrapper.querySelector(`#pbs-status-${instanceId}`);
- detailsContainer = instanceWrapper.querySelector(`#pbs-details-${instanceId}`);
+ if (instanceWrapper) {
+ // Instance Exists: Update
+ statusElement = instanceWrapper.querySelector(`#pbs-status-${instanceId}`);
+ detailsContainer = instanceWrapper.querySelector(`#pbs-details-${instanceId}`);
- // Update status text and color
- if (statusElement) {
- statusElement.textContent = statusText;
- statusElement.className = `text-sm ${statusColorClass}`; // Update classes
- }
-
- if (detailsContainer) {
- // Update Datastore Table Body
- dsTableBody = detailsContainer.querySelector(`#pbs-ds-tbody-${instanceId}`);
- if (dsTableBody) {
- dsTableBody.innerHTML = ''; // Clear existing rows
- if (showDetails && pbsInstance.datastores) {
- if (pbsInstance.datastores.length === 0) {
- dsTableBody.innerHTML = `
| No PBS datastores found or accessible. |
`;
- } else {
- pbsInstance.datastores.forEach(ds => {
- const totalBytes = ds.total || 0;
- const usedBytes = ds.used || 0;
- const availableBytes = (ds.available !== null && ds.available !== undefined) ? ds.available : (totalBytes > 0 ? totalBytes - usedBytes : 0);
- const usagePercent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
- const usageColor = getUsageColor(usagePercent);
- const usageText = totalBytes > 0 ? `${usagePercent}% (${formatBytes(usedBytes)} of ${formatBytes(totalBytes)})` : 'N/A';
- const gcStatusHtml = getPbsGcStatusText(ds.gcStatus);
- const row = document.createElement('tr');
- row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
- row.innerHTML = `
${ds.name || 'N/A'} | ${ds.path || 'N/A'} | ${formatBytes(usedBytes)} | ${formatBytes(availableBytes)} | ${totalBytes > 0 ? formatBytes(totalBytes) : 'N/A'} | ${totalBytes > 0 ? createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-'} | ${gcStatusHtml} | `;
- dsTableBody.appendChild(row);
- });
- }
- } else { // Not OK status, show status text in table
- dsTableBody.innerHTML = `
| ${statusText} |
`;
- }
- }
-
- // Update Task Summary Cards
- const summariesSection = detailsContainer.querySelector(`#pbs-summaries-section-${instanceId}`);
- if (summariesSection) {
- summariesSection.innerHTML = ''; // Clear existing cards
- summariesSection.appendChild(createSummaryCard('backup', 'Backups', pbsInstance.backupTasks));
- summariesSection.appendChild(createSummaryCard('verify', 'Verification', pbsInstance.verificationTasks));
- summariesSection.appendChild(createSummaryCard('sync', 'Sync', pbsInstance.syncTasks));
- summariesSection.appendChild(createSummaryCard('prune', 'Prune/GC', pbsInstance.pruneTasks));
- }
-
- // Update Task Tables
- if (showDetails) {
- populatePbsTaskTable(`pbs-recent-backup-tasks-tbody-${instanceId}`, pbsInstance.backupTasks?.recentTasks, sortState.pbsBackup.column, sortState.pbsBackup.direction);
- populatePbsTaskTable(`pbs-recent-verify-tasks-tbody-${instanceId}`, pbsInstance.verificationTasks?.recentTasks, sortState.pbsVerify.column, sortState.pbsVerify.direction);
- populatePbsTaskTable(`pbs-recent-sync-tasks-tbody-${instanceId}`, pbsInstance.syncTasks?.recentTasks, sortState.pbsSync.column, sortState.pbsSync.direction);
- populatePbsTaskTable(`pbs-recent-prunegc-tasks-tbody-${instanceId}`, pbsInstance.pruneTasks?.recentTasks, sortState.pbsPruneGc.column, sortState.pbsPruneGc.direction);
- // Re-apply/Ensure sort listeners exist
- setupPbsSortListener(`pbs-recent-backup-tasks-table-${instanceId}`, 'pbsBackup', pbsInstance);
- setupPbsSortListener(`pbs-recent-verify-tasks-table-${instanceId}`, 'pbsVerify', pbsInstance);
- setupPbsSortListener(`pbs-recent-sync-tasks-table-${instanceId}`, 'pbsSync', pbsInstance);
- setupPbsSortListener(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'pbsPruneGc', pbsInstance);
- } else {
- // If details are hidden, ensure tables show the status message
- const backupTbody = document.getElementById(`pbs-recent-backup-tasks-tbody-${instanceId}`);
- if (backupTbody) backupTbody.innerHTML = `
| ${statusText} |
`;
- const verifyTbody = document.getElementById(`pbs-recent-verify-tasks-tbody-${instanceId}`);
- if (verifyTbody) verifyTbody.innerHTML = `
| ${statusText} |
`;
- const syncTbody = document.getElementById(`pbs-recent-sync-tasks-tbody-${instanceId}`);
- if (syncTbody) syncTbody.innerHTML = `
| ${statusText} |
`;
- const pruneTbody = document.getElementById(`pbs-recent-prunegc-tasks-tbody-${instanceId}`);
- if (pruneTbody) pruneTbody.innerHTML = `
| ${statusText} |
`;
- }
-
- // Set visibility of the details container
- detailsContainer.classList.toggle('hidden', !showDetails);
- } // end if(detailsContainer)
-
- } else {
- // --- Instance Doesn't Exist: Create ---
- instanceWrapper = document.createElement('div');
- instanceWrapper.className = 'pbs-instance-section border border-gray-200 dark:border-gray-700 rounded p-4 mb-4 bg-gray-50/30 dark:bg-gray-800/30';
- instanceWrapper.id = instanceElementId;
-
- // Header
- const headerDiv = document.createElement('div');
- headerDiv.className = 'flex justify-between items-center mb-3';
- const instanceTitle = document.createElement('h3');
- instanceTitle.className = 'text-lg font-semibold text-gray-800 dark:text-gray-200';
- instanceTitle.textContent = instanceName;
- statusElement = document.createElement('div');
- statusElement.className = `text-sm ${statusColorClass}`;
- statusElement.id = `pbs-status-${instanceId}`;
+ // Update status text and color
+ if (statusElement) {
statusElement.textContent = statusText;
- headerDiv.appendChild(instanceTitle);
- headerDiv.appendChild(statusElement);
- instanceWrapper.appendChild(headerDiv);
+ statusElement.className = `text-sm ${statusColorClass}`;
+ }
- // Details Container
- detailsContainer = document.createElement('div');
- detailsContainer.className = `pbs-instance-details space-y-4 ${showDetails ? '' : 'hidden'}`;
- detailsContainer.id = `pbs-details-${instanceId}`;
+ // Update contents IF the details container exists
+ if (detailsContainer) {
+ // Update Datastore Table Body
+ dsTableBody = detailsContainer.querySelector(`#pbs-ds-tbody-${instanceId}`);
+ if (dsTableBody) {
+ dsTableBody.innerHTML = '';
+ if (showDetails && pbsInstance.datastores) {
+ if (pbsInstance.datastores.length === 0) {
+ dsTableBody.innerHTML = `
| No PBS datastores found or accessible. |
`;
+ } else {
+ pbsInstance.datastores.forEach(ds => {
+ const totalBytes = ds.total || 0;
+ const usedBytes = ds.used || 0;
+ const availableBytes = (ds.available !== null && ds.available !== undefined) ? ds.available : (totalBytes > 0 ? totalBytes - usedBytes : 0);
+ const usagePercent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
+ const usageColor = getUsageColor(usagePercent);
+ const usageText = totalBytes > 0 ? `${usagePercent}% (${formatBytes(usedBytes)} of ${formatBytes(totalBytes)})` : 'N/A';
+ const gcStatusHtml = getPbsGcStatusText(ds.gcStatus);
+ const row = document.createElement('tr');
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
+ row.innerHTML = `
${ds.name || 'N/A'} | ${ds.path || 'N/A'} | ${formatBytes(usedBytes)} | ${formatBytes(availableBytes)} | ${totalBytes > 0 ? formatBytes(totalBytes) : 'N/A'} | ${totalBytes > 0 ? createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-'} | ${gcStatusHtml} | `;
+ dsTableBody.appendChild(row);
+ });
+ }
+ } else {
+ dsTableBody.innerHTML = `
| ${statusText} |
`;
+ }
+ }
- // Datastores Section
- const dsSection = document.createElement('div');
- dsSection.id = `pbs-ds-section-${instanceId}`;
- dsSection.innerHTML = `
-
Datastores
-
-
- | Name | Path | Used | Available | Total | Usage | GC Status |
-
-
-
`;
- detailsContainer.appendChild(dsSection);
+ // Update Task Summary Cards
+ const summariesSection = detailsContainer.querySelector(`#pbs-summaries-section-${instanceId}`);
+ if (summariesSection) {
+ summariesSection.innerHTML = ''; // Clear existing cards
+ summariesSection.appendChild(createSummaryCard('backup', 'Backups', pbsInstance.backupTasks));
+ summariesSection.appendChild(createSummaryCard('verify', 'Verification', pbsInstance.verificationTasks));
+ summariesSection.appendChild(createSummaryCard('sync', 'Sync', pbsInstance.syncTasks));
+ summariesSection.appendChild(createSummaryCard('prune', 'Prune/GC', pbsInstance.pruneTasks));
+ }
- // Task Summaries Section
- const summariesSection = document.createElement('div');
- summariesSection.id = `pbs-summaries-section-${instanceId}`;
- summariesSection.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4';
- summariesSection.appendChild(createSummaryCard('backup', 'Backups', pbsInstance.backupTasks));
- summariesSection.appendChild(createSummaryCard('verify', 'Verification', pbsInstance.verificationTasks));
- summariesSection.appendChild(createSummaryCard('sync', 'Sync', pbsInstance.syncTasks));
- summariesSection.appendChild(createSummaryCard('prune', 'Prune/GC', pbsInstance.pruneTasks));
- detailsContainer.appendChild(summariesSection);
+ // Update Task Tables
+ if (showDetails) {
+ // ---> REMOVE: Sorting parameters <---
+ populatePbsTaskTable(`pbs-recent-backup-tasks-tbody-${instanceId}`, pbsInstance.backupTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-verify-tasks-tbody-${instanceId}`, pbsInstance.verificationTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-sync-tasks-tbody-${instanceId}`, pbsInstance.syncTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-prunegc-tasks-tbody-${instanceId}`, pbsInstance.pruneTasks?.recentTasks);
+ // ---> END REMOVE <---
+ // ---> REMOVE: Sort listener setup calls <---
+ // setupPbsSortListener(`pbs-recent-backup-tasks-table-${instanceId}`, 'pbsBackup', pbsInstance);
+ // setupPbsSortListener(`pbs-recent-verify-tasks-table-${instanceId}`, 'pbsVerify', pbsInstance);
+ // setupPbsSortListener(`pbs-recent-sync-tasks-table-${instanceId}`, 'pbsSync', pbsInstance);
+ // setupPbsSortListener(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'pbsPruneGc', pbsInstance);
+ // ---> END REMOVE <---
+ } else {
+ // If details are hidden, ensure tables show the status message
+ const backupTbody = document.getElementById(`pbs-recent-backup-tasks-tbody-${instanceId}`);
+ if (backupTbody) backupTbody.innerHTML = `
| ${statusText} |
`;
+ const verifyTbody = document.getElementById(`pbs-recent-verify-tasks-tbody-${instanceId}`);
+ if (verifyTbody) verifyTbody.innerHTML = `
| ${statusText} |
`;
+ const syncTbody = document.getElementById(`pbs-recent-sync-tasks-tbody-${instanceId}`);
+ if (syncTbody) syncTbody.innerHTML = `
| ${statusText} |
`;
+ const pruneTbody = document.getElementById(`pbs-recent-prunegc-tasks-tbody-${instanceId}`);
+ if (pruneTbody) pruneTbody.innerHTML = `
| ${statusText} |
`;
+ }
- // Create Task Sections
- const recentBackupTasksSection = document.createElement('div');
- recentBackupTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-backup-tasks-table-${instanceId}`, 'Backup', 'Guest');
- detailsContainer.appendChild(recentBackupTasksSection);
- const recentVerifyTasksSection = document.createElement('div');
- recentVerifyTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-verify-tasks-table-${instanceId}`, 'Verification', 'Guest/Group');
- detailsContainer.appendChild(recentVerifyTasksSection);
- const recentSyncTasksSection = document.createElement('div');
- recentSyncTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-sync-tasks-table-${instanceId}`, 'Sync', 'Job ID');
- detailsContainer.appendChild(recentSyncTasksSection);
- const recentPruneGcTasksSection = document.createElement('div');
- recentPruneGcTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'Prune/GC', 'Datastore/Group');
- detailsContainer.appendChild(recentPruneGcTasksSection);
+ // Toggle visibility at the end
+ detailsContainer.classList.toggle('hidden', !showDetails);
+ } // End if(detailsContainer)
- instanceWrapper.appendChild(detailsContainer);
- container.appendChild(instanceWrapper); // Append new instance to DOM
+ } else {
+ // Instance Doesn't Exist: Create
+ instanceWrapper = document.createElement('div');
+ instanceWrapper.className = 'pbs-instance-section border border-gray-200 dark:border-gray-700 rounded p-4 mb-4 bg-gray-50/30 dark:bg-gray-800/30';
+ instanceWrapper.id = instanceElementId;
- // Populate tables and attach listeners AFTER appending
- dsTableBody = instanceWrapper.querySelector(`#pbs-ds-tbody-${instanceId}`); // Find the newly created body
- if (dsTableBody) {
- if (showDetails && pbsInstance.datastores) {
- if (pbsInstance.datastores.length === 0) { dsTableBody.innerHTML = `
| No PBS datastores found or accessible. |
`; }
- else {
- // Populate rows
- pbsInstance.datastores.forEach(ds => {
- const totalBytes = ds.total || 0;
- const usedBytes = ds.used || 0;
- const availableBytes = (ds.available !== null && ds.available !== undefined) ? ds.available : (totalBytes > 0 ? totalBytes - usedBytes : 0);
- const usagePercent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
- const usageColor = getUsageColor(usagePercent);
- const usageText = totalBytes > 0 ? `${usagePercent}% (${formatBytes(usedBytes)} of ${formatBytes(totalBytes)})` : 'N/A';
- const gcStatusHtml = getPbsGcStatusText(ds.gcStatus);
- const row = document.createElement('tr');
- row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
- row.innerHTML = `
${ds.name || 'N/A'} | ${ds.path || 'N/A'} | ${formatBytes(usedBytes)} | ${formatBytes(availableBytes)} | ${totalBytes > 0 ? formatBytes(totalBytes) : 'N/A'} | ${totalBytes > 0 ? createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-'} | ${gcStatusHtml} | `;
- dsTableBody.appendChild(row);
- });
- }
- } else { dsTableBody.innerHTML = `
| ${statusText} |
`; }
- }
+ // Header
+ const headerDiv = document.createElement('div');
+ headerDiv.className = 'flex justify-between items-center mb-3';
+ const instanceTitle = document.createElement('h3');
+ instanceTitle.className = 'text-lg font-semibold text-gray-800 dark:text-gray-200';
+ instanceTitle.textContent = instanceName;
+ statusElement = document.createElement('div');
+ statusElement.className = `text-sm ${statusColorClass}`;
+ statusElement.id = `pbs-status-${instanceId}`;
+ statusElement.textContent = statusText;
+ headerDiv.appendChild(instanceTitle);
+ headerDiv.appendChild(statusElement);
+ instanceWrapper.appendChild(headerDiv);
- if (showDetails) {
- populatePbsTaskTable(`pbs-recent-backup-tasks-tbody-${instanceId}`, pbsInstance.backupTasks?.recentTasks, sortState.pbsBackup.column, sortState.pbsBackup.direction);
- populatePbsTaskTable(`pbs-recent-verify-tasks-tbody-${instanceId}`, pbsInstance.verificationTasks?.recentTasks, sortState.pbsVerify.column, sortState.pbsVerify.direction);
- populatePbsTaskTable(`pbs-recent-sync-tasks-tbody-${instanceId}`, pbsInstance.syncTasks?.recentTasks, sortState.pbsSync.column, sortState.pbsSync.direction);
- populatePbsTaskTable(`pbs-recent-prunegc-tasks-tbody-${instanceId}`, pbsInstance.pruneTasks?.recentTasks, sortState.pbsPruneGc.column, sortState.pbsPruneGc.direction);
+ // Details Container
+ detailsContainer = document.createElement('div');
+ detailsContainer.className = `pbs-instance-details space-y-4 ${showDetails ? '' : 'hidden'}`;
+ detailsContainer.id = `pbs-details-${instanceId}`;
- setupPbsSortListener(`pbs-recent-backup-tasks-table-${instanceId}`, 'pbsBackup', pbsInstance);
- setupPbsSortListener(`pbs-recent-verify-tasks-table-${instanceId}`, 'pbsVerify', pbsInstance);
- setupPbsSortListener(`pbs-recent-sync-tasks-table-${instanceId}`, 'pbsSync', pbsInstance);
- setupPbsSortListener(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'pbsPruneGc', pbsInstance);
- } else {
- // Ensure initial status message is present if details start hidden
- const backupTbody = document.getElementById(`pbs-recent-backup-tasks-tbody-${instanceId}`); if (backupTbody) backupTbody.innerHTML = `
| ${statusText} |
`;
- const verifyTbody = document.getElementById(`pbs-recent-verify-tasks-tbody-${instanceId}`); if (verifyTbody) verifyTbody.innerHTML = `
| ${statusText} |
`;
- const syncTbody = document.getElementById(`pbs-recent-sync-tasks-tbody-${instanceId}`); if (syncTbody) syncTbody.innerHTML = `
| ${statusText} |
`;
- const pruneTbody = document.getElementById(`pbs-recent-prunegc-tasks-tbody-${instanceId}`); if (pruneTbody) pruneTbody.innerHTML = `
| ${statusText} |
`;
- }
+ // Datastores Section
+ const dsSection = document.createElement('div');
+ dsSection.id = `pbs-ds-section-${instanceId}`;
+ dsSection.innerHTML = `
+
Datastores
+
+
+ | Name | Path | Used | Available | Total | Usage | GC Status |
+
+
+
`;
+ detailsContainer.appendChild(dsSection);
- } // End Upsert Logic
+ // Task Summaries Section
+ const summariesSection = document.createElement('div');
+ summariesSection.id = `pbs-summaries-section-${instanceId}`;
+ summariesSection.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4';
+ summariesSection.appendChild(createSummaryCard('backup', 'Backups', pbsInstance.backupTasks));
+ summariesSection.appendChild(createSummaryCard('verify', 'Verification', pbsInstance.verificationTasks));
+ summariesSection.appendChild(createSummaryCard('sync', 'Sync', pbsInstance.syncTasks));
+ summariesSection.appendChild(createSummaryCard('prune', 'Prune/GC', pbsInstance.pruneTasks));
+ detailsContainer.appendChild(summariesSection);
- }); // End forEach pbsInstance
+ // Create Task Sections
+ const recentBackupTasksSection = document.createElement('div');
+ recentBackupTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-backup-tasks-table-${instanceId}`, 'Backup', 'Guest');
+ detailsContainer.appendChild(recentBackupTasksSection);
+ const recentVerifyTasksSection = document.createElement('div');
+ recentVerifyTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-verify-tasks-table-${instanceId}`, 'Verification', 'Guest/Group');
+ detailsContainer.appendChild(recentVerifyTasksSection);
+ const recentSyncTasksSection = document.createElement('div');
+ recentSyncTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-sync-tasks-table-${instanceId}`, 'Sync', 'Job ID');
+ detailsContainer.appendChild(recentSyncTasksSection);
+ const recentPruneGcTasksSection = document.createElement('div');
+ recentPruneGcTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'Prune/GC', 'Datastore/Group');
+ detailsContainer.appendChild(recentPruneGcTasksSection);
- // --- Remove Orphaned Instances ---
- container.querySelectorAll('.pbs-instance-section').forEach(el => {
- if (!currentInstanceIds.has(el.id)) {
- console.log(`Removing orphaned PBS instance element: ${el.id}`);
- el.remove();
- }
- });
+ instanceWrapper.appendChild(detailsContainer);
+ container.appendChild(instanceWrapper); // Append new instance to DOM
- }
- // --- End Update PBS Info Function ---
+ // Populate tables and attach listeners AFTER appending
+ dsTableBody = instanceWrapper.querySelector(`#pbs-ds-tbody-${instanceId}`); // Find the newly created body
+ if (dsTableBody) {
+ if (showDetails && pbsInstance.datastores) {
+ if (pbsInstance.datastores.length === 0) { dsTableBody.innerHTML = `
| No PBS datastores found or accessible. |
`; }
+ else {
+ // Populate rows
+ pbsInstance.datastores.forEach(ds => {
+ const totalBytes = ds.total || 0;
+ const usedBytes = ds.used || 0;
+ const availableBytes = (ds.available !== null && ds.available !== undefined) ? ds.available : (totalBytes > 0 ? totalBytes - usedBytes : 0);
+ const usagePercent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0;
+ const usageColor = getUsageColor(usagePercent);
+ const usageText = totalBytes > 0 ? `${usagePercent}% (${formatBytes(usedBytes)} of ${formatBytes(totalBytes)})` : 'N/A';
+ const gcStatusHtml = getPbsGcStatusText(ds.gcStatus);
+ const row = document.createElement('tr');
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
+ row.innerHTML = `
${ds.name || 'N/A'} | ${ds.path || 'N/A'} | ${formatBytes(usedBytes)} | ${formatBytes(availableBytes)} | ${totalBytes > 0 ? formatBytes(totalBytes) : 'N/A'} | ${totalBytes > 0 ? createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-'} | ${gcStatusHtml} | `;
+ dsTableBody.appendChild(row);
+ });
+ }
+ } else { dsTableBody.innerHTML = `
| ${statusText} |
`; }
+ }
+
+ if (showDetails) {
+ populatePbsTaskTable(`pbs-recent-backup-tasks-tbody-${instanceId}`, pbsInstance.backupTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-verify-tasks-tbody-${instanceId}`, pbsInstance.verificationTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-sync-tasks-tbody-${instanceId}`, pbsInstance.syncTasks?.recentTasks);
+ populatePbsTaskTable(`pbs-recent-prunegc-tasks-tbody-${instanceId}`, pbsInstance.pruneTasks?.recentTasks);
+
+ setupPbsSortListener(`pbs-recent-backup-tasks-table-${instanceId}`, 'pbsBackup', pbsInstance);
+ setupPbsSortListener(`pbs-recent-verify-tasks-table-${instanceId}`, 'pbsVerify', pbsInstance);
+ setupPbsSortListener(`pbs-recent-sync-tasks-table-${instanceId}`, 'pbsSync', pbsInstance);
+ setupPbsSortListener(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'pbsPruneGc', pbsInstance);
+ } else {
+ // Ensure initial status message is present if details start hidden
+ const backupTbody = document.getElementById(`pbs-recent-backup-tasks-tbody-${instanceId}`); if (backupTbody) backupTbody.innerHTML = `
| ${statusText} |
`;
+ const verifyTbody = document.getElementById(`pbs-recent-verify-tasks-tbody-${instanceId}`); if (verifyTbody) verifyTbody.innerHTML = `
| ${statusText} |
`;
+ const syncTbody = document.getElementById(`pbs-recent-sync-tasks-tbody-${instanceId}`); if (syncTbody) syncTbody.innerHTML = `
| ${statusText} |
`;
+ const pruneTbody = document.getElementById(`pbs-recent-prunegc-tasks-tbody-${instanceId}`); if (pruneTbody) pruneTbody.innerHTML = `
| ${statusText} |
`;
+ }
+
+ } // End Upsert Logic
+
+ }); // End forEach pbsInstance
+
+ // --- Remove Orphaned Instances ---
+ container.querySelectorAll('.pbs-instance-section').forEach(el => {
+ if (!currentInstanceIds.has(el.id)) {
+ console.log(`Removing orphaned PBS instance element: ${el.id}`);
+ el.remove();
+ }
+ });
+
+}
+// --- End Update PBS Info Function ---
}); // End DOMContentLoaded
\ No newline at end of file