Fix API endpoint - use correct /api/v1/cluster/status

This commit is contained in:
2025-09-30 12:14:17 -03:00
parent 883c50a104
commit af42204897
2 changed files with 987 additions and 1 deletions

View File

@@ -756,7 +756,7 @@
async function loadDashboard() {
try {
showLoading();
const response = await fetch('/api/cluster/health');
const response = await fetch('/api/v1/cluster/status');
const data = await response.json();
currentData = data;
updateDashboard(data);

View File

@@ -0,0 +1,986 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenShift Resource Governance Tool</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.sidebar {
width: 250px;
background: #2c3e50;
color: white;
position: fixed;
height: 100vh;
left: 0;
top: 0;
z-index: 1000;
overflow-y: auto;
transition: transform 0.3s ease;
}
.sidebar-header {
padding: 1.5rem 1rem;
border-bottom: 1px solid #34495e;
background: #34495e;
}
.sidebar-header h2 {
font-size: 1.2rem;
font-weight: 600;
}
.sidebar-nav {
padding: 1rem 0;
}
.nav-item {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
color: #bdc3c7;
text-decoration: none;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.nav-item:hover {
background: #34495e;
color: white;
border-left-color: #3498db;
}
.nav-item.active {
background: #3498db;
color: white;
border-left-color: #2980b9;
}
.nav-divider {
height: 1px;
background-color: #34495e;
margin: 0.5rem 1rem;
}
.nav-icon {
font-size: 1.2rem;
margin-right: 0.75rem;
width: 20px;
text-align: center;
}
.nav-text {
font-weight: 500;
}
/* Main Content */
.main-content {
flex: 1;
margin-left: 250px;
min-height: 100vh;
}
/* Export Button */
.export-button {
position: fixed;
top: 1rem;
right: 1rem;
background: #27ae60;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
z-index: 1001;
transition: background 0.3s ease;
}
.export-button:hover {
background: #229954;
}
/* Header */
.header {
background: white;
padding: 2rem;
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 2rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 0.5rem;
}
/* Content */
.content {
padding: 2rem;
}
/* Cards */
.card {
background: white;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e0e0e0;
}
.card h2 {
font-size: 1.5rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 1rem;
}
.card h3 {
font-size: 1.2rem;
font-weight: 600;
color: #34495e;
margin-bottom: 0.75rem;
}
/* Metrics Grid */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.metric-card {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
border-left: 4px solid #3498db;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 0.5rem;
}
.metric-label {
font-size: 0.9rem;
color: #7f8c8d;
font-weight: 500;
}
.metric-card.warning {
border-left-color: #f39c12;
}
.metric-card.error {
border-left-color: #e74c3c;
}
.metric-card.success {
border-left-color: #27ae60;
}
/* Status Overview */
.status-overview {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
}
.status-icon {
font-size: 3rem;
}
.status-content h3 {
margin-bottom: 0.5rem;
font-size: 1.5rem;
}
.status-content p {
color: #7f8c8d;
font-size: 1rem;
}
/* Problem Summary Table */
.problem-summary {
margin-bottom: 2rem;
}
.filters {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
align-items: center;
flex-wrap: wrap;
}
.filter-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-group label {
font-weight: 500;
color: #34495e;
}
.filter-group select,
.filter-group input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.filter-checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-checkbox input[type="checkbox"] {
width: 16px;
height: 16px;
}
.apply-filters-btn {
background: #3498db;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background 0.3s ease;
}
.apply-filters-btn:hover {
background: #2980b9;
}
/* Table Styles */
.table-container {
overflow-x: auto;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
th {
background: #f8f9fa;
padding: 1rem;
text-align: left;
font-weight: 600;
color: #2c3e50;
border-bottom: 2px solid #e0e0e0;
}
td {
padding: 1rem;
border-bottom: 1px solid #f0f0f0;
}
tr:hover {
background: #f8f9fa;
}
/* Severity Badges */
.severity-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
}
.severity-critical {
background: #ffebee;
color: #c62828;
}
.severity-error {
background: #ffebee;
color: #d32f2f;
}
.severity-warning {
background: #fff3e0;
color: #f57c00;
}
.severity-info {
background: #e3f2fd;
color: #1976d2;
}
/* Action Buttons */
.action-buttons {
display: flex;
gap: 0.5rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: #3498db;
color: white;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-success {
background: #27ae60;
color: white;
}
.btn-success:hover {
background: #229954;
}
.btn-warning {
background: #f39c12;
color: white;
}
.btn-warning:hover {
background: #e67e22;
}
.btn-sm {
padding: 0.25rem 0.75rem;
font-size: 0.8rem;
}
/* Quick Actions */
.quick-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
flex-wrap: wrap;
}
.quick-action-btn {
background: #34495e;
color: white;
border: none;
padding: 1rem 1.5rem;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.75rem;
}
.quick-action-btn:hover {
background: #2c3e50;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* Loading States */
.loading {
text-align: center;
padding: 2rem;
color: #7f8c8d;
}
.loading::after {
content: '';
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.main-content {
margin-left: 0;
}
.metrics-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.filters {
flex-direction: column;
align-items: stretch;
}
.quick-actions {
flex-direction: column;
}
}
</style>
</head>
<body>
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<h2>Resource Governance</h2>
</div>
<nav class="sidebar-nav">
<a href="#" class="nav-item active" data-section="dashboard">
<span class="nav-icon">🏠</span>
<span class="nav-text">Dashboard</span>
</a>
<div class="nav-divider"></div>
<a href="#" class="nav-item" data-section="smart-recommendations">
<span class="nav-icon">🎯</span>
<span class="nav-text">Smart Recommendations</span>
</a>
<a href="#" class="nav-item" data-section="vpa-management">
<span class="nav-icon">⚙️</span>
<span class="nav-text">VPA Management</span>
</a>
<a href="#" class="nav-item" data-section="historical-analysis">
<span class="nav-icon">📈</span>
<span class="nav-text">Historical Analysis</span>
</a>
</nav>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Export Button -->
<button class="export-button" onclick="exportData()">Export</button>
<!-- Header -->
<div class="header">
<h1>OpenShift Resource Governance Tool</h1>
</div>
<!-- Content -->
<div class="content">
<!-- Dashboard Section -->
<div id="dashboardSection">
<!-- Cluster Overview -->
<div class="card">
<h2>🎯 Resource Governance Dashboard</h2>
<!-- Status Overview -->
<div class="status-overview">
<div class="status-icon" id="clusterStatusIcon">🟢</div>
<div class="status-content">
<h3 id="clusterStatus">Cluster Healthy</h3>
<p id="clusterStatusMessage">All systems operational</p>
</div>
</div>
<!-- Metrics Grid -->
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-value" id="totalPods">-</div>
<div class="metric-label">Total Pods</div>
</div>
<div class="metric-card">
<div class="metric-value" id="totalNamespaces">-</div>
<div class="metric-label">Namespaces</div>
</div>
<div class="metric-card">
<div class="metric-value" id="totalNodes">-</div>
<div class="metric-label">Nodes</div>
</div>
<div class="metric-card error" id="criticalIssuesCard">
<div class="metric-value" id="criticalIssues">-</div>
<div class="metric-label">Critical Issues</div>
</div>
</div>
<!-- Overcommit Summary -->
<div class="card">
<h3>📊 Cluster Overcommit Summary</h3>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-value" id="cpuOvercommit">-</div>
<div class="metric-label">CPU Overcommit</div>
</div>
<div class="metric-card">
<div class="metric-value" id="memoryOvercommit">-</div>
<div class="metric-label">Memory Overcommit</div>
</div>
<div class="metric-card">
<div class="metric-value" id="namespacesInOvercommit">-</div>
<div class="metric-label">Namespaces in Overcommit</div>
</div>
<div class="metric-card">
<div class="metric-value" id="resourceQuotaCoverage">-</div>
<div class="metric-label">Resource Quota Coverage</div>
</div>
</div>
</div>
</div>
<!-- Problem Summary -->
<div class="card problem-summary">
<h2>🔍 Problem Summary</h2>
<p style="margin-bottom: 1.5rem; color: #7f8c8d;">Identify namespaces with resource configuration issues and take action</p>
<!-- Filters -->
<div class="filters">
<div class="filter-group">
<label for="severityFilter">Severity:</label>
<select id="severityFilter">
<option value="all">All</option>
<option value="critical">Critical</option>
<option value="error">Error</option>
<option value="warning">Warning</option>
<option value="info">Info</option>
</select>
</div>
<div class="filter-group">
<label for="perPageFilter">Per page:</label>
<select id="perPageFilter">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="filter-checkbox">
<input type="checkbox" id="includeSystemNamespaces">
<label for="includeSystemNamespaces">Include system namespaces</label>
</div>
<button class="apply-filters-btn" onclick="applyFilters()">Apply Filters</button>
</div>
<!-- Problem Table -->
<div class="table-container">
<table>
<thead>
<tr>
<th>Namespace</th>
<th>Pods</th>
<th>Issues</th>
<th>Severity</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="problemTableBody">
<tr>
<td colspan="5" class="loading">Loading data...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Quick Actions -->
<div class="card">
<h2>⚡ Quick Actions</h2>
<div class="quick-actions">
<button class="quick-action-btn" onclick="generateVPARecommendations()">
<span>🎯</span>
<span>Generate VPA Recommendations</span>
</button>
<button class="quick-action-btn" onclick="showHistoricalAnalysis()">
<span>📈</span>
<span>Historical Analysis</span>
</button>
<button class="quick-action-btn" onclick="exportComplianceReport()">
<span>📊</span>
<span>Export Compliance Report</span>
</button>
<button class="quick-action-btn" onclick="fixAllIssues()">
<span>🔧</span>
<span>Fix All Issues</span>
</button>
</div>
</div>
</div>
<!-- Smart Recommendations Section -->
<div id="smartRecommendationsSection" style="display: none;">
<div class="card">
<h2>🎯 Smart Recommendations</h2>
<div id="smartRecommendationsContent">
<p class="loading">Loading recommendations...</p>
</div>
</div>
</div>
<!-- VPA Management Section -->
<div id="vpaManagementSection" style="display: none;">
<div class="card">
<h2>⚙️ VPA Management</h2>
<div id="vpaManagementContent">
<p class="loading">Loading VPA data...</p>
</div>
</div>
</div>
<!-- Historical Analysis Section -->
<div id="historicalAnalysisSection" style="display: none;">
<div class="card">
<h2>📈 Historical Analysis</h2>
<div id="historicalAnalysisContent">
<p class="loading">Loading historical data...</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentData = null;
let currentFilters = {
severity: 'all',
perPage: 20,
includeSystem: false
};
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
loadDashboard();
setupEventListeners();
});
// Setup event listeners
function setupEventListeners() {
// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
const section = this.dataset.section;
showSection(section);
});
});
// Filter changes
document.getElementById('severityFilter').addEventListener('change', function() {
currentFilters.severity = this.value;
});
document.getElementById('perPageFilter').addEventListener('change', function() {
currentFilters.perPage = parseInt(this.value);
});
document.getElementById('includeSystemNamespaces').addEventListener('change', function() {
currentFilters.includeSystem = this.checked;
});
}
// Show specific section
function showSection(section) {
// Hide all sections
document.querySelectorAll('[id$="Section"]').forEach(sec => {
sec.style.display = 'none';
});
// Remove active class from all nav items
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
// Show selected section
document.getElementById(section + 'Section').style.display = 'block';
document.querySelector(`[data-section="${section}"]`).classList.add('active');
// Load section data
switch(section) {
case 'dashboard':
loadDashboard();
break;
case 'smart-recommendations':
loadSmartRecommendations();
break;
case 'vpa-management':
loadVPAManagement();
break;
case 'historical-analysis':
loadHistoricalAnalysis();
break;
}
}
// Load dashboard data
async function loadDashboard() {
try {
showLoading();
const response = await fetch('/api/cluster/health');
const data = await response.json();
currentData = data;
updateDashboard(data);
} catch (error) {
console.error('Error loading dashboard:', error);
showError('Failed to load dashboard data');
}
}
// Update dashboard with data
function updateDashboard(data) {
// Update metrics
document.getElementById('totalPods').textContent = data.total_pods || 0;
document.getElementById('totalNamespaces').textContent = data.total_namespaces || 0;
document.getElementById('totalNodes').textContent = data.total_nodes || 0;
document.getElementById('criticalIssues').textContent = data.critical_issues || 0;
// Update overcommit metrics
document.getElementById('cpuOvercommit').textContent = data.cpu_overcommit ? `${data.cpu_overcommit}%` : '0%';
document.getElementById('memoryOvercommit').textContent = data.memory_overcommit ? `${data.memory_overcommit}%` : '0%';
document.getElementById('namespacesInOvercommit').textContent = data.namespaces_in_overcommit || 0;
document.getElementById('resourceQuotaCoverage').textContent = data.resource_quota_coverage ? `${data.resource_quota_coverage}%` : '0%';
// Update status
const statusIcon = document.getElementById('clusterStatusIcon');
const statusText = document.getElementById('clusterStatus');
const statusMessage = document.getElementById('clusterStatusMessage');
if (data.critical_issues > 0) {
statusIcon.textContent = '🔴';
statusText.textContent = 'Critical Issues Found';
statusMessage.textContent = `${data.critical_issues} critical issues need attention`;
} else if (data.total_warnings > 0) {
statusIcon.textContent = '🟡';
statusText.textContent = 'Issues Found';
statusMessage.textContent = `${data.total_warnings} warnings found`;
} else {
statusIcon.textContent = '🟢';
statusText.textContent = 'Cluster Healthy';
statusMessage.textContent = 'All systems operational';
}
// Update problem table
updateProblemTable(data);
}
// Update problem table
function updateProblemTable(data) {
const tbody = document.getElementById('problemTableBody');
if (!data.namespaces || data.namespaces.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; color: #7f8c8d;">No data available</td></tr>';
return;
}
let html = '';
data.namespaces.forEach(namespace => {
const severity = getHighestSeverity(namespace);
const severityClass = `severity-${severity}`;
html += `
<tr>
<td><strong>${namespace.name}</strong></td>
<td>${namespace.pod_count || 0}</td>
<td>${namespace.total_validations || 0}</td>
<td><span class="severity-badge ${severityClass}">${severity}</span></td>
<td>
<div class="action-buttons">
<button class="btn btn-primary btn-sm" onclick="analyzeNamespace('${namespace.name}')">Analyze</button>
<button class="btn btn-success btn-sm" onclick="fixNamespace('${namespace.name}')">Fix</button>
</div>
</td>
</tr>
`;
});
tbody.innerHTML = html;
}
// Get highest severity from namespace
function getHighestSeverity(namespace) {
if (namespace.critical_issues > 0) return 'critical';
if (namespace.error_issues > 0) return 'error';
if (namespace.warning_issues > 0) return 'warning';
return 'info';
}
// Apply filters
function applyFilters() {
if (!currentData) return;
// Filter namespaces based on current filters
let filteredNamespaces = currentData.namespaces || [];
if (!currentFilters.includeSystem) {
filteredNamespaces = filteredNamespaces.filter(ns =>
!ns.name.startsWith('kube-') &&
!ns.name.startsWith('openshift-') &&
!ns.name.startsWith('default')
);
}
if (currentFilters.severity !== 'all') {
filteredNamespaces = filteredNamespaces.filter(ns => {
const severity = getHighestSeverity(ns);
return severity === currentFilters.severity;
});
}
// Update table with filtered data
const filteredData = { ...currentData, namespaces: filteredNamespaces };
updateProblemTable(filteredData);
}
// Load smart recommendations
async function loadSmartRecommendations() {
try {
const response = await fetch('/api/recommendations');
const data = await response.json();
updateSmartRecommendations(data);
} catch (error) {
console.error('Error loading recommendations:', error);
document.getElementById('smartRecommendationsContent').innerHTML =
'<p style="color: #e74c3c;">Failed to load recommendations</p>';
}
}
// Update smart recommendations
function updateSmartRecommendations(data) {
const content = document.getElementById('smartRecommendationsContent');
content.innerHTML = '<p>Smart recommendations feature coming soon...</p>';
}
// Load VPA management
async function loadVPAManagement() {
try {
const response = await fetch('/api/vpa');
const data = await response.json();
updateVPAManagement(data);
} catch (error) {
console.error('Error loading VPA data:', error);
document.getElementById('vpaManagementContent').innerHTML =
'<p style="color: #e74c3c;">Failed to load VPA data</p>';
}
}
// Update VPA management
function updateVPAManagement(data) {
const content = document.getElementById('vpaManagementContent');
content.innerHTML = '<p>VPA management feature coming soon...</p>';
}
// Load historical analysis
async function loadHistoricalAnalysis() {
try {
const response = await fetch('/api/historical');
const data = await response.json();
updateHistoricalAnalysis(data);
} catch (error) {
console.error('Error loading historical data:', error);
document.getElementById('historicalAnalysisContent').innerHTML =
'<p style="color: #e74c3c;">Failed to load historical data</p>';
}
}
// Update historical analysis
function updateHistoricalAnalysis(data) {
const content = document.getElementById('historicalAnalysisContent');
content.innerHTML = '<p>Historical analysis feature coming soon...</p>';
}
// Action functions
function analyzeNamespace(namespace) {
alert(`Analyzing namespace: ${namespace}`);
}
function fixNamespace(namespace) {
alert(`Fixing namespace: ${namespace}`);
}
function generateVPARecommendations() {
alert('Generating VPA recommendations...');
}
function showHistoricalAnalysis() {
showSection('historical-analysis');
}
function exportComplianceReport() {
alert('Exporting compliance report...');
}
function fixAllIssues() {
if (confirm('Are you sure you want to fix all issues? This action cannot be undone.')) {
alert('Fixing all issues...');
}
}
function exportData() {
if (!currentData) {
alert('No data to export');
return;
}
const dataStr = JSON.stringify(currentData, null, 2);
const dataBlob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'resource-governance-report.json';
link.click();
URL.revokeObjectURL(url);
}
// Utility functions
function showLoading() {
document.getElementById('problemTableBody').innerHTML =
'<tr><td colspan="5" class="loading">Loading data...</td></tr>';
}
function showError(message) {
document.getElementById('problemTableBody').innerHTML =
`<tr><td colspan="5" style="text-align: center; color: #e74c3c;">${message}</td></tr>`;
}
</script>
</body>
</html>