From 636feb5b2a4df9f0df959085e3f1333d8085e2f2 Mon Sep 17 00:00:00 2001 From: andersonid Date: Thu, 16 Oct 2025 14:57:19 -0300 Subject: [PATCH] feat: implement phase 2 - reorganize accordion layout with pod cards, specific recommendations and action buttons --- app/static/index.html | 412 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 393 insertions(+), 19 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index 7af6f28..a94deb1 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -929,6 +929,231 @@ 50% { opacity: 0.7; } } + /* New Pod Card Styles */ + .pod-card { + background-color: var(--pf-global--BackgroundColor--200); + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: 8px; + margin-bottom: 16px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .pod-card-header { + background: linear-gradient(135deg, var(--pf-global--BackgroundColor--300) 0%, var(--pf-global--BackgroundColor--200) 100%); + padding: 16px 20px; + border-bottom: 1px solid var(--pf-global--BorderColor--200); + display: flex; + align-items: center; + justify-content: space-between; + } + + .pod-name-section { + display: flex; + align-items: center; + gap: 12px; + } + + .pod-name { + font-size: 18px; + font-weight: 600; + color: var(--pf-global--Color--100); + margin: 0; + } + + .pod-severity-badge { + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + } + + .pod-severity-badge.warning { + background-color: var(--pf-global--warning-color--100); + color: var(--pf-global--Color--100); + } + + .pod-severity-badge.error { + background-color: var(--pf-global--danger-color--100); + color: var(--pf-global--Color--100); + } + + .pod-severity-badge.info { + background-color: var(--pf-global--info-color--100); + color: var(--pf-global--Color--100); + } + + .pod-stats { + color: var(--pf-global--Color--200); + font-size: 14px; + } + + .pod-card-body { + padding: 20px; + } + + .resource-section { + margin-bottom: 24px; + } + + .resource-section:last-of-type { + margin-bottom: 0; + } + + .resource-section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--pf-global--BorderColor--300); + } + + .resource-type-title { + font-size: 16px; + font-weight: 600; + color: var(--pf-global--Color--100); + margin: 0; + } + + .resource-count { + background-color: var(--pf-global--BackgroundColor--300); + color: var(--pf-global--Color--200); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + } + + .resource-issues { + display: flex; + flex-direction: column; + gap: 12px; + } + + .resource-issue-item { + background-color: var(--pf-global--BackgroundColor--100); + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: 6px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 8px; + } + + .issue-details { + display: flex; + align-items: center; + gap: 16px; + } + + .issue-ratio { + background-color: var(--pf-global--warning-color--100); + color: var(--pf-global--Color--100); + padding: 4px 8px; + border-radius: 4px; + font-weight: 600; + font-size: 14px; + } + + .issue-values { + color: var(--pf-global--Color--200); + font-size: 14px; + font-family: 'Courier New', monospace; + } + + .issue-recommendation { + background-color: var(--pf-global--success-color--100); + color: var(--pf-global--Color--100); + padding: 12px; + border-radius: 4px; + font-size: 14px; + display: flex; + align-items: center; + gap: 8px; + } + + .issue-recommendation::before { + content: "💡"; + font-size: 16px; + } + + .other-issues-section { + margin-bottom: 24px; + } + + .other-issues { + display: flex; + flex-direction: column; + gap: 8px; + } + + .other-issue-item { + background-color: var(--pf-global--BackgroundColor--100); + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: 6px; + padding: 12px; + } + + .issue-type { + font-weight: 600; + color: var(--pf-global--Color--100); + font-size: 14px; + display: block; + margin-bottom: 4px; + } + + .issue-message { + color: var(--pf-global--Color--200); + font-size: 14px; + margin: 0; + } + + .pod-actions { + display: flex; + gap: 12px; + margin-top: 20px; + padding-top: 16px; + border-top: 1px solid var(--pf-global--BorderColor--200); + } + + .action-btn { + padding: 10px 16px; + border-radius: 6px; + border: none; + font-size: 14px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s ease; + text-decoration: none; + } + + .apply-fix-btn { + background-color: var(--pf-global--primary-color--100); + color: var(--pf-global--Color--100); + } + + .apply-fix-btn:hover { + background-color: var(--pf-global--primary-color--200); + transform: translateY(-1px); + } + + .view-yaml-btn { + background-color: transparent; + color: var(--pf-global--Color--200); + border: 1px solid var(--pf-global--BorderColor--200); + } + + .view-yaml-btn:hover { + background-color: var(--pf-global--BackgroundColor--300); + color: var(--pf-global--Color--100); + border-color: var(--pf-global--BorderColor--100); + } + .workload-issues-list { display: flex; flex-direction: column; @@ -2432,32 +2657,181 @@ return; } + // Group validations by pod for better organization + const podGroups = groupValidationsByPod(data.validations); + 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('')} + ${Object.values(podGroups).map(pod => createPodCard(pod)).join('')}
`; container.innerHTML = issuesHTML; } + + function groupValidationsByPod(validations) { + const groups = {}; + validations.forEach(validation => { + const podName = validation.pod_name; + if (!groups[podName]) { + groups[podName] = { + pod_name: podName, + validations: [], + severity: 'info', + cpuIssues: [], + memoryIssues: [], + otherIssues: [] + }; + } + groups[podName].validations.push(validation); + + // Categorize issues + if (validation.message && validation.message.includes('CPU')) { + groups[podName].cpuIssues.push(validation); + } else if (validation.message && validation.message.includes('Memory')) { + groups[podName].memoryIssues.push(validation); + } else { + groups[podName].otherIssues.push(validation); + } + + // Set highest severity + if (validation.severity === 'error' || + (validation.severity === 'warning' && groups[podName].severity !== 'error')) { + groups[podName].severity = validation.severity; + } + }); + return groups; + } + + function createPodCard(pod) { + return ` +
+
+
+

${pod.pod_name}

+ + ${pod.severity.toUpperCase()} + +
+
+ ${pod.validations.length} issues +
+
+
+ ${createResourceSection('CPU', pod.cpuIssues)} + ${createResourceSection('Memory', pod.memoryIssues)} + ${createOtherIssuesSection(pod.otherIssues)} +
+ + +
+
+
+ `; + } + + function createResourceSection(resourceType, issues) { + if (!issues || issues.length === 0) return ''; + + const recommendations = issues.map(issue => generateSpecificRecommendation(issue, resourceType)).join(''); + + return ` +
+
+

${resourceType} Issues

+ ${issues.length} issue${issues.length > 1 ? 's' : ''} +
+
+ ${issues.map(issue => ` +
+
+ ${extractRatio(issue.message)} + ${extractValues(issue.message)} +
+
+ ${generateSpecificRecommendation(issue, resourceType)} +
+
+ `).join('')} +
+
+ `; + } + + function createOtherIssuesSection(issues) { + if (!issues || issues.length === 0) return ''; + + return ` +
+
+

Other Issues

+ ${issues.length} issue${issues.length > 1 ? 's' : ''} +
+
+ ${issues.map(issue => ` +
+ ${issue.validation_type || 'Issue'} +

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

+
+ `).join('')} +
+
+ `; + } + + function extractRatio(message) { + const ratioMatch = message.match(/(\d+\.?\d*):1/); + return ratioMatch ? `${ratioMatch[1]}:1` : 'N/A'; + } + + function extractValues(message) { + const requestMatch = message.match(/Request: ([\d\.]+[mMi]?)/); + const limitMatch = message.match(/Limit: ([\d\.]+[mMi]?)/); + const request = requestMatch ? requestMatch[1] : 'N/A'; + const limit = limitMatch ? limitMatch[1] : 'N/A'; + return `${request} → ${limit}`; + } + + function generateSpecificRecommendation(issue, resourceType) { + const message = issue.message || ''; + const ratioMatch = message.match(/(\d+\.?\d*):1/); + const requestMatch = message.match(/Request: ([\d\.]+[mMi]?)/); + + if (!ratioMatch || !requestMatch) { + return `Set ${resourceType} limit to 3x the request value`; + } + + const currentRatio = parseFloat(ratioMatch[1]); + const requestValue = parseFloat(requestMatch[1]); + const unit = requestMatch[1].replace(/[\d\.]/g, ''); + + if (currentRatio > 3.0) { + const recommendedLimit = Math.round(requestValue * 3.0); + const recommendedLimitStr = recommendedLimit + unit; + return `Set ${resourceType} limit to ${recommendedLimitStr} (3:1 ratio)`; + } else { + return `${resourceType} ratio is acceptable (${ratioMatch[1]}:1)`; + } + } + + // Action button functions + function applyResourceFix(podName) { + console.log(`Applying resource fix for pod: ${podName}`); + // TODO: Implement actual fix application + alert(`Resource fix for ${podName} would be applied here.\n\nThis feature will:\n- Generate YAML patch\n- Apply to cluster\n- Verify changes\n\nImplementation coming in next phase.`); + } + + function viewYamlPatch(podName) { + console.log(`Viewing YAML patch for pod: ${podName}`); + // TODO: Implement YAML patch generation + alert(`YAML patch for ${podName} would be shown here.\n\nThis feature will:\n- Generate strategic merge patch\n- Show before/after comparison\n- Allow copy to clipboard\n\nImplementation coming in next phase.`); + } // Dashboard Charts Functions async function loadDashboardCharts() {