diff --git a/src/public/app.js b/src/public/app.js
index 96b0d95d6..305f58e62 100644
--- a/src/public/app.js
+++ b/src/public/app.js
@@ -31,6 +31,9 @@
})();
+// Helper function to sanitize strings for use in HTML IDs
+const sanitizeForId = (str) => str.replace(/[^a-zA-Z0-9-]/g, '-');
+
document.addEventListener('DOMContentLoaded', function() {
// Guard clauses to ensure essential elements exist before proceeding
const themeToggleButton = document.getElementById('theme-toggle-button');
@@ -145,6 +148,9 @@ document.addEventListener('DOMContentLoaded', function() {
// let pbsConfigured = false; // Flag to track if PBS is configured
// <--- END REMOVE
+ // Define initial limit for PBS task tables
+ const INITIAL_PBS_TASK_LIMIT = 5;
+
// --- Global Helper for Text Progress Bar ---
const createProgressTextBarHTML = (percent, text, colorClass) => {
const numericPercent = isNaN(parseInt(percent)) ? 0 : parseInt(percent);
@@ -1475,13 +1481,18 @@ document.addEventListener('DOMContentLoaded', function() {
};
const getPbsGcStatusText = (gcStatus) => {
- if (!gcStatus || gcStatus === 'unknown') return 'Unknown ';
+ // Handle falsy values, 'unknown', or literal 'N/A' string
+ if (!gcStatus || gcStatus === 'unknown' || gcStatus === 'N/A') {
+ return 'Unknown ';
+ }
+ // Determine color based on known status keywords
let colorClass = 'text-gray-600 dark:text-gray-400';
if (gcStatus.includes('error') || gcStatus.includes('failed')) {
colorClass = 'text-red-500 dark:text-red-400';
} else if (gcStatus === 'OK') {
colorClass = 'text-green-500 dark:text-green-400';
}
+ // Return the original status text with appropriate color
return `${gcStatus} `;
};
@@ -1521,36 +1532,78 @@ document.addEventListener('DOMContentLoaded', function() {
}
// --- 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!`);
+ function populatePbsTaskTable(parentSectionElement, fullTasksArray) {
+ if (!parentSectionElement) {
+ console.error("Parent section element not provided to populatePbsTaskTable.");
return;
}
+ // Find elements relative to the parent section
+ const tbody = parentSectionElement.querySelector('.pbs-task-tbody');
+ const buttonContainer = parentSectionElement.querySelector('.pbs-toggle-button-container');
+
+ if (!tbody || !buttonContainer) {
+ // If elements aren't found *within the parent*, log an error
+ console.error(`Required child elements (.pbs-task-tbody or .pbs-toggle-button-container) not found within provided parent section.`, parentSectionElement);
+ return;
+ }
+
+ // Ensure tbody has an ID if it doesn't (needed for button linking)
+ if (!tbody.id) {
+ const table = parentSectionElement.querySelector('table');
+ tbody.id = table && table.id ? table.id.replace('-table-', '-tbody-') : `pbs-tbody-${Date.now()}-${Math.random()}`;
+ console.warn(`Assigned dynamic ID to tbody: ${tbody.id}`);
+ }
+
tbody.innerHTML = ''; // Clear previous content
+ buttonContainer.innerHTML = ''; // Clear previous button
- // ---> REMOVE SORTING <---
- // const sortedTasks = sortPbsTasks(tasks, sortColumn, sortDirection); // Assumes sortPbsTasks is moved up
- const tasksToDisplay = tasks || []; // Use raw tasks or empty array
- // ---> END REMOVE <---
+ const tasks = fullTasksArray || []; // Ensure tasks is an array
+ const totalTasks = tasks.length;
+ const limit = INITIAL_PBS_TASK_LIMIT;
+ const isCurrentlyExpanded = tbody.dataset.isExpanded === 'true'; // Check current state
- if (!tasksToDisplay || tasksToDisplay.length === 0) { // Use tasksToDisplay
+ const tasksToDisplay = isCurrentlyExpanded ? tasks : tasks.slice(0, limit);
+
+ // Store full data and state on the tbody
+ tbody.dataset.fullTasks = JSON.stringify(tasks); // Store all tasks
+ tbody.dataset.initialLimit = limit;
+ tbody.dataset.isExpanded = isCurrentlyExpanded ? 'true' : 'false';
+
+ if (tasksToDisplay.length === 0) {
tbody.innerHTML = `
No recent tasks found (last 7 days). `;
- return;
+ } else {
+ tasksToDisplay.forEach(task => {
+ const row = document.createElement('tr');
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
+ row.innerHTML = `
+ ${task.id || 'N/A'}
+ ${getPbsStatusIcon(task.status)}
+ ${formatPbsTimestamp(task.startTime)}
+ ${formatDuration(task.duration)}
+ ${task.upid || 'N/A'}
+ `;
+ tbody.appendChild(row);
+ });
}
- tasksToDisplay.forEach(task => { // Use tasksToDisplay
- const row = document.createElement('tr');
- row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50';
- row.innerHTML = `
- ${task.id || 'N/A'}
- ${getPbsStatusIcon(task.status)}
- ${formatPbsTimestamp(task.startTime)}
- ${formatDuration(task.duration)}
- ${task.upid || 'N/A'}
- `;
- tbody.appendChild(row);
- });
+ // Add Show More/Less button if needed
+ if (totalTasks > limit) {
+ const button = document.createElement('button');
+ const isExpanded = tbody.dataset.isExpanded === 'true';
+ const buttonText = isExpanded ? 'Show Less' : `Show More (${totalTasks - limit} older)`;
+ const iconSvg = isExpanded
+ ? ' '
+ : ' ';
+
+ button.innerHTML = buttonText + iconSvg;
+ button.type = 'button';
+ // Updated classes for button styling
+ button.className = 'pbs-toggle-button text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-1 focus:ring-blue-500';
+ button.dataset.targetTbodyId = tbody.id; // Use the actual ID of the tbody we found/assigned
+ buttonContainer.appendChild(button);
+ } else {
+ tbody.dataset.isExpanded = 'false';
+ }
}
// --- END NEW Function ---
@@ -1595,7 +1648,10 @@ document.addEventListener('DOMContentLoaded', function() {
return card; // Return the element
};
- const createTaskTableHTML = (tableId, title, idColumnHeader) => `
+ const createTaskTableHTML = (tableId, title, idColumnHeader) => {
+ const tbodyId = tableId.replace('-table-', '-tbody-');
+ const toggleButtonContainerId = tableId.replace('-table', '-toggle-container');
+ return `
Recent ${title} Tasks
@@ -1608,11 +1664,14 @@ document.addEventListener('DOMContentLoaded', function() {
UPID
-
+
-
`;
+
+
+ `;
+ };
// ---> REMOVE: setupPbsSortListener function definition <---
/*
@@ -1624,7 +1683,8 @@ document.addEventListener('DOMContentLoaded', function() {
// Create/Update content for each PBS instance
pbsArray.forEach((pbsInstance, index) => {
- const instanceId = pbsInstance.pbsEndpointId || `instance-${index}`;
+ const rawInstanceId = pbsInstance.pbsEndpointId || `instance-${index}`;
+ const instanceId = sanitizeForId(rawInstanceId); // Use sanitized ID for elements
const instanceName = pbsInstance.pbsInstanceName || `PBS Instance ${index + 1}`;
const instanceElementId = `pbs-instance-${instanceId}`;
currentInstanceIds.add(instanceElementId);
@@ -1638,16 +1698,19 @@ document.addEventListener('DOMContentLoaded', function() {
let statusColorClass = 'text-gray-600 dark:text-gray-400';
switch (pbsInstance.status) {
case 'configured':
- statusText = `Configured (${pbsInstance.nodeName || '...'}), attempting connection...`;
+ // statusText = `Configured (${pbsInstance.nodeName || '...'}), attempting connection...`; // Original
+ statusText = `Configured, attempting connection...`; // Simplified
statusColorClass = 'text-gray-600 dark:text-gray-400';
break;
case 'ok':
- statusText = `Status: OK (${pbsInstance.nodeName || 'Unknown Node'})`;
+ // statusText = `Status: OK (${pbsInstance.nodeName || 'Unknown Node'})`; // Original
+ statusText = `Status: OK`; // Simplified
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
+ // statusText = `Error connecting (${pbsInstance.errorMessage || 'Check Pulse logs.'})`; // Original
+ statusText = `Error: ${pbsInstance.errorMessage || 'Connection failed'}`; // Simplified
statusColorClass = 'text-red-600 dark:text-red-400';
break;
// 'unconfigured' is handled by the empty array check earlier
@@ -1708,18 +1771,18 @@ document.addEventListener('DOMContentLoaded', function() {
// 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 <---
+ const backupSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="backup"]');
+ if (backupSection) populatePbsTaskTable(backupSection, pbsInstance.backupTasks?.recentTasks);
+
+ const verifySection = detailsContainer.querySelector('.pbs-task-section[data-task-type="verify"]');
+ if (verifySection) populatePbsTaskTable(verifySection, pbsInstance.verificationTasks?.recentTasks);
+
+ const syncSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="sync"]');
+ if (syncSection) populatePbsTaskTable(syncSection, pbsInstance.syncTasks?.recentTasks);
+
+ const pruneGcSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="prunegc"]');
+ if (pruneGcSection) populatePbsTaskTable(pruneGcSection, pbsInstance.pruneTasks?.recentTasks);
+
} else {
// If details are hidden, ensure tables show the status message
const backupTbody = document.getElementById(`pbs-recent-backup-tasks-tbody-${instanceId}`);
@@ -1768,7 +1831,17 @@ document.addEventListener('DOMContentLoaded', function() {
Datastores
- Name Path Used Available Total Usage GC Status
+
+
+ Name
+ Path
+ Used
+ Available
+ Total
+ Usage
+ GC Status
+
+
`;
@@ -1786,15 +1859,26 @@ document.addEventListener('DOMContentLoaded', function() {
// Create Task Sections
const recentBackupTasksSection = document.createElement('div');
+ recentBackupTasksSection.className = 'pbs-task-section'; // Add class
+ recentBackupTasksSection.dataset.taskType = 'backup'; // Add data attribute
recentBackupTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-backup-tasks-table-${instanceId}`, 'Backup', 'Guest');
detailsContainer.appendChild(recentBackupTasksSection);
+
const recentVerifyTasksSection = document.createElement('div');
+ recentVerifyTasksSection.className = 'pbs-task-section'; // Add class
+ recentVerifyTasksSection.dataset.taskType = 'verify'; // Add data attribute
recentVerifyTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-verify-tasks-table-${instanceId}`, 'Verification', 'Guest/Group');
detailsContainer.appendChild(recentVerifyTasksSection);
+
const recentSyncTasksSection = document.createElement('div');
+ recentSyncTasksSection.className = 'pbs-task-section'; // Add class
+ recentSyncTasksSection.dataset.taskType = 'sync'; // Add data attribute
recentSyncTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-sync-tasks-table-${instanceId}`, 'Sync', 'Job ID');
detailsContainer.appendChild(recentSyncTasksSection);
+
const recentPruneGcTasksSection = document.createElement('div');
+ recentPruneGcTasksSection.className = 'pbs-task-section'; // Add class
+ recentPruneGcTasksSection.dataset.taskType = 'prunegc'; // Add data attribute
recentPruneGcTasksSection.innerHTML = createTaskTableHTML(`pbs-recent-prunegc-tasks-table-${instanceId}`, 'Prune/GC', 'Datastore/Group');
detailsContainer.appendChild(recentPruneGcTasksSection);
@@ -1826,19 +1910,29 @@ document.addEventListener('DOMContentLoaded', function() {
}
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);
+ const backupSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="backup"]');
+ if (backupSection) populatePbsTaskTable(backupSection, pbsInstance.backupTasks?.recentTasks);
+
+ const verifySection = detailsContainer.querySelector('.pbs-task-section[data-task-type="verify"]');
+ if (verifySection) populatePbsTaskTable(verifySection, pbsInstance.verificationTasks?.recentTasks);
+
+ const syncSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="sync"]');
+ if (syncSection) populatePbsTaskTable(syncSection, pbsInstance.syncTasks?.recentTasks);
+
+ const pruneGcSection = detailsContainer.querySelector('.pbs-task-section[data-task-type="prunegc"]');
+ if (pruneGcSection) populatePbsTaskTable(pruneGcSection, pbsInstance.pruneTasks?.recentTasks);
- // ---> REMOVE: Sort listener setup calls <---
- } 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} `;
- }
+ } 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} `;
+ }
} // End Upsert Logic
@@ -1855,4 +1949,39 @@ document.addEventListener('DOMContentLoaded', function() {
}
// --- End Update PBS Info Function ---
+ // --- PBS Task Table Toggle Listener ---
+ const pbsInstancesContainer = document.getElementById('pbs-instances-container');
+ if (pbsInstancesContainer) {
+ pbsInstancesContainer.addEventListener('click', (event) => {
+ const button = event.target.closest('.pbs-toggle-button');
+ if (!button) return; // Click wasn't on a toggle button
+
+ const targetTbodyId = button.dataset.targetTbodyId;
+ const tbody = document.getElementById(targetTbodyId);
+ if (!tbody) {
+ console.error("Target tbody not found for toggle button:", targetTbodyId);
+ return;
+ }
+
+ // Find the parent section containing this tbody
+ const parentSection = tbody.closest('.pbs-task-section');
+ if (!parentSection) {
+ console.error("Parent task section (.pbs-task-section) not found for tbody:", targetTbodyId);
+ return;
+ }
+
+ const isCurrentlyExpanded = tbody.dataset.isExpanded === 'true';
+ const fullTasks = JSON.parse(tbody.dataset.fullTasks || '[]');
+
+ // Toggle the expanded state *before* re-populating
+ tbody.dataset.isExpanded = isCurrentlyExpanded ? 'false' : 'true';
+
+ // Re-populate the table using the parent section and full task list
+ populatePbsTaskTable(parentSection, fullTasks);
+ });
+ } else {
+ console.warn("PBS instances container not found, toggle functionality will not work.");
+ }
+ // --- End PBS Task Table Toggle Listener ---
+
}); // End DOMContentLoaded
\ No newline at end of file