Some checks failed
API Endpoint Tests / test-api-endpoints (push) Waiting to run
CLI Tests / test-cli (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
- Updated marketplace commands: `marketplace --action` → `market` subcommands - Updated wallet commands: direct flags → `wallet` subcommands - Updated AI commands: `ai-submit`, `ai-status` → `ai submit`, `ai status` - Updated blockchain commands: `chain` → `blockchain info` - Standardized command structure across all workflow files - Affected files: MULTI_NODE_MASTER_INDEX.md, TEST_MASTER_INDEX.md, multi-node-blockchain-marketplace
313 lines
14 KiB
HTML
313 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AITBC Metrics Dashboard</title>
|
|
<link rel="stylesheet" href="/assets/css/site-header.css">
|
|
<link rel="stylesheet" href="/assets/css/dashboards.css">
|
|
<link rel="preload" href="/assets/css/font-awesome.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
</head>
|
|
<body>
|
|
<div data-global-header></div>
|
|
|
|
<main>
|
|
<div class="container">
|
|
<!-- Header -->
|
|
<div class="dashboard-header">
|
|
<h1><i class="fas fa-chart-line"></i> System Metrics Dashboard</h1>
|
|
<p>Real-time monitoring of AITBC system performance and health</p>
|
|
<div class="last-updated">
|
|
<span>Last Updated: <span id="last-updated">Loading...</span></span>
|
|
<button onclick="refreshMetrics()" class="btn btn-primary">
|
|
<i class="fas fa-sync-alt"></i> Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metrics Grid -->
|
|
<div class="metrics-grid">
|
|
<!-- API Metrics -->
|
|
<div class="metric-card">
|
|
<div class="metric-header">
|
|
<h3><i class="fas fa-server"></i> API Metrics</h3>
|
|
</div>
|
|
<div class="metric-body">
|
|
<div class="metric-item">
|
|
<span class="metric-label">Total Requests</span>
|
|
<span class="metric-value" id="api-requests">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Errors</span>
|
|
<span class="metric-value error" id="api-errors">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Error Rate</span>
|
|
<span class="metric-value" id="error-rate">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Avg Response Time</span>
|
|
<span class="metric-value" id="avg-response-time">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Metrics -->
|
|
<div class="metric-card">
|
|
<div class="metric-header">
|
|
<h3><i class="fas fa-database"></i> Database Metrics</h3>
|
|
</div>
|
|
<div class="metric-body">
|
|
<div class="metric-item">
|
|
<span class="metric-label">Queries</span>
|
|
<span class="metric-value" id="db-queries">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Errors</span>
|
|
<span class="metric-value error" id="db-errors">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Active Connections</span>
|
|
<span class="metric-value" id="active-connections">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cache Metrics -->
|
|
<div class="metric-card">
|
|
<div class="metric-header">
|
|
<h3><i class="fas fa-memory"></i> Cache Metrics</h3>
|
|
</div>
|
|
<div class="metric-body">
|
|
<div class="metric-item">
|
|
<span class="metric-label">Cache Hits</span>
|
|
<span class="metric-value success" id="cache-hits">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Cache Misses</span>
|
|
<span class="metric-value warning" id="cache-misses">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Hit Rate</span>
|
|
<span class="metric-value" id="cache-hit-rate">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Metrics -->
|
|
<div class="metric-card">
|
|
<div class="metric-header">
|
|
<h3><i class="fas fa-microchip"></i> System Metrics</h3>
|
|
</div>
|
|
<div class="metric-body">
|
|
<div class="metric-item">
|
|
<span class="metric-label">Memory Usage</span>
|
|
<span class="metric-value" id="memory-usage">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">CPU Usage</span>
|
|
<span class="metric-value" id="cpu-usage">-</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Uptime</span>
|
|
<span class="metric-value" id="uptime">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Indicators -->
|
|
<div class="status-section">
|
|
<h2>System Status</h2>
|
|
<div class="status-indicators">
|
|
<div class="status-item" id="status-api">
|
|
<span class="status-label">API Service</span>
|
|
<span class="status-badge">Checking...</span>
|
|
</div>
|
|
<div class="status-item" id="status-database">
|
|
<span class="status-label">Database</span>
|
|
<span class="status-badge">Checking...</span>
|
|
</div>
|
|
<div class="status-item" id="status-cache">
|
|
<span class="status-label">Cache</span>
|
|
<span class="status-badge">Checking...</span>
|
|
</div>
|
|
<div class="status-item" id="status-blockchain">
|
|
<span class="status-label">Blockchain</span>
|
|
<span class="status-badge">Checking...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-section">
|
|
<h2>Active Alerts</h2>
|
|
<div class="status-indicators" id="alert-indicators">
|
|
<div class="status-item">
|
|
<span class="status-label">Alert State</span>
|
|
<span class="status-badge">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
// Auto-refresh metrics every 30 seconds
|
|
let refreshInterval;
|
|
const METRICS_ENDPOINT = '/v1/metrics';
|
|
const HEALTH_ENDPOINT = '/v1/health';
|
|
|
|
function refreshMetrics() {
|
|
// Update timestamp
|
|
document.getElementById('last-updated').textContent = new Date().toLocaleTimeString();
|
|
|
|
fetchMetrics();
|
|
checkServiceStatus();
|
|
}
|
|
|
|
async function fetchMetrics() {
|
|
try {
|
|
const response = await fetch(METRICS_ENDPOINT, { method: 'GET', cache: 'no-cache' });
|
|
if (!response.ok) {
|
|
throw new Error(`Metrics request failed with status ${response.status}`);
|
|
}
|
|
const metrics = await response.json();
|
|
|
|
// Update UI
|
|
document.getElementById('api-requests').textContent = metrics.api_requests.toLocaleString();
|
|
document.getElementById('api-errors').textContent = metrics.api_errors.toLocaleString();
|
|
document.getElementById('error-rate').textContent = Number(metrics.error_rate_percent).toFixed(2) + '%';
|
|
document.getElementById('avg-response-time').textContent = Number(metrics.avg_response_time_ms).toFixed(2) + 'ms';
|
|
document.getElementById('db-queries').textContent = metrics.database_queries.toLocaleString();
|
|
document.getElementById('db-errors').textContent = metrics.database_errors.toLocaleString();
|
|
document.getElementById('active-connections').textContent = metrics.active_connections;
|
|
document.getElementById('cache-hits').textContent = metrics.cache_hits.toLocaleString();
|
|
document.getElementById('cache-misses').textContent = metrics.cache_misses.toLocaleString();
|
|
document.getElementById('cache-hit-rate').textContent = Number(metrics.cache_hit_rate_percent).toFixed(2) + '%';
|
|
document.getElementById('memory-usage').textContent = Number(metrics.memory_usage_mb).toFixed(2) + ' MB';
|
|
document.getElementById('cpu-usage').textContent = Number(metrics.cpu_usage_percent).toFixed(2) + '%';
|
|
document.getElementById('uptime').textContent = metrics.uptime_formatted;
|
|
|
|
resetMetricClasses();
|
|
setMetricColor('error-rate', metrics.error_rate_percent, 1, 5);
|
|
setMetricColor('avg-response-time', metrics.avg_response_time_ms, 500, 1000);
|
|
setMetricColor('cache-hit-rate', metrics.cache_hit_rate_percent, 70, 85, true);
|
|
setMetricColor('memory-usage', (metrics.alerts?.memory_usage?.value || 0), 75, 90);
|
|
renderAlerts(metrics.alerts || {});
|
|
|
|
} catch (error) {
|
|
console.error('Failed to fetch metrics:', error);
|
|
document.getElementById('alert-indicators').innerHTML = `
|
|
<div class="status-item">
|
|
<span class="status-label">Alert State</span>
|
|
<span class="status-badge error">Metrics Unavailable</span>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
async function checkServiceStatus() {
|
|
const services = [
|
|
{ id: 'status-api', url: HEALTH_ENDPOINT },
|
|
{ id: 'status-database', url: HEALTH_ENDPOINT },
|
|
{ id: 'status-cache', url: HEALTH_ENDPOINT },
|
|
{ id: 'status-blockchain', url: 'http://localhost:8006/v1/health' }
|
|
];
|
|
|
|
for (const service of services) {
|
|
try {
|
|
const response = await fetch(service.url, { method: 'GET', cache: 'no-cache' });
|
|
const badge = document.querySelector(`#${service.id} .status-badge`);
|
|
|
|
if (response.ok) {
|
|
badge.textContent = 'Online';
|
|
badge.className = 'status-badge success';
|
|
} else {
|
|
badge.textContent = 'Degraded';
|
|
badge.className = 'status-badge warning';
|
|
}
|
|
} catch (error) {
|
|
const badge = document.querySelector(`#${service.id} .status-badge`);
|
|
badge.textContent = 'Offline';
|
|
badge.className = 'status-badge error';
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderAlerts(alerts) {
|
|
const container = document.getElementById('alert-indicators');
|
|
const entries = Object.entries(alerts);
|
|
const triggeredAlerts = entries.filter(([, alert]) => alert.triggered);
|
|
|
|
if (entries.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="status-item">
|
|
<span class="status-label">Alert State</span>
|
|
<span class="status-badge warning">No Alert Data</span>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
if (triggeredAlerts.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="status-item">
|
|
<span class="status-label">Alert State</span>
|
|
<span class="status-badge success">All Clear</span>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = triggeredAlerts.map(([name, alert]) => `
|
|
<div class="status-item">
|
|
<span class="status-label">${formatAlertName(name)}</span>
|
|
<span class="status-badge error">Critical (${alert.value} / ${alert.threshold})</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function formatAlertName(name) {
|
|
return name.replaceAll('_', ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
}
|
|
|
|
function resetMetricClasses() {
|
|
document.querySelectorAll('.metric-value').forEach((element) => {
|
|
element.classList.remove('success', 'warning', 'error');
|
|
});
|
|
}
|
|
|
|
function setMetricColor(elementId, value, warningThreshold, errorThreshold, invert = false) {
|
|
const element = document.getElementById(elementId);
|
|
const numValue = parseFloat(value);
|
|
|
|
if (invert) {
|
|
// Higher is better (e.g., cache hit rate)
|
|
if (numValue >= errorThreshold) {
|
|
element.classList.add('success');
|
|
} else if (numValue >= warningThreshold) {
|
|
element.classList.add('warning');
|
|
} else {
|
|
element.classList.add('error');
|
|
}
|
|
} else {
|
|
// Lower is better (e.g., error rate)
|
|
if (numValue <= warningThreshold) {
|
|
element.classList.add('success');
|
|
} else if (numValue <= errorThreshold) {
|
|
element.classList.add('warning');
|
|
} else {
|
|
element.classList.add('error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
refreshMetrics();
|
|
refreshInterval = setInterval(refreshMetrics, 30000); // Refresh every 30 seconds
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|