Init repo
This commit is contained in:
611
static/js/pages/monitoring.js
Normal file
611
static/js/pages/monitoring.js
Normal file
@@ -0,0 +1,611 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user