From ff2bafe6213d49dc19d16af0c13a5204d60ac00c Mon Sep 17 00:00:00 2001 From: andersonid Date: Fri, 3 Oct 2025 15:48:26 -0300 Subject: [PATCH] Refactor: convert Requests & Limits to accordion interface with pre-loaded data --- app/static/index.html | 481 ++++++++++++++++++++++++------------------ 1 file changed, 277 insertions(+), 204 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index fd8196b..e4f769e 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -732,6 +732,127 @@ background-color: rgba(255, 255, 255, 0.02); } + /* Workload Accordion Styles */ + .workloads-accordion { + margin-top: 16px; + } + + .workload-accordion-item { + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: 8px; + margin-bottom: 12px; + background-color: var(--pf-global--BackgroundColor--100); + overflow: hidden; + } + + .workload-accordion-header { + padding: 16px 20px; + background-color: var(--pf-global--BackgroundColor--200); + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + transition: background-color 0.2s ease; + } + + .workload-accordion-header:hover { + background-color: var(--pf-global--BackgroundColor--300); + } + + .workload-accordion-title { + display: flex; + align-items: center; + gap: 12px; + } + + .workload-accordion-icon { + transition: transform 0.2s ease; + color: var(--pf-global--Color--400); + } + + .workload-accordion-stats { + display: flex; + align-items: center; + gap: 16px; + } + + .workload-stat { + display: flex; + align-items: center; + gap: 6px; + color: var(--pf-global--Color--300); + font-size: 14px; + } + + .workload-stat i { + color: var(--pf-global--Color--400); + } + + .workload-accordion-content { + background-color: var(--pf-global--BackgroundColor--100); + border-top: 1px solid var(--pf-global--BorderColor--200); + } + + .workload-issues-container { + padding: 20px; + } + + .workload-issues-list { + display: flex; + flex-direction: column; + gap: 12px; + } + + .workload-issue-item { + background-color: var(--pf-global--BackgroundColor--200); + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: 6px; + padding: 16px; + } + + .workload-issue-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; + } + + .workload-issue-title { + font-weight: 600; + color: var(--pf-global--Color--100); + flex: 1; + } + + .workload-issue-pod { + color: var(--pf-global--Color--300); + font-size: 14px; + background-color: var(--pf-global--BackgroundColor--300); + padding: 4px 8px; + border-radius: 4px; + } + + .workload-issue-content { + margin-top: 8px; + } + + .workload-issue-message { + color: var(--pf-global--Color--200); + margin: 0 0 8px 0; + line-height: 1.5; + } + + .workload-issue-recommendation { + background-color: var(--pf-global--BackgroundColor--300); + border-left: 3px solid var(--pf-global--info-color--100); + padding: 12px; + border-radius: 4px; + margin-top: 8px; + } + + .workload-issue-recommendation strong { + color: var(--pf-global--info-color--100); + } + /* Modal Styles */ .modal { display: block; @@ -1591,15 +1712,133 @@ currentData = { validations: validationsData }; - // Update workloads table + // Update workloads accordion updateWorkloadsTable(validationsData); + // Pre-load all workload details + await preloadAllWorkloadDetails(); + } catch (error) { console.error('Error loading requests & limits data:', error); showError('workloads-table-container', 'Failed to load workload data'); } } + async function preloadAllWorkloadDetails() { + if (!window.workloadsData) return; + + // Load details for all namespaces in parallel + const loadPromises = window.workloadsData.map(async (namespace, index) => { + try { + const response = await fetch(`/api/v1/validations/namespace/${namespace.namespace}`); + const data = await response.json(); + + // Store the data for when accordion is opened + window.workloadDetails = window.workloadDetails || {}; + window.workloadDetails[namespace.namespace] = data; + + } catch (error) { + console.error(`Error loading details for namespace ${namespace.namespace}:`, error); + window.workloadDetails = window.workloadDetails || {}; + window.workloadDetails[namespace.namespace] = { error: 'Failed to load details' }; + } + }); + + await Promise.all(loadPromises); + } + + function toggleWorkloadIssues(index) { + const content = document.getElementById(`workload-content-${index}`); + const icon = document.getElementById(`workload-icon-${index}`); + + if (content.style.display === 'none') { + // Expand accordion + content.style.display = 'block'; + icon.classList.remove('fa-chevron-right'); + icon.classList.add('fa-chevron-down'); + + // Load issues if not already loaded + loadWorkloadIssues(index); + } else { + // Collapse accordion + content.style.display = 'none'; + icon.classList.remove('fa-chevron-down'); + icon.classList.add('fa-chevron-right'); + } + } + + function loadWorkloadIssues(index) { + const namespace = window.workloadsData[index]; + const container = document.getElementById(`workload-issues-${index}`); + + if (!namespace) return; + + // Check if we already have the data + if (window.workloadDetails && window.workloadDetails[namespace.namespace]) { + const data = window.workloadDetails[namespace.namespace]; + + if (data.error) { + container.innerHTML = ` +
+ + ${data.error} +
+ `; + return; + } + + // Display the issues + displayWorkloadIssues(container, data, namespace); + return; + } + + // If data not available, show loading + container.innerHTML = ` +
+
+ Loading issues... +
+ `; + } + + function displayWorkloadIssues(container, data, namespace) { + if (!data.validations || data.validations.length === 0) { + container.innerHTML = ` +
+ +

No issues found for this namespace

+
+ `; + return; + } + + const issuesHTML = ` +
+ ${data.validations.map(validation => ` +
+
+ + ${validation.severity.toUpperCase()} + + ${validation.validation_type || validation.title || 'Resource Issue'} + ${validation.pod_name} +
+
+

${validation.message || validation.description || 'No description available'}

+ ${validation.recommendation || validation.action_required ? ` +
+ Recommendation: ${validation.recommendation || validation.action_required} +
+ ` : ''} +
+
+ `).join('')} +
+ `; + + container.innerHTML = issuesHTML; + } + // Load settings async function loadSettings() { try { @@ -2430,48 +2669,52 @@

No Issues Found

All workloads are properly configured

- - `; + + `; return; } - const tableHTML = ` - - - - - - - - - - - - ${namespaces.map(namespace => ` - - - - - - - - `).join('')} - -
NamespacePodsIssuesStatusActions
+ // Create accordion HTML + const accordionHTML = ` +
+ ${namespaces.map((namespace, index) => ` +
+
+
+ ${namespace.namespace} -
${namespace.pods.size}${namespace.validations.length} + +
+ + + ${namespace.pods.size} pods + + + + ${namespace.validations.length} issues + ${getSeverityText(namespace)} -
- -
+ + + + + `).join('')} + `; - container.innerHTML = tableHTML; + container.innerHTML = accordionHTML; + + // Store namespace data globally for accordion functions + window.workloadsData = namespaces; } function updateHistoricalWorkloads(data) { @@ -3122,177 +3365,7 @@ `; } - function analyzeNamespace(namespaceName) { - if (!currentData || !currentData.validations || !currentData.validations.validations) return; - - // Filter validations for this namespace - const namespaceValidations = currentData.validations.validations.filter(v => v.namespace === namespaceName); - if (namespaceValidations.length === 0) return; - - // Group by pod - const podGroups = {}; - namespaceValidations.forEach(validation => { - const podName = validation.pod_name; - if (!podGroups[podName]) { - podGroups[podName] = { - pod_name: podName, - namespace: namespaceName, - phase: 'Running', // Default phase - node_name: 'Unknown', // Default node - containers: [], - validations: [] - }; - } - podGroups[podName].validations.push(validation); - }); - - // Create namespace object for compatibility - const namespace = { - namespace: namespaceName, - pods: podGroups, - validations: namespaceValidations, - severity_breakdown: { - error: namespaceValidations.filter(v => v.severity === 'error').length, - warning: namespaceValidations.filter(v => v.severity === 'warning').length, - info: namespaceValidations.filter(v => v.severity === 'info').length - } - }; - - // Show details in modal - showNamespaceDetails(namespaceName, namespace); - } - function showNamespaceDetails(namespaceName, namespace) { - // Create modal if it doesn't exist - let modal = document.getElementById('namespaceModal'); - if (!modal) { - modal = document.createElement('div'); - modal.id = 'namespaceModal'; - modal.className = 'modal'; - modal.innerHTML = ` - - `; - document.body.appendChild(modal); - - // Add close functionality - modal.querySelector('.close').onclick = () => modal.style.display = 'none'; - modal.onclick = (e) => { - if (e.target === modal) modal.style.display = 'none'; - }; - } - - // Use the passed namespace object - if (!namespace) return; - - let content = ` -
-
-

📊 Summary

-
-
- Pods: ${Object.keys(namespace.pods || {}).length} -
-
- Total Issues: ${namespace.validations?.length || 0} -
-
- Errors: ${namespace.severity_breakdown?.error || 0} -
-
- Warnings: ${namespace.severity_breakdown?.warning || 0} -
-
-
-
-

🔍 Pod Analysis

- `; - - // Add details for each pod - Object.values(namespace.pods || {}).forEach(pod => { - content += ` -
-

📦 ${pod.pod_name}

-
-
Status: ${pod.phase}
-
Node: ${pod.node_name}
-
-
-
Containers:
- `; - - pod.containers.forEach(container => { - const hasRequests = Object.keys(container.resources?.requests || {}).length > 0; - const hasLimits = Object.keys(container.resources?.limits || {}).length > 0; - - content += ` -
-
${container.name}
-

Image: ${container.image}

-
-
- Requests: - ${hasRequests ? - `${JSON.stringify(container.resources.requests)}` : - '❌ Not defined' - } -
-
- Limits: - ${hasLimits ? - `${JSON.stringify(container.resources.limits)}` : - '❌ Not defined' - } -
-
-
- `; - }); - - content += ` -
-
-
Issues Found:
- `; - - if (pod.validations && pod.validations.length > 0) { - pod.validations.forEach(validation => { - const severityClass = validation.severity === 'error' ? 'danger' : - validation.severity === 'warning' ? 'warning' : 'info'; - const severityColor = validation.severity === 'error' ? 'var(--pf-global--danger-color--100)' : - validation.severity === 'warning' ? 'var(--pf-global--warning-color--100)' : 'var(--pf-global--info-color--100)'; - - content += ` -
-

${validation.validation_type || validation.title || 'Issue'}: ${validation.message || validation.description}

-

Recommendation: ${validation.recommendation || validation.action_required || 'No specific recommendation available'}

-
- `; - }); - } else { - content += `

✅ No issues found for this pod.

`; - } - - content += ` -
-
- `; - }); - - content += ` -
-
- `; - - // Populate and show modal - document.getElementById('modalBody').innerHTML = content; - modal.style.display = 'block'; - } function getSeverityClass(namespace) { const breakdown = namespace.severity_breakdown || {};