← back to Melanie Project
public/agents-app.js
386 lines
// Agent Dashboard Client
class AgentDashboard {
constructor() {
this.agents = new Map();
this.logs = [];
this.settings = this.loadSettings();
this.ws = null;
this.startTime = Date.now();
this.init();
}
init() {
this.setupNavigation();
this.connectWebSocket();
this.startPolling();
this.updateSystemStatus();
// Add sample initial logs
this.addLog('info', 'server-health', 'Agent dashboard initialized');
this.addLog('info', 'api-monitor', 'Monitoring Anthropic API health');
this.addLog('info', 'upload-guardian', 'File upload monitoring active');
this.addLog('info', 'ssl-cert', 'SSL certificate monitoring active');
}
setupNavigation() {
const tabs = document.querySelectorAll('.nav-tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const view = tab.dataset.view;
this.switchView(view);
// Update active tab
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
});
});
}
switchView(view) {
const views = document.querySelectorAll('.view-container');
views.forEach(v => v.classList.remove('active'));
const targetView = document.getElementById(`${view}View`);
if (targetView) {
targetView.classList.add('active');
// Refresh data for the view
if (view === 'monitoring') {
this.updateMonitoringCharts();
} else if (view === 'logs') {
this.renderLogs();
}
}
}
connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}`;
try {
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.addLog('info', 'system', 'Real-time monitoring connected');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleWebSocketMessage(data);
};
this.ws.onerror = () => {
console.log('WebSocket not available, using polling');
this.addLog('warning', 'system', 'Real-time updates unavailable, using polling');
};
this.ws.onclose = () => {
setTimeout(() => this.connectWebSocket(), 5000);
};
} catch (error) {
console.log('WebSocket connection failed, using polling');
}
}
handleWebSocketMessage(data) {
if (data.type === 'agent-update') {
this.updateAgentMetrics(data.agent, data.metrics);
} else if (data.type === 'log') {
this.addLog(data.level, data.agent, data.message);
} else if (data.type === 'system-status') {
this.updateSystemStatus(data.status);
}
}
startPolling() {
// Poll for updates every 5 seconds
setInterval(() => {
this.fetchAgentStatus();
this.updateUptime();
}, 5000);
// Initial fetch
this.fetchAgentStatus();
}
async fetchAgentStatus() {
try {
const response = await fetch('/api/agents/status');
if (response.ok) {
const data = await response.json();
this.updateAllAgents(data);
}
} catch (error) {
console.error('Failed to fetch agent status:', error);
this.addLog('error', 'system', 'Failed to fetch agent status');
}
}
updateAllAgents(data) {
if (data.serverHealth) {
this.updateAgentMetrics('server-health', {
uptime: this.formatUptime(Date.now() - this.startTime),
lastCheck: 'Just now',
restarts: data.serverHealth.restarts || 0
});
}
if (data.apiMonitor) {
this.updateAgentMetrics('api-monitor', {
successRate: data.apiMonitor.successRate || '100%',
avgResponse: data.apiMonitor.avgResponse || '0ms',
autoFixes: data.apiMonitor.autoFixes || 0
});
}
if (data.uploadGuardian) {
this.updateAgentMetrics('upload-guardian', {
uploadCount: data.uploadGuardian.uploadCount || 0,
uploadFailed: data.uploadGuardian.uploadFailed || 0,
uploadFixed: data.uploadGuardian.uploadFixed || 0
});
}
if (data.sslCert) {
this.updateAgentMetrics('ssl-cert', {
sslExpiry: data.sslCert.expiresIn || 'Unknown',
sslStatus: data.sslCert.status || 'Valid',
sslRenewals: data.sslCert.renewals || 0
});
}
}
updateAgentMetrics(agentId, metrics) {
for (const [key, value] of Object.entries(metrics)) {
const element = document.getElementById(`${agentId.replace('-health', '')}-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`);
if (element) {
element.textContent = value;
}
}
}
updateUptime() {
const uptime = Date.now() - this.startTime;
const uptimeElement = document.getElementById('server-uptime');
if (uptimeElement) {
uptimeElement.textContent = this.formatUptime(uptime);
}
}
formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}d ${hours % 24}h`;
if (hours > 0) return `${hours}h ${minutes % 60}m`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
}
updateSystemStatus(status = 'healthy') {
const statusElement = document.getElementById('systemStatus');
const indicator = statusElement.querySelector('.status-indicator');
const text = statusElement.querySelector('.status-text');
indicator.className = 'status-indicator';
if (status === 'healthy') {
text.textContent = 'All Systems Operational';
// Green indicator (default)
} else if (status === 'warning') {
text.textContent = 'Minor Issues Detected';
indicator.classList.add('warning');
} else if (status === 'error') {
text.textContent = 'Critical Issues';
indicator.classList.add('error');
}
}
addLog(level, agent, message) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
agent,
message
};
this.logs.unshift(logEntry);
// Keep only last 1000 logs
if (this.logs.length > 1000) {
this.logs = this.logs.slice(0, 1000);
}
// Update logs view if it's active
if (document.getElementById('logsView').classList.contains('active')) {
this.renderLogs();
}
}
renderLogs() {
const container = document.getElementById('logsContainer');
const agentFilter = document.getElementById('logFilter').value;
const levelFilter = document.getElementById('logLevel').value;
let filteredLogs = this.logs;
if (agentFilter !== 'all') {
filteredLogs = filteredLogs.filter(log => log.agent === agentFilter);
}
if (levelFilter !== 'all') {
filteredLogs = filteredLogs.filter(log => log.level === levelFilter);
}
if (filteredLogs.length === 0) {
container.innerHTML = '<p style="color: var(--text-secondary);">No logs to display</p>';
return;
}
container.innerHTML = filteredLogs.map(log => `
<div class="log-entry ${log.level}">
<span class="log-timestamp">${new Date(log.timestamp).toLocaleTimeString()}</span>
<span class="log-agent">[${log.agent}]</span>
<span class="log-message">${log.message}</span>
</div>
`).join('');
}
updateMonitoringCharts() {
// Placeholder for charts - would integrate with Chart.js in production
document.getElementById('total-requests').textContent = this.logs.length;
document.getElementById('total-fixes').textContent =
this.logs.filter(log => log.level === 'fix').length;
document.getElementById('total-uptime').textContent = '99.9%';
document.getElementById('avg-response').textContent = '234ms';
}
loadSettings() {
const defaultSettings = {
autoRestart: true,
autoRetry: true,
autoRenewSSL: true,
autoCompressUploads: true,
serverCheckInterval: 30,
apiCheckInterval: 60,
sslCheckInterval: 24
};
try {
const saved = localStorage.getItem('agentSettings');
return saved ? { ...defaultSettings, ...JSON.parse(saved) } : defaultSettings;
} catch {
return defaultSettings;
}
}
saveSettings() {
const settings = {
autoRestart: document.getElementById('autoRestart').checked,
autoRetry: document.getElementById('autoRetry').checked,
autoRenewSSL: document.getElementById('autoRenewSSL').checked,
autoCompressUploads: document.getElementById('autoCompressUploads').checked,
serverCheckInterval: parseInt(document.getElementById('serverCheckInterval').value),
apiCheckInterval: parseInt(document.getElementById('apiCheckInterval').value),
sslCheckInterval: parseInt(document.getElementById('sslCheckInterval').value)
};
localStorage.setItem('agentSettings', JSON.stringify(settings));
this.settings = settings;
// Send to backend
fetch('/api/agents/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings)
});
this.addLog('info', 'system', 'Settings saved successfully');
alert('Settings saved successfully!');
}
}
// Global functions for button actions
let dashboard;
window.addEventListener('DOMContentLoaded', () => {
dashboard = new AgentDashboard();
});
function restartAgent(agentId) {
dashboard.addLog('info', agentId, 'Manual restart triggered');
fetch(`/api/agents/${agentId}/restart`, { method: 'POST' })
.then(() => dashboard.addLog('fix', agentId, 'Agent restarted successfully'))
.catch(() => dashboard.addLog('error', agentId, 'Failed to restart agent'));
}
function viewLogs(agentId) {
document.getElementById('logFilter').value = agentId;
dashboard.switchView('logs');
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
document.querySelector('[data-view="logs"]').classList.add('active');
dashboard.renderLogs();
}
function testAPI() {
dashboard.addLog('info', 'api-monitor', 'Running API health check...');
fetch('/api/agents/test-api', { method: 'POST' })
.then(res => res.json())
.then(data => {
if (data.success) {
dashboard.addLog('fix', 'api-monitor', `API test successful - ${data.responseTime}ms`);
} else {
dashboard.addLog('error', 'api-monitor', `API test failed: ${data.error}`);
}
})
.catch(() => dashboard.addLog('error', 'api-monitor', 'API test failed'));
}
function testUpload() {
dashboard.addLog('info', 'upload-guardian', 'Running upload test...');
// Simulate upload test
setTimeout(() => {
dashboard.addLog('fix', 'upload-guardian', 'Upload test completed successfully');
}, 1000);
}
function checkSSL() {
dashboard.addLog('info', 'ssl-cert', 'Checking SSL certificate...');
fetch('/api/agents/check-ssl', { method: 'POST' })
.then(res => res.json())
.then(data => {
dashboard.addLog('fix', 'ssl-cert', `SSL valid until: ${data.expiresIn}`);
})
.catch(() => dashboard.addLog('error', 'ssl-cert', 'SSL check failed'));
}
function clearLogs() {
if (confirm('Are you sure you want to clear all logs?')) {
dashboard.logs = [];
dashboard.renderLogs();
dashboard.addLog('info', 'system', 'Logs cleared');
}
}
function saveSettings() {
dashboard.saveSettings();
}
// Setup log filter listeners
document.addEventListener('DOMContentLoaded', () => {
const logFilter = document.getElementById('logFilter');
const logLevel = document.getElementById('logLevel');
if (logFilter) {
logFilter.addEventListener('change', () => dashboard.renderLogs());
}
if (logLevel) {
logLevel.addEventListener('change', () => dashboard.renderLogs());
}
});