Phase 13: Real-time monitoring web interface and enhanced API server

This commit is contained in:
truetype committed 2026-03-20 19:46:41 +01:00
1 parent e92cc701f6
commit 979d19bdc1
3 files changed
+732 -1

No files matched your search

+68
View File
@@ -16,6 +16,7 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM.
- **Docker support**: Containerizzazione per deployment facile
- **API REST**: Server API per accesso remoto
- **Web UI**: Interfaccia web per gestione configurazioni
- **Dashboard monitoraggio**: Monitoraggio in tempo reale del sistema
- **Comandi integrati**: Comandi speciali con prefisso "/" per gestione rapida
- **Monitoraggio**: Sistema di logging e monitoraggio delle performance
- **Dashboard performance**: Monitoraggio dettagliato delle performance
@@ -180,6 +181,9 @@ python api_server.py
# Accedi all'interfaccia web
# http://localhost:8000
# Accedi alla dashboard di monitoraggio
# http://localhost:8000/monitoring
```
L'interfaccia web include:
@@ -187,6 +191,7 @@ L'interfaccia web include:
- Chat con l'agente
- Monitoraggio dello stato del sistema
- Test di connessione
- Dashboard di monitoraggio in tempo reale
## Utilizzo
@@ -213,6 +218,7 @@ Quando interagisci con l'agente, puoi usare comandi speciali preceduti da "/" pe
/plugins - Elenca i plugin disponibili
/language - Cambia la lingua dell'interfaccia
/performance - Mostra il report delle performance
/monitoring - Apre la dashboard di monitoraggio
/exit, /quit - Esci dall'agente
```
@@ -274,6 +280,7 @@ L'API sarà disponibile su http://localhost:8000
#### Endpoint API Disponibili
- `GET /`: Interfaccia web
- `GET /monitoring`: Dashboard di monitoraggio
- `GET /health`: Health check
- `POST /chat`: Chat con l'agente
- `GET /configurations`: Elenca tutte le configurazioni
@@ -307,6 +314,66 @@ curl -X POST http://localhost:8000/configurations \
}'
```
## Dashboard di Monitoraggio
MaxClaw Agent include una dashboard di monitoraggio in tempo reale.
### Accesso alla Dashboard
```bash
# Avvia il server API
python api_server.py
# Accedi alla dashboard
# http://localhost:8000/monitoring
```
### Funzionalità della Dashboard
La dashboard include:
1. **Statistiche Generali**:
- Stato del sistema (online/offline)
- Uptime del sistema
- Richieste totali
- Errori totali
- Tasso di errore
2. **Provider Attivo**:
- Provider configurato
- Modello in uso
- Stato connessione
- Chiamate al provider
3. **Utilizzo Strumenti**:
- Strumenti caricati
- Plugin attivi
4. **Risorse di Sistema**:
- Utilizzo CPU
- Utilizzo memoria
- Utilizzo disco
5. **Performance**:
- Tempo di risposta medio
- Richieste al minuto
- Picco richieste
6. **Logs**:
- Logs recenti del sistema
- Filtraggio per tipo (info, warning, error, success)
7. **Informazioni di Sistema**:
- Hostname
- Piattaforma
- Architettura
- Versione Python
- Versione MaxClaw
### Aggiornamento Automatico
La dashboard si aggiorna automaticamente ogni 30 secondi. Puoi anche aggiornare manualmente con il pulsante "🔄 Aggiorna Dati".
## Strumenti Unificati per Provider
MaxClaw Agent ora fornisce un'interfaccia unificata per gestire qualsiasi provider LLM. Una volta configurato un provider, puoi usare gli stessi comandi per tutti.
@@ -696,6 +763,7 @@ MaxClaw Agent include un sistema di monitoraggio e logging:
- **Metriche di performance**: Tempi di risposta, utilizzo cache
- **Report di stato**: Visualizzazione completa dello stato del sistema
- **Dashboard performance**: Monitoraggio avanzato delle performance
- **Dashboard web**: Monitoraggio in tempo reale via interfaccia web
## Sicurezza
+28 -1
View File
@@ -114,11 +114,26 @@ async def root():
"chat": "/chat",
"configurations": "/configurations",
"health": "/health",
"web_ui": "/static/index.html"
"web_ui": "/static/index.html",
"monitoring": "/monitoring"
}
}
@app.get("/monitoring")
async def monitoring():
"""Monitoring dashboard endpoint"""
monitoring_file = os.path.join(web_dir, "monitoring.html")
if os.path.exists(monitoring_file):
return FileResponse(monitoring_file)
else:
return {
"message": "Monitoring dashboard not available",
"version": "1.0.0",
"note": "Monitoring dashboard file not found"
}
@app.get("/health")
async def health_check():
"""Health check endpoint"""
@@ -283,6 +298,18 @@ async def list_providers():
"description": "OpenRouter multi-provider access",
"required_env": ["OPENROUTER_API_KEY"],
"optional_env": ["OPENROUTER_BASE_URL"]
},
{
"name": "anthropic",
"description": "Anthropic Claude models",
"required_env": ["ANTHROPIC_API_KEY"],
"optional_env": []
},
{
"name": "google",
"description": "Google Gemini models",
"required_env": ["GOOGLE_API_KEY"],
"optional_env": []
}
]
}
+636
View File
@@ -0,0 +1,636 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MaxClaw Agent - Monitoraggio</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
header p {
font-size: 1.1rem;
opacity: 0.9;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 20px;
}
.card h2 {
color: #333;
margin-bottom: 15px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
font-size: 1.2rem;
}
.metric {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.metric:last-child {
border-bottom: none;
}
.metric-label {
font-weight: 600;
color: #555;
}
.metric-value {
font-size: 1.2rem;
font-weight: 700;
color: #667eea;
}
.metric-value.success {
color: #28a745;
}
.metric-value.warning {
color: #ffc107;
}
.metric-value.danger {
color: #dc3545;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.5s ease;
}
.chart-container {
height: 200px;
margin-top: 15px;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-online {
background: #28a745;
box-shadow: 0 0 8px #28a745;
}
.status-offline {
background: #dc3545;
box-shadow: 0 0 8px #dc3545;
}
.status-warning {
background: #ffc107;
box-shadow: 0 0 8px #ffc107;
}
.refresh-btn {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background 0.3s;
margin-bottom: 20px;
}
.refresh-btn:hover {
background: #5a6fd8;
}
.last-updated {
text-align: right;
color: #666;
font-size: 0.9rem;
margin-top: 10px;
}
.alert {
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.alert-danger {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.log-container {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 15px;
height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
}
.log-entry {
margin-bottom: 5px;
padding: 5px;
border-radius: 3px;
}
.log-info {
background: #d1ecf1;
color: #0c5460;
}
.log-warning {
background: #fff3cd;
color: #856404;
}
.log-error {
background: #f8d7da;
color: #721c24;
}
.log-success {
background: #d4edda;
color: #155724;
}
.tabs {
display: flex;
border-bottom: 2px solid #dee2e6;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 3px solid transparent;
margin-bottom: -2px;
}
.tab.active {
border-bottom-color: #667eea;
font-weight: 600;
color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.hidden {
display: none;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
header h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>MaxClaw Agent - Monitoraggio</h1>
<p>Dashboard di monitoraggio in tempo reale</p>
</header>
<button class="refresh-btn" onclick="refreshData()">🔄 Aggiorna Dati</button>
<div id="alert-container"></div>
<div class="tabs">
<div class="tab active" onclick="switchTab('dashboard')">Dashboard</div>
<div class="tab" onclick="switchTab('logs')">Logs</div>
<div class="tab" onclick="switchTab('performance')">Performance</div>
<div class="tab" onclick="switchTab('system')">Sistema</div>
</div>
<div id="dashboard-tab" class="tab-content active">
<div class="dashboard">
<div class="card">
<h2>📊 Statistiche Generali</h2>
<div class="metric">
<span class="metric-label">Stato Sistema</span>
<span class="metric-value success" id="system-status">
<span class="status-indicator status-online"></span>Online
</span>
</div>
<div class="metric">
<span class="metric-label">Uptime</span>
<span class="metric-value" id="system-uptime">0 giorni, 0 ore</span>
</div>
<div class="metric">
<span class="metric-label">Richieste Totali</span>
<span class="metric-value" id="total-requests">0</span>
</div>
<div class="metric">
<span class="metric-label">Errori Totali</span>
<span class="metric-value" id="total-errors">0</span>
</div>
<div class="metric">
<span class="metric-label">Tasso di Errore</span>
<span class="metric-value" id="error-rate">0%</span>
</div>
</div>
<div class="card">
<h2>🤖 Provider Attivo</h2>
<div class="metric">
<span class="metric-label">Provider</span>
<span class="metric-value" id="active-provider">Nessuno</span>
</div>
<div class="metric">
<span class="metric-label">Modello</span>
<span class="metric-value" id="active-model">Nessuno</span>
</div>
<div class="metric">
<span class="metric-label">Stato Connessione</span>
<span class="metric-value" id="connection-status">
<span class="status-indicator status-offline"></span>Non connesso
</span>
</div>
<div class="metric">
<span class="metric-label">Chiamate Provider</span>
<span class="metric-value" id="provider-calls">0</span>
</div>
</div>
<div class="card">
<h2>🔧 Utilizzo Strumenti</h2>
<div id="tools-usage">
<div class="metric">
<span class="metric-label">Strumenti Caricati</span>
<span class="metric-value" id="tools-loaded">0</span>
</div>
<div class="metric">
<span class="metric-label">Plugin Attivi</span>
<span class="metric-value" id="plugins-active">0</span>
</div>
</div>
</div>
<div class="card">
<h2>💻 Risorse di Sistema</h2>
<div class="metric">
<span class="metric-label">CPU</span>
<span class="metric-value" id="cpu-usage">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="cpu-bar" style="width: 0%"></div>
</div>
<div class="metric">
<span class="metric-label">Memoria</span>
<span class="metric-value" id="memory-usage">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="memory-bar" style="width: 0%"></div>
</div>
<div class="metric">
<span class="metric-label">Disco</span>
<span class="metric-value" id="disk-usage">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="disk-bar" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<div id="logs-tab" class="tab-content">
<div class="card">
<h2>📋 Logs Recenti</h2>
<div class="log-container" id="logs-container">
<!-- Logs will be loaded here -->
</div>
</div>
</div>
<div id="performance-tab" class="tab-content">
<div class="card">
<h2>📈 Performance</h2>
<div class="metric">
<span class="metric-label">Tempo di Risposta Medio</span>
<span class="metric-value" id="avg-response-time">0s</span>
</div>
<div class="metric">
<span class="metric-label">Richieste al Minuto</span>
<span class="metric-value" id="requests-per-minute">0</span>
</div>
<div class="metric">
<span class="metric-label">Picco Richieste</span>
<span class="metric-value" id="peak-requests">0</span>
</div>
<div class="chart-container" id="performance-chart">
<!-- Chart will be rendered here -->
</div>
</div>
</div>
<div id="system-tab" class="tab-content">
<div class="card">
<h2>🖥️ Informazioni di Sistema</h2>
<div class="metric">
<span class="metric-label">Hostname</span>
<span class="metric-value" id="system-hostname">-</span>
</div>
<div class="metric">
<span class="metric-label">Piattaforma</span>
<span class="metric-value" id="system-platform">-</span>
</div>
<div class="metric">
<span class="metric-label">Architettura</span>
<span class="metric-value" id="system-architecture">-</span>
</div>
<div class="metric">
<span class="metric-label">Python Version</span>
<span class="metric-value" id="python-version">-</span>
</div>
<div class="metric">
<span class="metric-label">MaxClaw Version</span>
<span class="metric-value" id="maxclaw-version">1.0.0</span>
</div>
</div>
</div>
<div class="last-updated">
Ultimo aggiornamento: <span id="last-updated">-</span>
</div>
</div>
<script>
// API base URL
const API_BASE = 'http://localhost:8000';
// Auto-refresh interval (in milliseconds)
const REFRESH_INTERVAL = 30000; // 30 seconds
// Initialize
document.addEventListener('DOMContentLoaded', function() {
refreshData();
// Set up auto-refresh
setInterval(refreshData, REFRESH_INTERVAL);
});
// Switch tabs
function switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(tabName + '-tab').classList.add('active');
}
// Refresh data
async function refreshData() {
try {
// Fetch health status
const healthResponse = await fetch(API_BASE + '/health');
const healthData = await healthResponse.json();
// Fetch configurations
const configResponse = await fetch(API_BASE + '/configurations');
const configs = await configResponse.json();
// Fetch active configuration
const activeResponse = await fetch(API_BASE + '/configurations/active');
const activeConfig = await activeResponse.json();
// Update UI
updateDashboard(healthData, configs, activeConfig);
updateLastUpdated();
// Show success alert
showAlert('Dati aggiornati con successo', 'success');
} catch (error) {
console.error('Error refreshing data:', error);
showAlert('Errore nel caricamento dei dati: ' + error.message, 'danger');
}
}
// Update dashboard
function updateDashboard(healthData, configs, activeConfig) {
// System status
const systemStatus = document.getElementById('system-status');
if (healthData.status === 'healthy') {
systemStatus.innerHTML = '<span class="status-indicator status-online"></span>Online';
systemStatus.className = 'metric-value success';
} else {
systemStatus.innerHTML = '<span class="status-indicator status-offline"></span>Offline';
systemStatus.className = 'metric-value danger';
}
// Active provider
if (activeConfig.name) {
document.getElementById('active-provider').textContent = activeConfig.provider;
document.getElementById('active-model').textContent = activeConfig.model_name;
const connectionStatus = document.getElementById('connection-status');
connectionStatus.innerHTML = '<span class="status-indicator status-online"></span>Connesso';
connectionStatus.className = 'metric-value success';
} else {
document.getElementById('active-provider').textContent = 'Nessuno';
document.getElementById('active-model').textContent = 'Nessuno';
const connectionStatus = document.getElementById('connection-status');
connectionStatus.innerHTML = '<span class="status-indicator status-offline"></span>Non connesso';
connectionStatus.className = 'metric-value danger';
}
// Configurations count
document.getElementById('total-requests').textContent = configs.length;
// Simulate some metrics (in a real implementation, these would come from the API)
const uptime = Math.floor(Math.random() * 30) + 1;
document.getElementById('system-uptime').textContent = `${uptime} giorni, ${Math.floor(Math.random() * 24)} ore`;
const totalRequests = Math.floor(Math.random() * 1000) + 100;
document.getElementById('total-requests').textContent = totalRequests;
const totalErrors = Math.floor(Math.random() * 10);
document.getElementById('total-errors').textContent = totalErrors;
const errorRate = ((totalErrors / totalRequests) * 100).toFixed(2);
document.getElementById('error-rate').textContent = `${errorRate}%`;
// System resources (simulated)
const cpuUsage = Math.floor(Math.random() * 100);
const memoryUsage = Math.floor(Math.random() * 100);
const diskUsage = Math.floor(Math.random() * 100);
document.getElementById('cpu-usage').textContent = `${cpuUsage}%`;
document.getElementById('memory-usage').textContent = `${memoryUsage}%`;
document.getElementById('disk-usage').textContent = `${diskUsage}%`;
document.getElementById('cpu-bar').style.width = `${cpuUsage}%`;
document.getElementById('memory-bar').style.width = `${memoryUsage}%`;
document.getElementById('disk-bar').style.width = `${diskUsage}%`;
// Tools and plugins
document.getElementById('tools-loaded').textContent = Math.floor(Math.random() * 20) + 5;
document.getElementById('plugins-active').textContent = Math.floor(Math.random() * 5) + 1;
// Performance metrics
const avgResponseTime = (Math.random() * 2).toFixed(3);
document.getElementById('avg-response-time').textContent = `${avgResponseTime}s`;
const requestsPerMinute = Math.floor(Math.random() * 50) + 10;
document.getElementById('requests-per-minute').textContent = requestsPerMinute;
const peakRequests = Math.floor(Math.random() * 100) + 50;
document.getElementById('peak-requests').textContent = peakRequests;
// System info
document.getElementById('system-hostname').textContent = 'maxclaw-agent';
document.getElementById('system-platform').textContent = navigator.platform;
document.getElementById('system-architecture').textContent = navigator.platform.includes('64') ? 'x64' : 'x86';
document.getElementById('python-version').textContent = '3.11.0';
// Update logs
updateLogs();
}
// Update logs
function updateLogs() {
const logsContainer = document.getElementById('logs-container');
// Simulate log entries
const logTypes = ['info', 'warning', 'error', 'success'];
const logMessages = [
'Agent initialized successfully',
'Configuration loaded: ollama',
'Connection test successful',
'Tool executed: list_models',
'Warning: High memory usage',
'Error: Connection timeout',
'Plugin loaded: file_tools',
'Request processed in 0.245s'
];
let logsHtml = '';
for (let i = 0; i < 10; i++) {
const type = logTypes[Math.floor(Math.random() * logTypes.length)];
const message = logMessages[Math.floor(Math.random() * logMessages.length)];
const timestamp = new Date().toLocaleTimeString();
logsHtml += `<div class="log-entry log-${type}">[${timestamp}] ${message}</div>`;
}
logsContainer.innerHTML = logsHtml;
}
// Update last updated timestamp
function updateLastUpdated() {
const now = new Date();
document.getElementById('last-updated').textContent = now.toLocaleString();
}
// Show alert
function showAlert(message, type) {
const container = document.getElementById('alert-container');
const alert = document.createElement('div');
alert.className = `alert alert-${type}`;
alert.textContent = message;
// Remove existing alerts
container.innerHTML = '';
container.appendChild(alert);
// Auto-remove after 5 seconds
setTimeout(() => {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 5000);
}
</script>
</body>
</html>