Implement simplified UI/UX with health scores and grouped validations
This commit is contained in:
@@ -1278,6 +1278,336 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Simplified View Styles */
|
||||
.view-mode-toggle {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: 2px solid #e9ecef;
|
||||
background: white;
|
||||
color: #6c757d;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.mode-btn:hover:not(.active) {
|
||||
background: #f8f9fa;
|
||||
border-color: #007bff;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.health-summary {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.summary-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 2px solid;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.stat-card.excellent {
|
||||
border-color: #28a745;
|
||||
background: #f8fff9;
|
||||
}
|
||||
|
||||
.stat-card.good {
|
||||
border-color: #28a745;
|
||||
background: #f8fff9;
|
||||
}
|
||||
|
||||
.stat-card.medium {
|
||||
border-color: #ffc107;
|
||||
background: #fffdf5;
|
||||
}
|
||||
|
||||
.stat-card.poor {
|
||||
border-color: #fd7e14;
|
||||
background: #fff8f5;
|
||||
}
|
||||
|
||||
.stat-card.critical {
|
||||
border-color: #dc3545;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.pod-cards-container {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.pod-health-card {
|
||||
background: white;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pod-health-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.pod-health-card.excellent {
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.pod-health-card.good {
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.pod-health-card.medium {
|
||||
border-color: #ffc107;
|
||||
}
|
||||
|
||||
.pod-health-card.poor {
|
||||
border-color: #fd7e14;
|
||||
}
|
||||
|
||||
.pod-health-card.critical {
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.pod-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pod-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.pod-namespace {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.health-score {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.health-score-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.health-status {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.resource-display {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.resource-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.resource-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.resource-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pod-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #007bff;
|
||||
background: white;
|
||||
color: #007bff;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.technical-details {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.technical-details.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.technical-card {
|
||||
background: white;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.technical-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.validation-groups {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.validation-group {
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.validation-group.critical {
|
||||
background: #fff5f5;
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
|
||||
.validation-group.warning {
|
||||
background: #fffdf5;
|
||||
border-left-color: #ffc107;
|
||||
}
|
||||
|
||||
.validation-group.info {
|
||||
background: #f8f9fa;
|
||||
border-left-color: #6c757d;
|
||||
}
|
||||
|
||||
.validation-group-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.validation-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.validation-item {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.validation-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.oc-command {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #218838;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1291,6 +1621,10 @@
|
||||
<span class="nav-icon">🏠</span>
|
||||
<span class="nav-text">Cluster Health</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-section="simplified-view">
|
||||
<span class="nav-icon">🎯</span>
|
||||
<span class="nav-text">Simplified View</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-section="workload-categories">
|
||||
<span class="nav-icon">📊</span>
|
||||
<span class="nav-text">Workload Analysis</span>
|
||||
@@ -1561,6 +1895,70 @@
|
||||
<div id="vpaList"></div>
|
||||
</div>
|
||||
|
||||
<!-- Simplified View -->
|
||||
<div class="card" id="simplifiedViewCard" style="display: none;">
|
||||
<h2>🎯 Simplified Pod Health View</h2>
|
||||
|
||||
<!-- View Mode Toggle -->
|
||||
<div class="view-mode-toggle">
|
||||
<button class="mode-btn active" id="simpleModeBtn" onclick="switchViewMode('simple')">Simple Mode</button>
|
||||
<button class="mode-btn" id="technicalModeBtn" onclick="switchViewMode('technical')">Technical Mode</button>
|
||||
</div>
|
||||
|
||||
<!-- Health Summary -->
|
||||
<div class="health-summary" id="healthSummary">
|
||||
<div class="summary-stats">
|
||||
<div class="stat-card excellent">
|
||||
<div class="stat-icon">✅</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" id="excellentCount">0</div>
|
||||
<div class="stat-label">Excellent</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card good">
|
||||
<div class="stat-icon">✅</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" id="goodCount">0</div>
|
||||
<div class="stat-label">Good</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card medium">
|
||||
<div class="stat-icon">🟡</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" id="mediumCount">0</div>
|
||||
<div class="stat-label">Medium</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card poor">
|
||||
<div class="stat-icon">🟠</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" id="poorCount">0</div>
|
||||
<div class="stat-label">Poor</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card critical">
|
||||
<div class="stat-icon">🔴</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" id="criticalCount">0</div>
|
||||
<div class="stat-label">Critical</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pod Health Cards (Simple Mode) -->
|
||||
<div id="simplePodCards" class="pod-cards-container">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</div>
|
||||
|
||||
<!-- Technical Details (Technical Mode) -->
|
||||
<div id="technicalDetails" class="technical-details hidden">
|
||||
<div id="technicalPodCards" class="technical-cards-container">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div class="loading hidden" id="loading">
|
||||
<p>Loading data...</p>
|
||||
@@ -3422,6 +3820,7 @@
|
||||
// Show selected section
|
||||
const sectionMap = {
|
||||
'dashboard': 'validationsCard',
|
||||
'simplified-view': 'simplifiedViewCard',
|
||||
'historical-analysis': 'historicalCard',
|
||||
'smart-recommendations': 'smartRecommendationsCard',
|
||||
'workload-categories': 'workloadCategoriesCard',
|
||||
@@ -3441,21 +3840,208 @@
|
||||
case 'dashboard':
|
||||
loadClusterHealth();
|
||||
break;
|
||||
case 'historical-analysis':
|
||||
loadHistoricalValidations();
|
||||
break;
|
||||
case 'smart-recommendations':
|
||||
loadSmartRecommendations();
|
||||
break;
|
||||
case 'workload-categories':
|
||||
loadWorkloadCategories();
|
||||
break;
|
||||
case 'vpa-recommendations':
|
||||
loadVPARecommendations();
|
||||
break;
|
||||
case 'simplified-view':
|
||||
loadSimplifiedView();
|
||||
break;
|
||||
case 'historical-analysis':
|
||||
loadHistoricalValidations();
|
||||
break;
|
||||
case 'smart-recommendations':
|
||||
loadSmartRecommendations();
|
||||
break;
|
||||
case 'workload-categories':
|
||||
loadWorkloadCategories();
|
||||
break;
|
||||
case 'vpa-recommendations':
|
||||
loadVPARecommendations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified View Functions
|
||||
let currentViewMode = 'simple';
|
||||
let healthScoresData = null;
|
||||
|
||||
async function loadSimplifiedView() {
|
||||
try {
|
||||
showLoading();
|
||||
|
||||
const response = await fetch('/api/v1/pod-health-scores');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
healthScoresData = await response.json();
|
||||
updateHealthSummary(healthScoresData.summary);
|
||||
updatePodCards(healthScoresData.pods);
|
||||
|
||||
hideLoading();
|
||||
} catch (error) {
|
||||
console.error('Error loading simplified view:', error);
|
||||
showError('Error loading simplified view: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updateHealthSummary(summary) {
|
||||
document.getElementById('excellentCount').textContent = summary.excellent;
|
||||
document.getElementById('goodCount').textContent = summary.good;
|
||||
document.getElementById('mediumCount').textContent = summary.medium;
|
||||
document.getElementById('poorCount').textContent = summary.poor;
|
||||
document.getElementById('criticalCount').textContent = summary.critical;
|
||||
}
|
||||
|
||||
function updatePodCards(pods) {
|
||||
const container = document.getElementById('simplePodCards');
|
||||
const technicalContainer = document.getElementById('technicalPodCards');
|
||||
|
||||
let simpleHtml = '';
|
||||
let technicalHtml = '';
|
||||
|
||||
pods.forEach(pod => {
|
||||
// Simple mode card
|
||||
simpleHtml += `
|
||||
<div class="pod-health-card ${pod.status_color}">
|
||||
<div class="pod-header">
|
||||
<div>
|
||||
<div class="pod-name">${pod.pod_name}</div>
|
||||
<div class="pod-namespace">${pod.namespace}</div>
|
||||
</div>
|
||||
<div class="health-score">
|
||||
<span class="health-score-value">${pod.health_score}/10</span>
|
||||
<span class="health-status">${pod.health_status}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-display">
|
||||
<div class="resource-item">
|
||||
<span class="resource-icon">⚡</span>
|
||||
<div class="resource-text">
|
||||
<div class="resource-label">CPU</div>
|
||||
<div class="resource-value">${pod.cpu_display} ${pod.cpu_status}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<span class="resource-icon">💾</span>
|
||||
<div class="resource-text">
|
||||
<div class="resource-label">Memory</div>
|
||||
<div class="resource-value">${pod.memory_display} ${pod.memory_status}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pod-actions">
|
||||
<button class="action-btn primary" onclick="viewPodDetails('${pod.pod_name}')">View Details</button>
|
||||
${pod.available_actions.includes('fix_cpu_ratio') ? '<button class="action-btn" onclick="fixPodIssue(\'' + pod.pod_name + '\', \'cpu_ratio\')">Fix CPU Ratio</button>' : ''}
|
||||
${pod.available_actions.includes('add_requests') ? '<button class="action-btn" onclick="fixPodIssue(\'' + pod.pod_name + '\', \'add_requests\')">Add Requests</button>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Technical mode card
|
||||
technicalHtml += `
|
||||
<div class="technical-card">
|
||||
<div class="technical-header">
|
||||
<div>
|
||||
<div class="pod-name">${pod.pod_name}</div>
|
||||
<div class="pod-namespace">${pod.namespace}</div>
|
||||
</div>
|
||||
<div class="health-score">
|
||||
<span class="health-score-value">${pod.health_score}/10</span>
|
||||
<span class="health-status">${pod.health_status}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="validation-groups">
|
||||
${pod.critical_issues.length > 0 ? `
|
||||
<div class="validation-group critical">
|
||||
<div class="validation-group-title">🔴 Critical Issues (${pod.critical_issues.length})</div>
|
||||
<ul class="validation-list">
|
||||
${pod.critical_issues.map(issue => `<li class="validation-item">${issue}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${pod.warnings.length > 0 ? `
|
||||
<div class="validation-group warning">
|
||||
<div class="validation-group-title">🟡 Warnings (${pod.warnings.length})</div>
|
||||
<ul class="validation-list">
|
||||
${pod.warnings.map(warning => `<li class="validation-item">${warning}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${pod.info_items.length > 0 ? `
|
||||
<div class="validation-group info">
|
||||
<div class="validation-group-title">🔵 Information (${pod.info_items.length})</div>
|
||||
<ul class="validation-list">
|
||||
${pod.info_items.map(info => `<li class="validation-item">${info}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${pod.oc_commands.length > 0 ? `
|
||||
<div class="oc-commands">
|
||||
<h4>Available Commands:</h4>
|
||||
${pod.oc_commands.map(cmd => `
|
||||
<div class="oc-command">
|
||||
${cmd}
|
||||
<button class="copy-btn" onclick="copyToClipboard('${cmd.replace(/'/g, "\\'")}')">Copy</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = simpleHtml;
|
||||
technicalContainer.innerHTML = technicalHtml;
|
||||
}
|
||||
|
||||
function switchViewMode(mode) {
|
||||
currentViewMode = mode;
|
||||
|
||||
// Update button states
|
||||
document.getElementById('simpleModeBtn').classList.toggle('active', mode === 'simple');
|
||||
document.getElementById('technicalModeBtn').classList.toggle('active', mode === 'technical');
|
||||
|
||||
// Show/hide appropriate content
|
||||
document.getElementById('simplePodCards').style.display = mode === 'simple' ? 'block' : 'none';
|
||||
document.getElementById('technicalDetails').classList.toggle('hidden', mode === 'simple');
|
||||
}
|
||||
|
||||
function viewPodDetails(podName) {
|
||||
// Switch to technical mode and scroll to pod
|
||||
switchViewMode('technical');
|
||||
const podCard = document.querySelector(`[data-pod="${podName}"]`);
|
||||
if (podCard) {
|
||||
podCard.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
function fixPodIssue(podName, issueType) {
|
||||
// This would implement the fix logic
|
||||
alert(`Fixing ${issueType} for pod ${podName}`);
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// Show success feedback
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Copied!';
|
||||
btn.style.background = '#28a745';
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
btn.style.background = '#28a745';
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
alert('Failed to copy to clipboard');
|
||||
});
|
||||
}
|
||||
|
||||
// Add click handlers for navigation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
|
||||
Reference in New Issue
Block a user