Files
GopherGate/static/js/pages/monitoring.js

611 lines
19 KiB
JavaScript

// Monitoring Page Module
class MonitoringPage {
constructor() {
this.isPaused = false;
this.requestStream = [];
this.systemLogs = [];
this.init();
}
async init() {
// Load initial data
await this.loadSystemMetrics();
await this.loadCharts();
// Setup event listeners
this.setupEventListeners();
// Setup WebSocket subscriptions
this.setupWebSocketSubscriptions();
// Start simulated updates for demo
this.startDemoUpdates();
}
async loadSystemMetrics() {
const container = document.getElementById('system-metrics');
if (!container) return;
const metrics = [
{ label: 'CPU Usage', value: '24%', trend: 'down', color: 'success' },
{ label: 'Memory Usage', value: '1.8 GB', trend: 'stable', color: 'warning' },
{ label: 'Disk I/O', value: '45 MB/s', trend: 'up', color: 'primary' },
{ label: 'Network', value: '125 KB/s', trend: 'up', color: 'info' },
{ label: 'Active Connections', value: '42', trend: 'stable', color: 'success' },
{ label: 'Queue Length', value: '3', trend: 'down', color: 'success' }
];
container.innerHTML = metrics.map(metric => `
<div class="metric-item">
<div class="metric-label">${metric.label}</div>
<div class="metric-value">${metric.value}</div>
<div class="metric-trend ${metric.trend}">
<i class="fas fa-arrow-${metric.trend === 'up' ? 'up' : metric.trend === 'down' ? 'down' : 'minus'}"></i>
</div>
</div>
`).join('');
// Add CSS for metrics
this.addMetricStyles();
}
addMetricStyles() {
const style = document.createElement('style');
style.textContent = `
.metric-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
border-bottom: 1px solid var(--border-color);
}
.metric-item:last-child {
border-bottom: none;
}
.metric-label {
font-size: 0.875rem;
color: var(--text-secondary);
}
.metric-value {
font-weight: 600;
color: var(--text-primary);
}
.metric-trend {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
.metric-trend.up {
background-color: rgba(239, 68, 68, 0.1);
color: var(--danger);
}
.metric-trend.down {
background-color: rgba(16, 185, 129, 0.1);
color: var(--success);
}
.metric-trend.stable {
background-color: rgba(100, 116, 139, 0.1);
color: var(--text-secondary);
}
.monitoring-stream {
height: 300px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: var(--border-radius-sm);
padding: 0.5rem;
background-color: var(--bg-secondary);
}
.stream-entry {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s ease;
}
.stream-entry:last-child {
border-bottom: none;
}
.stream-entry.highlight {
background-color: rgba(37, 99, 235, 0.05);
}
.stream-entry-time {
font-size: 0.75rem;
color: var(--text-light);
min-width: 60px;
}
.stream-entry-icon {
font-size: 0.875rem;
width: 24px;
text-align: center;
}
.stream-entry-content {
flex: 1;
font-size: 0.875rem;
}
.stream-entry-details {
font-size: 0.75rem;
color: var(--text-secondary);
margin-top: 0.125rem;
}
.log-stream {
height: 400px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: var(--border-radius-sm);
padding: 0.5rem;
background-color: var(--bg-secondary);
font-family: monospace;
font-size: 0.75rem;
}
.log-entry {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.25rem 0.5rem;
border-bottom: 1px solid var(--border-color);
}
.log-entry:last-child {
border-bottom: none;
}
.log-time {
color: var(--text-light);
min-width: 80px;
}
.log-level {
width: 24px;
text-align: center;
}
.log-info .log-level {
color: var(--info);
}
.log-warn .log-level {
color: var(--warning);
}
.log-error .log-level {
color: var(--danger);
}
.log-debug .log-level {
color: var(--text-light);
}
.log-message {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
document.head.appendChild(style);
}
async loadCharts() {
await this.loadResponseTimeChart();
await this.loadErrorRateChart();
await this.loadRateLimitChart();
}
async loadResponseTimeChart() {
try {
// Generate demo data for response time
const labels = Array.from({ length: 20 }, (_, i) => `${i + 1}m`);
const data = {
labels: labels,
datasets: [{
label: 'Response Time (ms)',
data: labels.map(() => Math.floor(Math.random() * 200) + 300),
color: '#3b82f6',
fill: true
}]
};
window.chartManager.createLineChart('response-time-chart', data, {
scales: {
y: {
title: {
display: true,
text: 'Milliseconds'
}
}
}
});
} catch (error) {
console.error('Error loading response time chart:', error);
}
}
async loadErrorRateChart() {
try {
const labels = Array.from({ length: 20 }, (_, i) => `${i + 1}m`);
const data = {
labels: labels,
datasets: [{
label: 'Error Rate (%)',
data: labels.map(() => Math.random() * 5),
color: '#ef4444',
fill: true
}]
};
window.chartManager.createLineChart('error-rate-chart', data, {
scales: {
y: {
title: {
display: true,
text: 'Percentage'
},
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
});
} catch (error) {
console.error('Error loading error rate chart:', error);
}
}
async loadRateLimitChart() {
try {
const labels = ['Web App', 'Mobile App', 'API Integration', 'Internal Tools', 'Testing'];
const data = {
labels: labels,
datasets: [{
label: 'Rate Limit Usage',
data: [65, 45, 78, 34, 60],
color: '#10b981'
}]
};
window.chartManager.createBarChart('rate-limit-chart', data, {
scales: {
y: {
title: {
display: true,
text: 'Percentage'
},
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
});
} catch (error) {
console.error('Error loading rate limit chart:', error);
}
}
setupEventListeners() {
// Pause/resume monitoring button
const pauseBtn = document.getElementById('pause-monitoring');
if (pauseBtn) {
pauseBtn.addEventListener('click', () => {
this.togglePause();
});
}
}
setupWebSocketSubscriptions() {
if (!window.wsManager) return;
// Subscribe to request updates
window.wsManager.subscribe('requests', (request) => {
if (!this.isPaused) {
this.addToRequestStream(request);
}
});
// Subscribe to log updates
window.wsManager.subscribe('logs', (log) => {
if (!this.isPaused) {
this.addToLogStream(log);
}
});
// Subscribe to metric updates
window.wsManager.subscribe('metrics', (metric) => {
if (!this.isPaused) {
this.updateCharts(metric);
}
});
}
togglePause() {
this.isPaused = !this.isPaused;
const pauseBtn = document.getElementById('pause-monitoring');
if (pauseBtn) {
if (this.isPaused) {
pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume';
pauseBtn.classList.remove('btn-secondary');
pauseBtn.classList.add('btn-success');
if (window.authManager) {
window.authManager.showToast('Monitoring paused', 'warning');
}
} else {
pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
pauseBtn.classList.remove('btn-success');
pauseBtn.classList.add('btn-secondary');
if (window.authManager) {
window.authManager.showToast('Monitoring resumed', 'success');
}
}
}
}
addToRequestStream(request) {
const streamElement = document.getElementById('request-stream');
if (!streamElement) return;
// Create entry
const entry = document.createElement('div');
entry.className = 'stream-entry';
// Format time
const time = new Date().toLocaleTimeString();
// Determine icon based on status
let icon = 'question-circle';
let color = 'var(--text-secondary)';
if (request.status === 'success') {
icon = 'check-circle';
color = 'var(--success)';
} else if (request.status === 'error') {
icon = 'exclamation-circle';
color = 'var(--danger)';
}
entry.innerHTML = `
<div class="stream-entry-time">${time}</div>
<div class="stream-entry-icon" style="color: ${color}">
<i class="fas fa-${icon}"></i>
</div>
<div class="stream-entry-content">
<strong>${request.client_id || 'Unknown'}</strong> →
${request.provider || 'Unknown'} (${request.model || 'Unknown'})
<div class="stream-entry-details">
${request.tokens || 0} tokens • ${request.duration || 0}ms
</div>
</div>
`;
// Add to top of stream
streamElement.insertBefore(entry, streamElement.firstChild);
// Store in memory (limit to 100)
this.requestStream.unshift({
time,
request,
element: entry
});
if (this.requestStream.length > 100) {
const oldEntry = this.requestStream.pop();
if (oldEntry.element.parentNode) {
oldEntry.element.remove();
}
}
// Add highlight animation
entry.classList.add('highlight');
setTimeout(() => entry.classList.remove('highlight'), 1000);
}
addToLogStream(log) {
const logElement = document.getElementById('system-logs');
if (!logElement) return;
// Create entry
const entry = document.createElement('div');
entry.className = `log-entry log-${log.level || 'info'}`;
// Format time
const time = new Date().toLocaleTimeString();
// Determine icon based on level
let icon = 'info-circle';
if (log.level === 'error') icon = 'exclamation-circle';
if (log.level === 'warn') icon = 'exclamation-triangle';
if (log.level === 'debug') icon = 'bug';
entry.innerHTML = `
<div class="log-time">${time}</div>
<div class="log-level">
<i class="fas fa-${icon}"></i>
</div>
<div class="log-message">${log.message || ''}</div>
`;
// Add to top of stream
logElement.insertBefore(entry, logElement.firstChild);
// Store in memory (limit to 100)
this.systemLogs.unshift({
time,
log,
element: entry
});
if (this.systemLogs.length > 100) {
const oldEntry = this.systemLogs.pop();
if (oldEntry.element.parentNode) {
oldEntry.element.remove();
}
}
}
updateCharts(metric) {
// Update charts with new metric data
if (metric.type === 'response_time' && window.chartManager.charts.has('response-time-chart')) {
this.updateResponseTimeChart(metric.value);
}
if (metric.type === 'error_rate' && window.chartManager.charts.has('error-rate-chart')) {
this.updateErrorRateChart(metric.value);
}
}
updateResponseTimeChart(value) {
window.chartManager.addDataPoint('response-time-chart', value);
}
updateErrorRateChart(value) {
window.chartManager.addDataPoint('error-rate-chart', value);
}
startDemoUpdates() {
// Simulate incoming requests for demo purposes
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
setInterval(() => {
if (!this.isPaused && Math.random() > 0.3) { // 70% chance
this.simulateRequest();
}
}, 2000);
// Simulate logs
setInterval(() => {
if (!this.isPaused && Math.random() > 0.5) { // 50% chance
this.simulateLog();
}
}, 3000);
// Simulate metrics
setInterval(() => {
if (!this.isPaused) {
this.simulateMetric();
}
}, 5000);
}
}
simulateRequest() {
const clients = ['client-1', 'client-2', 'client-3', 'client-4', 'client-5'];
const providers = ['OpenAI', 'Gemini', 'DeepSeek', 'Grok'];
const models = ['gpt-4', 'gpt-3.5-turbo', 'gemini-pro', 'deepseek-chat', 'grok-beta'];
const statuses = ['success', 'success', 'success', 'error', 'warning']; // Mostly success
const request = {
client_id: clients[Math.floor(Math.random() * clients.length)],
provider: providers[Math.floor(Math.random() * providers.length)],
model: models[Math.floor(Math.random() * models.length)],
tokens: Math.floor(Math.random() * 2000) + 100,
duration: Math.floor(Math.random() * 1000) + 100,
status: statuses[Math.floor(Math.random() * statuses.length)],
timestamp: Date.now()
};
this.addToRequestStream(request);
}
simulateLog() {
const levels = ['info', 'info', 'info', 'warn', 'error'];
const messages = [
'Request processed successfully',
'Cache hit for model gpt-4',
'Rate limit check passed',
'High latency detected for DeepSeek provider',
'API key validation failed',
'Database connection pool healthy',
'New client registered: client-7',
'Backup completed successfully',
'Memory usage above 80% threshold',
'Provider Grok is offline'
];
const log = {
level: levels[Math.floor(Math.random() * levels.length)],
message: messages[Math.floor(Math.random() * messages.length)],
timestamp: Date.now()
};
this.addToLogStream(log);
}
simulateMetric() {
const metricTypes = ['response_time', 'error_rate'];
const type = metricTypes[Math.floor(Math.random() * metricTypes.length)];
let value;
if (type === 'response_time') {
value = Math.floor(Math.random() * 200) + 300; // 300-500ms
} else {
value = Math.random() * 5; // 0-5%
}
this.updateCharts({ type, value });
}
clearStreams() {
const streamElement = document.getElementById('request-stream');
const logElement = document.getElementById('system-logs');
if (streamElement) {
streamElement.innerHTML = '';
this.requestStream = [];
}
if (logElement) {
logElement.innerHTML = '';
this.systemLogs = [];
}
}
refresh() {
this.loadSystemMetrics();
this.loadCharts();
this.clearStreams();
if (window.authManager) {
window.authManager.showToast('Monitoring refreshed', 'success');
}
}
}
// Initialize monitoring page when needed
window.initMonitoring = async () => {
window.monitoringPage = new MonitoringPage();
};
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = MonitoringPage;
}