diff --git a/src/public/js/ui/pbs.js b/src/public/js/ui/pbs.js index c32cdb9be..b7fd2eb04 100644 --- a/src/public/js/ui/pbs.js +++ b/src/public/js/ui/pbs.js @@ -172,7 +172,7 @@ PulseApp.ui.pbs = (() => { const _createTaskTableRow = (task) => { const target = parsePbsTaskTarget(task); - const statusIcon = getPbsStatusIcon(task.status); + const statusIconHTML = getPbsStatusIcon(task.status); const startTime = task.startTime ? PulseApp.utils.formatPbsTimestamp(task.startTime) : 'N/A'; const duration = task.duration !== null ? PulseApp.utils.formatDuration(task.duration) : 'N/A'; const upid = task.upid || 'N/A'; @@ -180,13 +180,33 @@ PulseApp.ui.pbs = (() => { const row = document.createElement('tr'); row.className = `${CSS_CLASSES.BORDER_B_GRAY_200_DARK_GRAY_700} ${CSS_CLASSES.HOVER_BG_GRAY_50_DARK_HOVER_BG_GRAY_700_50} ${CSS_CLASSES.TRANSITION_COLORS} ${CSS_CLASSES.DURATION_150} ${CSS_CLASSES.EASE_IN_OUT}`; - row.innerHTML = ` - ${target} - ${statusIcon} - ${startTime} - ${duration} - ${shortUpid} - `; + + const targetCell = document.createElement('td'); + targetCell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.TEXT_SM} ${CSS_CLASSES.TEXT_GRAY_700_DARK_GRAY_300}`; + targetCell.textContent = target; + row.appendChild(targetCell); + + const statusCell = document.createElement('td'); + statusCell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.TEXT_SM}`; + statusCell.innerHTML = statusIconHTML; + row.appendChild(statusCell); + + const startTimeCell = document.createElement('td'); + startTimeCell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.TEXT_SM} ${CSS_CLASSES.TEXT_GRAY_500_DARK_GRAY_400} ${CSS_CLASSES.WHITESPACE_NOWRAP}`; + startTimeCell.textContent = startTime; + row.appendChild(startTimeCell); + + const durationCell = document.createElement('td'); + durationCell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.TEXT_SM} ${CSS_CLASSES.TEXT_GRAY_500_DARK_GRAY_400} ${CSS_CLASSES.WHITESPACE_NOWRAP}`; + durationCell.textContent = duration; + row.appendChild(durationCell); + + const upidCell = document.createElement('td'); + upidCell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.TEXT_XS} ${CSS_CLASSES.FONT_MONO} ${CSS_CLASSES.TEXT_GRAY_400} dark:text-gray-500 ${CSS_CLASSES.TRUNCATE}`; + upidCell.title = upid; + upidCell.textContent = shortUpid; + row.appendChild(upidCell); + return row; }; @@ -247,7 +267,11 @@ PulseApp.ui.pbs = (() => { if (showDetails && datastores) { if (datastores.length === 0) { - dsTableBody.innerHTML = `No PBS datastores found or accessible.`; + const row = dsTableBody.insertRow(); + const cell = row.insertCell(); + cell.colSpan = 7; + cell.className = `px-4 py-4 text-sm text-gray-400 text-center`; + cell.textContent = 'No PBS datastores found or accessible.'; } else { datastores.forEach(ds => { const totalBytes = ds.total || 0; @@ -257,14 +281,39 @@ PulseApp.ui.pbs = (() => { const usageColor = PulseApp.utils.getUsageColor(usagePercent); const usageText = totalBytes > 0 ? `${usagePercent}% (${PulseApp.utils.formatBytes(usedBytes)} of ${PulseApp.utils.formatBytes(totalBytes)})` : 'N/A'; const gcStatusHtml = getPbsGcStatusText(ds.gcStatus); - const row = document.createElement('tr'); + + const row = dsTableBody.insertRow(); row.className = `${CSS_CLASSES.HOVER_BG_GRAY_50_DARK_HOVER_BG_GRAY_700_50}`; - row.innerHTML = `${ds.name || 'N/A'} ${ds.path || 'N/A'} ${PulseApp.utils.formatBytes(usedBytes)} ${PulseApp.utils.formatBytes(availableBytes)} ${totalBytes > 0 ? PulseApp.utils.formatBytes(totalBytes) : 'N/A'} ${totalBytes > 0 ? PulseApp.utils.createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-'} ${gcStatusHtml}`; - dsTableBody.appendChild(row); + + const createCell = (content, classNames = [], isHtml = false) => { + const cell = row.insertCell(); + cell.className = `${CSS_CLASSES.P1_PX2} ${CSS_CLASSES.WHITESPACE_NOWRAP} ${classNames.join(' ')}`; + if (isHtml) { + cell.innerHTML = content; + } else { + cell.textContent = content; + } + return cell; + }; + + createCell(ds.name || 'N/A'); + createCell(ds.path || 'N/A', [CSS_CLASSES.TEXT_GRAY_500_DARK_GRAY_400]); + createCell(PulseApp.utils.formatBytes(usedBytes)); + createCell(PulseApp.utils.formatBytes(availableBytes)); + createCell(totalBytes > 0 ? PulseApp.utils.formatBytes(totalBytes) : 'N/A'); + + const usageCell = createCell(totalBytes > 0 ? PulseApp.utils.createProgressTextBarHTML(usagePercent, usageText, usageColor) : '-', [], true); + usageCell.style.minWidth = '150px'; + + createCell(gcStatusHtml, [], true); }); } } else { - dsTableBody.innerHTML = `${statusText}`; + const row = dsTableBody.insertRow(); + const cell = row.insertCell(); + cell.colSpan = 7; + cell.className = `px-4 py-4 ${CSS_CLASSES.TEXT_SM} ${CSS_CLASSES.TEXT_GRAY_400} text-center`; + cell.textContent = statusText; } }; @@ -327,48 +376,96 @@ PulseApp.ui.pbs = (() => { const lastOk = PulseApp.utils.formatPbsTimestamp(summary.lastOk); const lastFailed = PulseApp.utils.formatPbsTimestamp(summary.lastFailed); const failedStyle = (failed > 0) ? `${CSS_CLASSES.FONT_BOLD} ${CSS_CLASSES.TEXT_RED_600_DARK_RED_400}` : `${CSS_CLASSES.TEXT_RED_600_DARK_RED_400} ${CSS_CLASSES.FONT_SEMIBOLD}`; - const highlightClass = (failed > 0) ? CSS_CLASSES.BORDER_L_4_RED_500_DARK_RED_400 : CSS_CLASSES.BORDER_L_4_TRANSPARENT; + card.className = `${CSS_CLASSES.BORDER_GRAY_200_DARK_BORDER_GRAY_700} ${CSS_CLASSES.ROUNDED} ${CSS_CLASSES.P3} ${CSS_CLASSES.BG_GRAY_100_50_DARK_BG_GRAY_700_50} ${highlightClass}`; - card.innerHTML = ` -

${title} (7d)

-
-
OK: ${ok}
-
Failed: ${failed}
-
Total: ${total}
-
Last OK: ${lastOk}
-
Last Fail: ${lastFailed}
-
`; + const heading = document.createElement('h4'); + heading.className = `${CSS_CLASSES.TEXT_MD} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.MB2} ${CSS_CLASSES.TEXT_GRAY_700_DARK_GRAY_300}`; + heading.textContent = `${title} (7d)`; + card.appendChild(heading); + + const contentDiv = document.createElement('div'); + contentDiv.className = `${CSS_CLASSES.SPACE_Y_1} ${CSS_CLASSES.TEXT_SM}`; + + const createStatDiv = (label, value, valueClass = '') => { + const div = document.createElement('div'); + const labelSpan = document.createElement('span'); + labelSpan.className = `${CSS_CLASSES.FONT_MEDIUM} ${CSS_CLASSES.TEXT_GRAY_800_DARK_GRAY_200}`; + labelSpan.textContent = `${label}:`; + div.appendChild(labelSpan); + + const valueSpan = document.createElement('span'); + valueSpan.className = `${CSS_CLASSES.ML1} ${valueClass}`; + valueSpan.textContent = value; + div.appendChild(valueSpan); + return div; + }; + + contentDiv.appendChild(createStatDiv('OK', ok, `${CSS_CLASSES.TEXT_GREEN_600_DARK_GREEN_400} ${CSS_CLASSES.FONT_SEMIBOLD}`)); + contentDiv.appendChild(createStatDiv('Failed', failed, failedStyle)); + contentDiv.appendChild(createStatDiv('Total', total, `${CSS_CLASSES.TEXT_GRAY_700_DARK_GRAY_300} ${CSS_CLASSES.FONT_SEMIBOLD}`)); + contentDiv.appendChild(createStatDiv('Last OK', lastOk, `${CSS_CLASSES.TEXT_GRAY_600_DARK_GRAY_400} ${CSS_CLASSES.TEXT_XS}`)); + contentDiv.appendChild(createStatDiv('Last Fail', lastFailed, `${CSS_CLASSES.TEXT_GRAY_600_DARK_GRAY_400} ${CSS_CLASSES.TEXT_XS}`)); + + card.appendChild(contentDiv); return card; }; - const _createTaskTableHTML = (tableId, title, idColumnHeader) => { - const tbodyId = tableId.replace('-table-', '-tbody-'); // Keep this for now as it's specific - const toggleButtonContainerId = tableId.replace('-table', '-toggle-container'); // Keep this for now - return ` -

Recent ${title} Tasks

-
- - - - - - - - - - - - - -
${idColumnHeader}StatusStart TimeDurationUPID
-
-
- -

No recent tasks found.

-
- `; + const _createTaskTableElement = (tableId, title, idColumnHeader) => { + const fragment = document.createDocumentFragment(); + + const heading = document.createElement('h4'); + heading.className = `${CSS_CLASSES.TEXT_MD} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.MB2} ${CSS_CLASSES.TEXT_GRAY_700_DARK_GRAY_300}`; + heading.textContent = `Recent ${title} Tasks`; + fragment.appendChild(heading); + + const tableContainer = document.createElement('div'); + tableContainer.className = `${CSS_CLASSES.OVERFLOW_X_AUTO} ${CSS_CLASSES.BORDER_GRAY_200_DARK_BORDER_GRAY_700} ${CSS_CLASSES.ROUNDED}`; + + const table = document.createElement('table'); + table.id = tableId; + table.className = `${CSS_CLASSES.MIN_W_FULL} ${CSS_CLASSES.DIVIDE_Y_GRAY_200_DARK_DIVIDE_GRAY_700} ${CSS_CLASSES.TEXT_SM}`; + + const thead = document.createElement('thead'); + thead.className = `${CSS_CLASSES.STICKY} ${CSS_CLASSES.TOP_0} ${CSS_CLASSES.Z_10} ${CSS_CLASSES.BG_GRAY_100_DARK_BG_GRAY_800}`; + const headerRow = document.createElement('tr'); + headerRow.className = `${CSS_CLASSES.TEXT_XS} ${CSS_CLASSES.FONT_MEDIUM} ${CSS_CLASSES.TRACKING_WIDER} ${CSS_CLASSES.TEXT_LEFT} ${CSS_CLASSES.TEXT_GRAY_600_UPPERCASE_DARK_TEXT_GRAY_300} ${CSS_CLASSES.BORDER_B_GRAY_300_DARK_BORDER_GRAY_600}`; + + const headers = [idColumnHeader, 'Status', 'Start Time', 'Duration', 'UPID']; + headers.forEach(text => { + const th = document.createElement('th'); + th.scope = 'col'; + th.className = CSS_CLASSES.P1_PX2; + th.textContent = text; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + const tbody = document.createElement('tbody'); + tbody.id = tableId.replace('-table-', '-tbody-'); + tbody.className = `${CSS_CLASSES.PBS_TASK_TBODY} ${CSS_CLASSES.DIVIDE_Y_GRAY_200_DARK_DIVIDE_GRAY_700}`; + table.appendChild(tbody); + tableContainer.appendChild(table); + fragment.appendChild(tableContainer); + + const toggleButtonContainer = document.createElement('div'); + toggleButtonContainer.id = tableId.replace('-table', '-toggle-container'); + toggleButtonContainer.className = `pbs-toggle-button-container ${CSS_CLASSES.PT1} ${CSS_CLASSES.TEXT_RIGHT}`; + + const showMoreButton = document.createElement('button'); + showMoreButton.className = `${CSS_CLASSES.PBS_SHOW_MORE} ${CSS_CLASSES.TEXT_BLUE_600_HOVER_BLUE_800_DARK_BLUE_400_DARK_HOVER_BLUE_300} ${CSS_CLASSES.HIDDEN}`; + showMoreButton.textContent = 'Show More'; + toggleButtonContainer.appendChild(showMoreButton); + + const noTasksMessage = document.createElement('p'); + noTasksMessage.className = `${CSS_CLASSES.PBS_NO_TASKS} ${CSS_CLASSES.TEXT_XS} ${CSS_CLASSES.TEXT_GRAY_400} dark:text-gray-500 ${CSS_CLASSES.HIDDEN} ${CSS_CLASSES.ITALIC}`; + noTasksMessage.textContent = 'No recent tasks found.'; + toggleButtonContainer.appendChild(noTasksMessage); + + fragment.appendChild(toggleButtonContainer); + return fragment; }; pbsArray.forEach((pbsInstance, index) => { @@ -439,15 +536,111 @@ PulseApp.ui.pbs = (() => { if (health === 'ok') colorClass = CSS_CLASSES.BG_GREEN_500; else if (health === 'warning') colorClass = CSS_CLASSES.BG_YELLOW_500; else if (health === 'error') colorClass = CSS_CLASSES.BG_RED_500; - return ``; + const span = document.createElement('span'); + span.title = title; + span.className = `${CSS_CLASSES.INLINE_BLOCK} ${CSS_CLASSES.W_3} ${CSS_CLASSES.H_3} ${colorClass} ${CSS_CLASSES.ROUNDED_FULL} ${CSS_CLASSES.MR2} ${CSS_CLASSES.FLEX_SHRINK_0}`; + return span; + }; + + const _createPbsInstanceElement = (pbsInstanceData, instanceId, instanceName, overallHealth, healthTitle, showDetails, statusText) => { + const instanceWrapper = document.createElement('div'); + instanceWrapper.className = `${CSS_CLASSES.PBS_INSTANCE_SECTION} ${CSS_CLASSES.BORDER_GRAY_200_DARK_BORDER_GRAY_700} ${CSS_CLASSES.ROUNDED} p-4 mb-4 bg-gray-50/30 dark:bg-gray-800/30`; + instanceWrapper.id = ID_PREFIXES.PBS_INSTANCE + instanceId; + + const headerDiv = document.createElement('div'); + headerDiv.className = `${CSS_CLASSES.FLEX} ${CSS_CLASSES.JUSTIFY_BETWEEN} ${CSS_CLASSES.ITEMS_CENTER} ${CSS_CLASSES.MB3}`; + const instanceTitleElement = document.createElement('h3'); + instanceTitleElement.className = `${CSS_CLASSES.TEXT_LG} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.TEXT_GRAY_800_DARK_GRAY_200} ${CSS_CLASSES.FLEX} ${CSS_CLASSES.ITEMS_CENTER}`; + instanceTitleElement.appendChild(_createHealthBadgeHTML(overallHealth, healthTitle)); + instanceTitleElement.appendChild(document.createTextNode(instanceName)); + headerDiv.appendChild(instanceTitleElement); + instanceWrapper.appendChild(headerDiv); + + const detailsContainer = document.createElement('div'); + detailsContainer.className = `${CSS_CLASSES.PBS_INSTANCE_DETAILS} ${CSS_CLASSES.SPACE_Y_4} ${showDetails ? '' : CSS_CLASSES.HIDDEN}`; + detailsContainer.id = ID_PREFIXES.PBS_DETAILS + instanceId; + + const dsSection = document.createElement('div'); + dsSection.id = ID_PREFIXES.PBS_DS_SECTION + instanceId; + const dsHeading = document.createElement('h4'); + dsHeading.className = `${CSS_CLASSES.TEXT_MD} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.MB2} ${CSS_CLASSES.TEXT_GRAY_700_DARK_GRAY_300}`; + dsHeading.textContent = 'Datastores'; + dsSection.appendChild(dsHeading); + const dsTableContainer = document.createElement('div'); + dsTableContainer.className = `${CSS_CLASSES.OVERFLOW_X_AUTO} ${CSS_CLASSES.BORDER_GRAY_200_DARK_BORDER_GRAY_700} ${CSS_CLASSES.ROUNDED}`; + const dsTable = document.createElement('table'); + dsTable.id = ID_PREFIXES.PBS_DS_TABLE + instanceId; + dsTable.className = `${CSS_CLASSES.MIN_W_FULL} ${CSS_CLASSES.DIVIDE_Y_GRAY_200_DARK_DIVIDE_GRAY_700} ${CSS_CLASSES.TEXT_SM}`; + const dsThead = document.createElement('thead'); + dsThead.className = `${CSS_CLASSES.STICKY} ${CSS_CLASSES.TOP_0} ${CSS_CLASSES.Z_10} ${CSS_CLASSES.BG_GRAY_100_DARK_BG_GRAY_800}`; + const dsHeaderRow = document.createElement('tr'); + dsHeaderRow.className = `${CSS_CLASSES.TEXT_XS} ${CSS_CLASSES.FONT_MEDIUM} ${CSS_CLASSES.TRACKING_WIDER} ${CSS_CLASSES.TEXT_LEFT} ${CSS_CLASSES.TEXT_GRAY_600_UPPERCASE_DARK_TEXT_GRAY_300} ${CSS_CLASSES.BORDER_B_GRAY_300_DARK_BORDER_GRAY_600}`; + ['Name', 'Path', 'Used', 'Available', 'Total', 'Usage', 'GC Status'].forEach(headerText => { + const th = document.createElement('th'); + th.scope = 'col'; + th.className = CSS_CLASSES.P1_PX2; + th.textContent = headerText; + dsHeaderRow.appendChild(th); + }); + dsThead.appendChild(dsHeaderRow); + dsTable.appendChild(dsThead); + const dsTbody = document.createElement('tbody'); + dsTbody.id = ID_PREFIXES.PBS_DS_TBODY + instanceId; + dsTbody.className = CSS_CLASSES.DIVIDE_Y_GRAY_200_DARK_DIVIDE_GRAY_700; + dsTable.appendChild(dsTbody); + dsTableContainer.appendChild(dsTable); + dsSection.appendChild(dsTableContainer); + detailsContainer.appendChild(dsSection); + + const summariesSection = document.createElement('div'); + summariesSection.id = ID_PREFIXES.PBS_SUMMARIES_SECTION + instanceId; + summariesSection.className = `${CSS_CLASSES.GRID} ${CSS_CLASSES.GRID_COLS_1} ${CSS_CLASSES.MD_GRID_COLS_2} ${CSS_CLASSES.LG_GRID_COLS_4} ${CSS_CLASSES.GAP_4}`; + summariesSection.appendChild(_createSummaryCard('backup', 'Backups', pbsInstanceData.backupTasks)); + summariesSection.appendChild(_createSummaryCard('verify', 'Verification', pbsInstanceData.verificationTasks)); + summariesSection.appendChild(_createSummaryCard('sync', 'Sync', pbsInstanceData.syncTasks)); + summariesSection.appendChild(_createSummaryCard('prune', 'Prune/GC', pbsInstanceData.pruneTasks)); + detailsContainer.appendChild(summariesSection); + + const recentBackupTasksSection = document.createElement('div'); + recentBackupTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; + recentBackupTasksSection.dataset.taskType = 'backup'; + recentBackupTasksSection.appendChild(_createTaskTableElement(ID_PREFIXES.PBS_RECENT_BACKUP_TASKS_TABLE + instanceId, 'Backup', 'Guest')); + detailsContainer.appendChild(recentBackupTasksSection); + + const recentVerifyTasksSection = document.createElement('div'); + recentVerifyTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; + recentVerifyTasksSection.dataset.taskType = 'verify'; + recentVerifyTasksSection.appendChild(_createTaskTableElement(ID_PREFIXES.PBS_RECENT_VERIFY_TASKS_TABLE + instanceId, 'Verification', 'Guest/Group')); + detailsContainer.appendChild(recentVerifyTasksSection); + + const recentSyncTasksSection = document.createElement('div'); + recentSyncTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; + recentSyncTasksSection.dataset.taskType = 'sync'; + recentSyncTasksSection.appendChild(_createTaskTableElement(ID_PREFIXES.PBS_RECENT_SYNC_TASKS_TABLE + instanceId, 'Sync', 'Job ID')); + detailsContainer.appendChild(recentSyncTasksSection); + + const recentPruneGcTasksSection = document.createElement('div'); + recentPruneGcTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; + recentPruneGcTasksSection.dataset.taskType = 'prunegc'; + recentPruneGcTasksSection.appendChild(_createTaskTableElement(ID_PREFIXES.PBS_RECENT_PRUNEGC_TASKS_TABLE + instanceId, 'Prune/GC', 'Datastore/Group')); + detailsContainer.appendChild(recentPruneGcTasksSection); + + instanceWrapper.appendChild(detailsContainer); + + const dsTableBodyElement = instanceWrapper.querySelector(`#${ID_PREFIXES.PBS_DS_TBODY}${instanceId}`); + _populateDsTableBody(dsTableBodyElement, pbsInstanceData.datastores, statusText, showDetails); + _populateInstanceTaskSections(detailsContainer, instanceId, pbsInstanceData, statusText, showDetails); + + return instanceWrapper; }; if (instanceWrapper) { detailsContainer = instanceWrapper.querySelector(`#${ID_PREFIXES.PBS_DETAILS}${instanceId}`); instanceTitleElement = instanceWrapper.querySelector('h3'); - if (instanceTitleElement) { - instanceTitleElement.innerHTML = `${_createHealthBadgeHTML(overallHealth, healthTitle)}${instanceName}`; + instanceTitleElement.innerHTML = ''; // Clear existing content + instanceTitleElement.appendChild(_createHealthBadgeHTML(overallHealth, healthTitle)); + instanceTitleElement.appendChild(document.createTextNode(instanceName)); } if (detailsContainer) { @@ -466,87 +659,10 @@ PulseApp.ui.pbs = (() => { _populateInstanceTaskSections(detailsContainer, instanceId, pbsInstance, statusText, showDetails); detailsContainer.classList.toggle(CSS_CLASSES.HIDDEN, !showDetails); } - } else { - instanceWrapper = document.createElement('div'); - instanceWrapper.className = `${CSS_CLASSES.PBS_INSTANCE_SECTION} ${CSS_CLASSES.BORDER_GRAY_200_DARK_BORDER_GRAY_700} ${CSS_CLASSES.ROUNDED} p-4 mb-4 bg-gray-50/30 dark:bg-gray-800/30`; - instanceWrapper.id = instanceElementId; - - const headerDiv = document.createElement('div'); - headerDiv.className = `${CSS_CLASSES.FLEX} ${CSS_CLASSES.JUSTIFY_BETWEEN} ${CSS_CLASSES.ITEMS_CENTER} ${CSS_CLASSES.MB3}`; - instanceTitleElement = document.createElement('h3'); - instanceTitleElement.className = `${CSS_CLASSES.TEXT_LG} ${CSS_CLASSES.FONT_SEMIBOLD} ${CSS_CLASSES.TEXT_GRAY_800_DARK_GRAY_200} ${CSS_CLASSES.FLEX} ${CSS_CLASSES.ITEMS_CENTER}`; - instanceTitleElement.innerHTML = `${_createHealthBadgeHTML(overallHealth, healthTitle)}${instanceName}`; - headerDiv.appendChild(instanceTitleElement); - instanceWrapper.appendChild(headerDiv); - - detailsContainer = document.createElement('div'); - detailsContainer.className = `${CSS_CLASSES.PBS_INSTANCE_DETAILS} ${CSS_CLASSES.SPACE_Y_4} ${showDetails ? '' : CSS_CLASSES.HIDDEN}`; - detailsContainer.id = ID_PREFIXES.PBS_DETAILS + instanceId; - - const dsSection = document.createElement('div'); - dsSection.id = ID_PREFIXES.PBS_DS_SECTION + instanceId; - dsSection.innerHTML = ` -

Datastores

-
- - - - - - - - - - - - - -
NamePathUsedAvailableTotalUsageGC Status
-
`; - detailsContainer.appendChild(dsSection); - - const summariesSection = document.createElement('div'); - summariesSection.id = ID_PREFIXES.PBS_SUMMARIES_SECTION + instanceId; - summariesSection.className = `${CSS_CLASSES.GRID} ${CSS_CLASSES.GRID_COLS_1} ${CSS_CLASSES.MD_GRID_COLS_2} ${CSS_CLASSES.LG_GRID_COLS_4} ${CSS_CLASSES.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); - - const recentBackupTasksSection = document.createElement('div'); - recentBackupTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; - recentBackupTasksSection.dataset.taskType = 'backup'; - recentBackupTasksSection.innerHTML = _createTaskTableHTML(ID_PREFIXES.PBS_RECENT_BACKUP_TASKS_TABLE + instanceId, 'Backup', 'Guest'); - detailsContainer.appendChild(recentBackupTasksSection); - - const recentVerifyTasksSection = document.createElement('div'); - recentVerifyTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; - recentVerifyTasksSection.dataset.taskType = 'verify'; - recentVerifyTasksSection.innerHTML = _createTaskTableHTML(ID_PREFIXES.PBS_RECENT_VERIFY_TASKS_TABLE + instanceId, 'Verification', 'Guest/Group'); - detailsContainer.appendChild(recentVerifyTasksSection); - - const recentSyncTasksSection = document.createElement('div'); - recentSyncTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; - recentSyncTasksSection.dataset.taskType = 'sync'; - recentSyncTasksSection.innerHTML = _createTaskTableHTML(ID_PREFIXES.PBS_RECENT_SYNC_TASKS_TABLE + instanceId, 'Sync', 'Job ID'); - detailsContainer.appendChild(recentSyncTasksSection); - - const recentPruneGcTasksSection = document.createElement('div'); - recentPruneGcTasksSection.className = CSS_CLASSES.PBS_TASK_SECTION; - recentPruneGcTasksSection.dataset.taskType = 'prunegc'; - recentPruneGcTasksSection.innerHTML = _createTaskTableHTML(ID_PREFIXES.PBS_RECENT_PRUNEGC_TASKS_TABLE + instanceId, 'Prune/GC', 'Datastore/Group'); - detailsContainer.appendChild(recentPruneGcTasksSection); - - instanceWrapper.appendChild(detailsContainer); + instanceWrapper = _createPbsInstanceElement(pbsInstance, instanceId, instanceName, overallHealth, healthTitle, showDetails, statusText); container.appendChild(instanceWrapper); - - dsTableBody = instanceWrapper.querySelector(`#${ID_PREFIXES.PBS_DS_TBODY}${instanceId}`); - _populateDsTableBody(dsTableBody, pbsInstance.datastores, statusText, showDetails); - _populateInstanceTaskSections(detailsContainer, instanceId, pbsInstance, statusText, showDetails); } - }); container.querySelectorAll(`.${CSS_CLASSES.PBS_INSTANCE_SECTION}`).forEach(el => {