<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PAGI SSE Dashboard</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #eee;
min-height: 100vh;
padding: 2rem;
}
h1 { margin-bottom: 1rem; color: #00d9ff; }
.status {
padding: 0.5rem 1rem;
border-radius: 4px;
margin-bottom: 1rem;
display: inline-block;
}
.status.connected { background: #0a3d0a; color: #4caf50; }
.status.disconnected { background: #3d0a0a; color: #f44336; }
.status.connecting { background: #3d3d0a; color: #ff9800; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.card {
background: #16213e;
border-radius: 8px;
padding: 1.5rem;
text-align: center;
}
.card .label { color: #888; font-size: 0.875rem; margin-bottom: 0.5rem; }
.card .value { font-size: 2.5rem; font-weight: bold; color: #00d9ff; }
.card .unit { font-size: 1rem; color: #666; }
.events {
background: #16213e;
border-radius: 8px;
padding: 1rem;
max-height: 300px;
overflow-y: auto;
}
.events h3 { margin-bottom: 0.5rem; color: #888; }
.event {
font-family: monospace;
font-size: 0.75rem;
padding: 0.25rem 0;
border-bottom: 1px solid #1a1a2e;
color: #aaa;
}
.event .type { color: #00d9ff; }
</style>
</head>
<body>
<h1>PAGI Live Dashboard</h1>
<div id="status" class="status connecting">Connecting...</div>
<div class="grid">
<div class="card">
<div class="label">CPU Usage</div>
<div class="value"><span id="cpu">--</span><span class="unit">%</span></div>
</div>
<div class="card">
<div class="label">Memory Usage</div>
<div class="value"><span id="memory">--</span><span class="unit">%</span></div>
</div>
<div class="card">
<div class="label">Requests/sec</div>
<div class="value"><span id="requests">--</span></div>
</div>
<div class="card">
<div class="label">Last Update</div>
<div class="value" style="font-size: 1.25rem"><span id="timestamp">--</span></div>
</div>
</div>
<div class="events">
<h3>Event Log</h3>
<div id="event-log"></div>
</div>
<script>
const statusEl = document.getElementById('status');
const cpuEl = document.getElementById('cpu');
const memoryEl = document.getElementById('memory');
const requestsEl = document.getElementById('requests');
const timestampEl = document.getElementById('timestamp');
const eventLog = document.getElementById('event-log');
let eventSource;
function connect() {
statusEl.className = 'status connecting';
statusEl.textContent = 'Connecting...';
eventSource = new EventSource('/events');
eventSource.onopen = () => {
statusEl.className = 'status connected';
statusEl.textContent = 'Connected';
logEvent('open', 'Connection established');
};
eventSource.onerror = () => {
statusEl.className = 'status disconnected';
statusEl.textContent = 'Disconnected - Reconnecting...';
logEvent('error', 'Connection lost');
};
eventSource.addEventListener('connected', (e) => {
const data = JSON.parse(e.data);
logEvent('connected', `Subscriber ID: ${data.subscriber_id}`);
});
eventSource.addEventListener('reconnected', (e) => {
const data = JSON.parse(e.data);
logEvent('reconnected', `Resumed from ID: ${data.last_id}`);
});
eventSource.addEventListener('metrics', (e) => {
const data = JSON.parse(e.data);
cpuEl.textContent = data.cpu;
memoryEl.textContent = data.memory;
requestsEl.textContent = data.requests;
timestampEl.textContent = new Date(data.timestamp * 1000).toLocaleTimeString();
logEvent('metrics', `CPU: ${data.cpu}%, Mem: ${data.memory}%`);
});
}
function logEvent(type, message) {
const div = document.createElement('div');
div.className = 'event';
div.innerHTML = `<span class="type">[${type}]</span> ${message}`;
eventLog.insertBefore(div, eventLog.firstChild);
// Keep only last 50 events
while (eventLog.children.length > 50) {
eventLog.removeChild(eventLog.lastChild);
}
}
connect();
</script>
</body>
</html>