Files
openshift-resource-governance/app/static/index.html
andersonid b2b47c4f1c feat: rebrand application to UWRU Scanner
- Change application name to UWRU Scanner (User Workloads and Resource Usage Scanner)
- Update title, header logo, and all references
- Update FastAPI app metadata and health check
- Update README.md with new branding
- Maintain OpenShift Console visual identity
2025-10-02 11:31:47 -03:00

1052 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UWRU Scanner - User Workloads and Resource Usage Scanner</title>
<!-- Red Hat Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:wght@300;400;500;600;700&family=Red+Hat+Text:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- PatternFly 6.3.1 CSS -->
<link rel="stylesheet" href="https://unpkg.com/@patternfly/patternfly@6.3.1/patternfly.css">
<link rel="stylesheet" href="https://unpkg.com/@patternfly/patternfly@6.3.1/patternfly-addons.css">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Custom OpenShift-like styles -->
<style>
:root {
/* OpenShift Color Palette */
--pf-global--primary-color--100: #0066CC;
--pf-global--primary-color--200: #004080;
--pf-global--success-color--100: #3E8635;
--pf-global--warning-color--100: #F0AB00;
--pf-global--danger-color--100: #C9190B;
--pf-global--info-color--100: #009596;
/* Dark Theme Colors */
--pf-global--BackgroundColor--100: #151515;
--pf-global--BackgroundColor--200: #1E1E1E;
--pf-global--BackgroundColor--300: #2B2B2B;
--pf-global--Color--100: #FFFFFF;
--pf-global--Color--200: #F0F0F0;
--pf-global--Color--300: #D2D2D2;
--pf-global--Color--400: #8A8A8A;
/* Typography */
--pf-global--FontFamily--sans-serif: 'Red Hat Text', 'Helvetica Neue', Arial, sans-serif;
--pf-global--FontFamily--heading: 'Red Hat Display', 'Helvetica Neue', Arial, sans-serif;
}
/* Dark Mode Base */
body {
background-color: var(--pf-global--BackgroundColor--100);
color: var(--pf-global--Color--100);
font-family: var(--pf-global--FontFamily--sans-serif);
margin: 0;
padding: 0;
}
/* OpenShift-like Header */
.openshift-header {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
border-bottom: 1px solid #404040;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.openshift-header-left {
display: flex;
align-items: center;
gap: 16px;
}
.openshift-header-right {
display: flex;
align-items: center;
gap: 16px;
}
.hamburger-menu {
color: var(--pf-global--Color--100);
font-size: 18px;
cursor: pointer;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.hamburger-menu:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.openshift-logo {
color: var(--pf-global--Color--100);
font-family: var(--pf-global--FontFamily--heading);
font-size: 18px;
font-weight: 600;
text-decoration: none;
}
.header-icon {
color: var(--pf-global--Color--100);
font-size: 16px;
cursor: pointer;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s;
position: relative;
}
.header-icon:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.notification-badge {
position: absolute;
top: 4px;
right: 4px;
background-color: var(--pf-global--danger-color--100);
color: white;
font-size: 10px;
font-weight: 600;
padding: 2px 6px;
border-radius: 10px;
min-width: 16px;
text-align: center;
}
.user-dropdown {
display: flex;
align-items: center;
gap: 8px;
color: var(--pf-global--Color--100);
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
}
.user-dropdown:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* OpenShift-like Sidebar */
.openshift-sidebar {
background-color: #000000;
width: 280px;
height: calc(100vh - 60px);
position: fixed;
left: 0;
top: 60px;
overflow-y: auto;
border-right: 1px solid #404040;
z-index: 1000;
}
.sidebar-section {
padding: 16px 0;
border-bottom: 1px solid #404040;
}
.sidebar-section-title {
color: var(--pf-global--Color--400);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 0 24px 8px;
margin: 0;
}
.sidebar-nav {
list-style: none;
margin: 0;
padding: 0;
}
.sidebar-nav-item {
margin: 0;
}
.sidebar-nav-link {
display: flex;
align-items: center;
gap: 12px;
color: var(--pf-global--Color--100);
text-decoration: none;
padding: 12px 24px;
transition: all 0.2s;
border-left: 3px solid transparent;
}
.sidebar-nav-link:hover {
background-color: rgba(255, 255, 255, 0.05);
color: var(--pf-global--Color--100);
}
.sidebar-nav-link.active {
background-color: rgba(0, 102, 204, 0.1);
border-left-color: var(--pf-global--primary-color--100);
color: var(--pf-global--primary-color--100);
}
.sidebar-nav-icon {
font-size: 16px;
width: 20px;
text-align: center;
}
.sidebar-nav-arrow {
margin-left: auto;
font-size: 12px;
opacity: 0.6;
}
/* Main Content Area */
.main-content {
margin-left: 280px;
padding: 24px;
background-color: var(--pf-global--BackgroundColor--100);
min-height: calc(100vh - 60px);
}
.page-header {
margin-bottom: 24px;
}
.page-title {
font-family: var(--pf-global--FontFamily--heading);
font-size: 28px;
font-weight: 600;
color: var(--pf-global--Color--100);
margin: 0 0 8px 0;
}
.page-description {
color: var(--pf-global--Color--300);
font-size: 16px;
margin: 0;
}
/* OpenShift-like Cards */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.openshift-card {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
border: 1px solid #404040;
border-radius: 8px;
padding: 24px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.openshift-card:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.card-title {
font-family: var(--pf-global--FontFamily--heading);
font-size: 18px;
font-weight: 600;
color: var(--pf-global--Color--100);
margin: 0;
}
.card-action {
color: var(--pf-global--primary-color--100);
text-decoration: none;
font-size: 14px;
font-weight: 500;
}
.card-action:hover {
text-decoration: underline;
}
/* Metrics Cards */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.metric-card {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
border: 1px solid #404040;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.3s ease;
}
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
.metric-value {
font-family: var(--pf-global--FontFamily--heading);
font-size: 32px;
font-weight: 700;
color: var(--pf-global--Color--100);
margin: 0 0 8px 0;
}
.metric-label {
color: var(--pf-global--Color--300);
font-size: 14px;
font-weight: 500;
margin: 0;
}
.metric-icon {
font-size: 24px;
margin-bottom: 12px;
}
.metric-icon.success {
color: var(--pf-global--success-color--100);
}
.metric-icon.warning {
color: var(--pf-global--warning-color--100);
}
.metric-icon.danger {
color: var(--pf-global--danger-color--100);
}
.metric-icon.info {
color: var(--pf-global--info-color--100);
}
/* Status Indicators */
.status-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-indicator.success {
background-color: rgba(62, 134, 53, 0.2);
color: var(--pf-global--success-color--100);
border: 1px solid rgba(62, 134, 53, 0.3);
}
.status-indicator.warning {
background-color: rgba(240, 171, 0, 0.2);
color: var(--pf-global--warning-color--100);
border: 1px solid rgba(240, 171, 0, 0.3);
}
.status-indicator.danger {
background-color: rgba(201, 25, 11, 0.2);
color: var(--pf-global--danger-color--100);
border: 1px solid rgba(201, 25, 11, 0.3);
}
.status-indicator.info {
background-color: rgba(0, 149, 150, 0.2);
color: var(--pf-global--info-color--100);
border: 1px solid rgba(0, 149, 150, 0.3);
}
/* Tables */
.openshift-table {
background: linear-gradient(135deg, #2B2B2B 0%, #1E1E1E 100%);
border: 1px solid #404040;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.table-header {
background: linear-gradient(135deg, #404040 0%, #2B2B2B 100%);
padding: 16px 24px;
border-bottom: 1px solid #404040;
}
.table-title {
font-family: var(--pf-global--FontFamily--heading);
font-size: 18px;
font-weight: 600;
color: var(--pf-global--Color--100);
margin: 0;
}
.table-content {
padding: 0;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th {
background-color: #404040;
color: var(--pf-global--Color--100);
font-weight: 600;
font-size: 14px;
padding: 12px 24px;
text-align: left;
border-bottom: 1px solid #404040;
}
.table td {
color: var(--pf-global--Color--200);
font-size: 14px;
padding: 12px 24px;
border-bottom: 1px solid #404040;
}
.table tr:hover {
background-color: rgba(255, 255, 255, 0.02);
}
/* Buttons */
.openshift-button {
background: linear-gradient(135deg, var(--pf-global--primary-color--100) 0%, var(--pf-global--primary-color--200) 100%);
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.openshift-button:hover {
background: linear-gradient(135deg, var(--pf-global--primary-color--200) 0%, #003366 100%);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 102, 204, 0.3);
}
.openshift-button.secondary {
background: linear-gradient(135deg, #404040 0%, #2B2B2B 100%);
color: var(--pf-global--Color--100);
}
.openshift-button.secondary:hover {
background: linear-gradient(135deg, #505050 0%, #404040 100%);
}
/* Loading States */
.loading-spinner {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
color: var(--pf-global--Color--300);
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #404040;
border-top: 2px solid var(--pf-global--primary-color--100);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 12px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Responsive */
@media (max-width: 768px) {
.openshift-sidebar {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.openshift-sidebar.open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.metrics-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Hidden sections */
.section-hidden {
display: none !important;
}
</style>
</head>
<body>
<!-- OpenShift-like Header -->
<header class="openshift-header">
<div class="openshift-header-left">
<button class="hamburger-menu" id="sidebar-toggle">
<i class="fas fa-bars"></i>
</button>
<a href="#" class="openshift-logo">UWRU Scanner</a>
</div>
<div class="openshift-header-right">
<div class="header-icon">
<i class="fas fa-th"></i>
</div>
<div class="header-icon" style="position: relative;">
<i class="fas fa-bell"></i>
<span class="notification-badge">46</span>
</div>
<div class="header-icon">
<i class="fas fa-plus"></i>
</div>
<div class="header-icon">
<i class="fas fa-question-circle"></i>
</div>
<div class="user-dropdown">
<i class="fas fa-user-circle"></i>
<span>anobre</span>
<i class="fas fa-chevron-down"></i>
</div>
</div>
</header>
<!-- OpenShift-like Sidebar -->
<nav class="openshift-sidebar" id="sidebar">
<div class="sidebar-section">
<div class="sidebar-nav">
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link active" data-section="workload-scanner">
<i class="fas fa-home sidebar-nav-icon"></i>
<span>Home</span>
</a>
</li>
</div>
</div>
<div class="sidebar-section">
<h3 class="sidebar-section-title">Favorites</h3>
<ul class="sidebar-nav">
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link" data-section="workload-scanner">
<i class="fas fa-search sidebar-nav-icon"></i>
<span>UWRU Scanner</span>
</a>
</li>
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link" data-section="historical-analysis">
<i class="fas fa-chart-line sidebar-nav-icon"></i>
<span>Historical Analysis</span>
</a>
</li>
</ul>
</div>
<div class="sidebar-section">
<h3 class="sidebar-section-title">Workloads</h3>
<ul class="sidebar-nav">
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link">
<i class="fas fa-cube sidebar-nav-icon"></i>
<span>Pods</span>
<i class="fas fa-chevron-right sidebar-nav-arrow"></i>
</a>
</li>
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link">
<i class="fas fa-layer-group sidebar-nav-icon"></i>
<span>Deployments</span>
<i class="fas fa-chevron-right sidebar-nav-arrow"></i>
</a>
</li>
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link">
<i class="fas fa-server sidebar-nav-icon"></i>
<span>Services</span>
<i class="fas fa-chevron-right sidebar-nav-arrow"></i>
</a>
</li>
</ul>
</div>
<div class="sidebar-section">
<h3 class="sidebar-section-title">Observe</h3>
<ul class="sidebar-nav">
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link">
<i class="fas fa-chart-bar sidebar-nav-icon"></i>
<span>Metrics</span>
<i class="fas fa-chevron-right sidebar-nav-arrow"></i>
</a>
</li>
<li class="sidebar-nav-item">
<a href="#" class="sidebar-nav-link">
<i class="fas fa-search sidebar-nav-icon"></i>
<span>Logs</span>
<i class="fas fa-chevron-right sidebar-nav-arrow"></i>
</a>
</li>
</ul>
</div>
</nav>
<!-- Main Content -->
<main class="main-content">
<!-- Workload Scanner Section -->
<section id="workload-scanner-section">
<div class="page-header">
<h1 class="page-title">UWRU Scanner</h1>
<p class="page-description">User Workloads and Resource Usage Scanner - Identify and analyze workloads with resource configuration issues</p>
</div>
<!-- Metrics Cards -->
<div class="metrics-grid" id="metrics-grid">
<div class="metric-card">
<div class="metric-icon success">
<i class="fas fa-cube"></i>
</div>
<div class="metric-value" id="total-workloads">-</div>
<div class="metric-label">Total Workloads</div>
</div>
<div class="metric-card">
<div class="metric-icon info">
<i class="fas fa-layer-group"></i>
</div>
<div class="metric-value" id="total-namespaces">-</div>
<div class="metric-label">Namespaces</div>
</div>
<div class="metric-card">
<div class="metric-icon danger">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="metric-value" id="critical-issues">-</div>
<div class="metric-label">Critical Issues</div>
</div>
<div class="metric-card">
<div class="metric-icon warning">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="metric-value" id="total-warnings">-</div>
<div class="metric-label">Warnings</div>
</div>
</div>
<!-- Workloads Table Card -->
<div class="openshift-card">
<div class="card-header">
<h2 class="card-title">Workloads with Issues</h2>
<button class="openshift-button" id="refresh-workloads">
<i class="fas fa-sync-alt"></i>
Refresh
</button>
</div>
<div class="table-content" id="workloads-table-container">
<div class="loading-spinner">
<div class="spinner"></div>
Loading workloads...
</div>
</div>
</div>
</section>
<!-- Historical Analysis Section -->
<section id="historical-analysis-section" class="section-hidden">
<div class="page-header">
<h1 class="page-title">Historical Analysis</h1>
<p class="page-description">Resource consumption analysis and historical data</p>
</div>
<!-- Workloads List Card -->
<div class="openshift-card">
<div class="card-header">
<h2 class="card-title">Available Workloads</h2>
<button class="openshift-button" id="refresh-historical">
<i class="fas fa-sync-alt"></i>
Refresh
</button>
</div>
<div class="table-content" id="historical-workloads-container">
<div class="loading-spinner">
<div class="spinner"></div>
Loading historical data...
</div>
</div>
</div>
<!-- Workload Details Card (hidden initially) -->
<div class="openshift-card section-hidden" id="workload-details-container">
<div class="card-header">
<h2 class="card-title" id="workload-details-title">Workload Details</h2>
<button class="openshift-button secondary" id="close-workload-details">
<i class="fas fa-times"></i>
Close
</button>
</div>
<div class="table-content" id="workload-details-content">
<!-- Workload details will be populated here -->
</div>
</div>
</section>
</main>
<!-- JavaScript -->
<script>
// Global variables
let currentData = null;
let currentSection = 'workload-scanner';
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
function initializeApp() {
// Setup navigation
setupNavigation();
// Load initial data
loadWorkloadScanner();
}
function setupNavigation() {
// Sidebar navigation
const navLinks = document.querySelectorAll('.sidebar-nav-link[data-section]');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
showSection(section);
});
});
// Sidebar toggle
document.getElementById('sidebar-toggle').addEventListener('click', function() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('open');
});
// Close workload details
document.getElementById('close-workload-details').addEventListener('click', function() {
document.getElementById('workload-details-container').classList.add('section-hidden');
});
// Refresh buttons
document.getElementById('refresh-workloads').addEventListener('click', loadWorkloadScanner);
document.getElementById('refresh-historical').addEventListener('click', loadHistoricalAnalysis);
}
function showSection(section) {
// Hide all sections
document.querySelectorAll('main section').forEach(sec => {
sec.classList.add('section-hidden');
});
// Show selected section
document.getElementById(section + '-section').classList.remove('section-hidden');
// Update active nav item
document.querySelectorAll('.sidebar-nav-link').forEach(link => {
link.classList.remove('active');
});
document.querySelector(`.sidebar-nav-link[data-section="${section}"]`).classList.add('active');
currentSection = section;
// Load section data
if (section === 'workload-scanner') {
loadWorkloadScanner();
} else if (section === 'historical-analysis') {
loadHistoricalAnalysis();
}
}
async function loadWorkloadScanner() {
try {
showLoading('workloads-table-container');
// Load cluster status
const clusterResponse = await fetch('/api/v1/cluster/status');
const clusterData = await clusterResponse.json();
// Load validations
const validationsResponse = await fetch('/api/v1/validations');
const validationsData = await validationsResponse.json();
currentData = { cluster: clusterData, validations: validationsData };
// Update metrics cards
updateMetricsCards(clusterData);
// Update workloads table
updateWorkloadsTable(validationsData);
} catch (error) {
console.error('Error loading workload scanner data:', error);
showError('workloads-table-container', 'Failed to load workload data');
}
}
async function loadHistoricalAnalysis() {
try {
showLoading('historical-workloads-container');
// Load historical data
const response = await fetch('/api/v1/historical-analysis');
const data = await response.json();
updateHistoricalWorkloads(data);
} catch (error) {
console.error('Error loading historical analysis data:', error);
showError('historical-workloads-container', 'Failed to load historical data');
}
}
function updateMetricsCards(data) {
document.getElementById('total-workloads').textContent = data.total_pods || 0;
document.getElementById('total-namespaces').textContent = data.total_namespaces || 0;
document.getElementById('critical-issues').textContent = data.critical_issues || 0;
document.getElementById('total-warnings').textContent = data.total_warnings || 0;
}
function updateWorkloadsTable(data) {
const container = document.getElementById('workloads-table-container');
if (!data.namespaces || data.namespaces.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 40px; color: var(--pf-global--Color--300);">
<i class="fas fa-check-circle" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--success-color--100);"></i>
<h3 style="margin: 0 0 8px 0; color: var(--pf-global--Color--100);">No Issues Found</h3>
<p style="margin: 0;">All workloads are properly configured</p>
</div>
`;
return;
}
const tableHTML = `
<table class="table">
<thead>
<tr>
<th>Namespace</th>
<th>Pods</th>
<th>Issues</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${data.namespaces.map(namespace => `
<tr>
<td>
<strong style="color: var(--pf-global--Color--100);">${namespace.namespace}</strong>
</td>
<td>${Object.keys(namespace.pods || {}).length}</td>
<td>${namespace.total_validations || 0}</td>
<td>
<span class="status-indicator ${getSeverityClass(namespace)}">
${getSeverityText(namespace)}
</span>
</td>
<td>
<button class="openshift-button secondary" onclick="analyzeWorkload('${namespace.namespace}')">
<i class="fas fa-search"></i>
Analyze
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
container.innerHTML = tableHTML;
}
function updateHistoricalWorkloads(data) {
const container = document.getElementById('historical-workloads-container');
if (!data.workloads || data.workloads.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 40px; color: var(--pf-global--Color--300);">
<i class="fas fa-chart-line" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--info-color--100);"></i>
<h3 style="margin: 0 0 8px 0; color: var(--pf-global--Color--100);">No Historical Data</h3>
<p style="margin: 0;">No workloads available for analysis</p>
</div>
`;
return;
}
const tableHTML = `
<table class="table">
<thead>
<tr>
<th>Workload</th>
<th>Namespace</th>
<th>Pods</th>
<th>CPU Usage</th>
<th>Memory Usage</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${data.workloads.map(workload => `
<tr>
<td>
<strong style="color: var(--pf-global--Color--100);">${workload.name}</strong>
</td>
<td>${workload.namespace}</td>
<td>${workload.pod_count || 0}</td>
<td>${workload.cpu_usage || 'N/A'}</td>
<td>${workload.memory_usage || 'N/A'}</td>
<td>${workload.last_updated ? new Date(workload.last_updated).toLocaleString() : 'N/A'}</td>
<td>
<button class="openshift-button" onclick="showWorkloadDetails('${workload.name}', '${workload.namespace}')">
<i class="fas fa-eye"></i>
View Details
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
container.innerHTML = tableHTML;
}
function showWorkloadDetails(workloadName, namespace) {
// Update title
document.getElementById('workload-details-title').textContent = `${workloadName} - ${namespace}`;
// Load workload details
loadWorkloadDetails(workloadName, namespace);
// Show details container
document.getElementById('workload-details-container').classList.remove('section-hidden');
}
async function loadWorkloadDetails(workloadName, namespace) {
try {
const response = await fetch(`/api/v1/historical-analysis/${namespace}/${workloadName}`);
const data = await response.json();
updateWorkloadDetails(data);
} catch (error) {
console.error('Error loading workload details:', error);
document.getElementById('workload-details-content').innerHTML =
'<div style="text-align: center; padding: 40px; color: var(--pf-global--danger-color--100);">Failed to load workload details</div>';
}
}
function updateWorkloadDetails(data) {
const container = document.getElementById('workload-details-content');
container.innerHTML = `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px;">
<div class="openshift-card">
<div class="card-header">
<h3 class="card-title">CPU Usage</h3>
</div>
<div style="padding: 20px; text-align: center; color: var(--pf-global--Color--300);">
<i class="fas fa-microchip" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--info-color--100);"></i>
<p>CPU usage data will be displayed here</p>
</div>
</div>
<div class="openshift-card">
<div class="card-header">
<h3 class="card-title">Memory Usage</h3>
</div>
<div style="padding: 20px; text-align: center; color: var(--pf-global--Color--300);">
<i class="fas fa-memory" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--warning-color--100);"></i>
<p>Memory usage data will be displayed here</p>
</div>
</div>
</div>
`;
}
function analyzeWorkload(namespace) {
console.log('Analyzing workload:', namespace);
// TODO: Implement workload analysis
}
function getSeverityClass(namespace) {
const breakdown = namespace.severity_breakdown || {};
if (breakdown.error > 0) return 'danger';
if (breakdown.warning > 0) return 'warning';
if (breakdown.info > 0) return 'info';
return 'success';
}
function getSeverityText(namespace) {
const breakdown = namespace.severity_breakdown || {};
if (breakdown.error > 0) return 'Error';
if (breakdown.warning > 0) return 'Warning';
if (breakdown.info > 0) return 'Info';
return 'Healthy';
}
function showLoading(containerId) {
const container = document.getElementById(containerId);
container.innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
Loading...
</div>
`;
}
function showError(containerId, message) {
const container = document.getElementById(containerId);
container.innerHTML = `
<div style="text-align: center; padding: 40px; color: var(--pf-global--danger-color--100);">
<i class="fas fa-exclamation-triangle" style="font-size: 48px; margin-bottom: 16px;"></i>
<h3 style="margin: 0 0 8px 0; color: var(--pf-global--Color--100);">Error</h3>
<p style="margin: 0;">${message}</p>
</div>
`;
}
</script>
</body>
</html>