feat(dashboard): Ajouter sections Sauvegardes, Système, Corrections et Apprentissage
- Section Sauvegardes: export par catégorie (workflows, corrections, modèles, config) et backup complet - Section Système: infos version, Python, OS, points de restauration, upload packages mise à jour - Section Corrections: gestion des Correction Packs (liste, création, export, stats) - Section Apprentissage: stats corpus FAISS, sessions traitées, graphiques évolution, top corrections Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,10 @@
|
|||||||
<div class="tab" onclick="switchTab('metrics')">📊 Métriques</div>
|
<div class="tab" onclick="switchTab('metrics')">📊 Métriques</div>
|
||||||
<div class="tab" onclick="switchTab('logs')">📄 Logs</div>
|
<div class="tab" onclick="switchTab('logs')">📄 Logs</div>
|
||||||
<div class="tab" onclick="switchTab('tests')">🧪 Tests</div>
|
<div class="tab" onclick="switchTab('tests')">🧪 Tests</div>
|
||||||
|
<div class="tab" onclick="switchTab('backups')">💾 Sauvegardes</div>
|
||||||
|
<div class="tab" onclick="switchTab('system')">⚙️ Système</div>
|
||||||
|
<div class="tab" onclick="switchTab('corrections')">🔧 Corrections</div>
|
||||||
|
<div class="tab" onclick="switchTab('learning')">🧠 Apprentissage</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -394,6 +398,270 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab: Sauvegardes -->
|
||||||
|
<div id="tab-backups" class="tab-content">
|
||||||
|
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">💾</span> Sauvegardes & Export</h2>
|
||||||
|
<p style="color:#64748b;font-size:13px;">Exportez et téléchargez vos données pour la sauvegarde ou le transfert</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" onclick="refreshBackupStats()">🔄 Actualiser</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-4" id="backupStats">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statWorkflowsBackup">-</div>
|
||||||
|
<div class="stat-label">Workflows</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statCorrectionsBackup">-</div>
|
||||||
|
<div class="stat-label">Corrections</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statModelsBackup">-</div>
|
||||||
|
<div class="stat-label">Modèles</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statSessionsBackup">-</div>
|
||||||
|
<div class="stat-label">Sessions</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">📦</span> Export par catégorie</h2>
|
||||||
|
<div class="backup-list">
|
||||||
|
<div class="backup-item" onclick="downloadBackup('workflows')">
|
||||||
|
<div class="backup-icon">🔄</div>
|
||||||
|
<div class="backup-info">
|
||||||
|
<h4>Workflows</h4>
|
||||||
|
<p>Tous les workflows et templates</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
|
||||||
|
</div>
|
||||||
|
<div class="backup-item" onclick="downloadBackup('correction-packs')">
|
||||||
|
<div class="backup-icon">🔧</div>
|
||||||
|
<div class="backup-info">
|
||||||
|
<h4>Correction Packs</h4>
|
||||||
|
<p>Packs de corrections cross-workflow</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
|
||||||
|
</div>
|
||||||
|
<div class="backup-item" onclick="downloadBackup('trained-models')">
|
||||||
|
<div class="backup-icon">🧠</div>
|
||||||
|
<div class="backup-info">
|
||||||
|
<h4>Modèles entraînés</h4>
|
||||||
|
<p>Index FAISS et embeddings</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
|
||||||
|
</div>
|
||||||
|
<div class="backup-item" onclick="downloadBackup('config')">
|
||||||
|
<div class="backup-icon">⚙️</div>
|
||||||
|
<div class="backup-info">
|
||||||
|
<h4>Configuration</h4>
|
||||||
|
<p>Paramètres système (secrets masqués)</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">📁</span> Backup complet</h2>
|
||||||
|
<div style="padding:20px;text-align:center;">
|
||||||
|
<div style="font-size:48px;margin-bottom:20px;">📥</div>
|
||||||
|
<p style="color:#94a3b8;margin-bottom:20px;">Téléchargez une archive complète de toutes vos données</p>
|
||||||
|
<button class="btn btn-success" style="font-size:16px;padding:15px 30px;" onclick="downloadFullBackup()">
|
||||||
|
⬇️ Télécharger le backup complet
|
||||||
|
</button>
|
||||||
|
<p style="color:#64748b;font-size:12px;margin-top:15px;">Inclut: workflows, corrections, modèles, configuration</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab: Système -->
|
||||||
|
<div id="tab-system" class="tab-content">
|
||||||
|
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">⚙️</span> Système & Version</h2>
|
||||||
|
<p style="color:#64748b;font-size:13px;">Informations système, mises à jour et points de restauration</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" onclick="refreshSystemInfo()">🔄 Actualiser</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-3">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="sysVersion">-</div>
|
||||||
|
<div class="stat-label">Version</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="sysCommit">-</div>
|
||||||
|
<div class="stat-label">Commit</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div id="sysUpdateStatus" style="font-size:24px;">⏳</div>
|
||||||
|
<div class="stat-label">Mise à jour</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">🖥️</span> Informations système</h2>
|
||||||
|
<div class="system-info" id="systemInfoDetails">
|
||||||
|
<div class="loading"><div class="spinner"></div>Chargement...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">⏮️</span> Points de restauration</h2>
|
||||||
|
<div class="actions-bar">
|
||||||
|
<button class="btn btn-success btn-small" onclick="createVersionBackup()">➕ Créer un point</button>
|
||||||
|
</div>
|
||||||
|
<div class="backup-list" id="versionBackupsList">
|
||||||
|
<div class="loading"><div class="spinner"></div>Chargement...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:20px;">
|
||||||
|
<h2><span class="icon">📤</span> Mise à jour manuelle</h2>
|
||||||
|
<div style="display:flex;gap:20px;align-items:center;flex-wrap:wrap;">
|
||||||
|
<div style="flex:1;min-width:300px;">
|
||||||
|
<p style="color:#94a3b8;margin-bottom:15px;">Uploadez un package de mise à jour (.zip) contenant un manifest valide</p>
|
||||||
|
<input type="file" id="updateFileInput" accept=".zip" style="display:none;" onchange="uploadUpdatePackage()">
|
||||||
|
<button class="btn btn-warning" onclick="document.getElementById('updateFileInput').click()">
|
||||||
|
📤 Uploader un package
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="updatePackageInfo" style="flex:1;min-width:300px;padding:15px;background:#0f172a;border-radius:8px;display:none;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab: Correction Packs -->
|
||||||
|
<div id="tab-corrections" class="tab-content">
|
||||||
|
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🔧</span> Correction Packs</h2>
|
||||||
|
<p style="color:#64748b;font-size:13px;">Capitalisez les corrections utilisateur pour améliorer l'auto-healing cross-workflow</p>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px;">
|
||||||
|
<button class="btn btn-success" onclick="showCreatePackModal()">➕ Nouveau Pack</button>
|
||||||
|
<button class="btn btn-primary" onclick="refreshCorrectionPacks()">🔄 Actualiser</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-4">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statPacks">-</div>
|
||||||
|
<div class="stat-label">Packs</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statTotalCorrections">-</div>
|
||||||
|
<div class="stat-label">Corrections</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statApplications">-</div>
|
||||||
|
<div class="stat-label">Applications</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statSuccessRate">-</div>
|
||||||
|
<div class="stat-label">Taux succès</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">📦</span> Packs disponibles</h2>
|
||||||
|
<div class="correction-packs-list" id="correctionPacksList">
|
||||||
|
<div class="loading"><div class="spinner"></div>Chargement...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal création pack -->
|
||||||
|
<div id="createPackModal" class="modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:500px;">
|
||||||
|
<span class="modal-close" onclick="closeCreatePackModal()">×</span>
|
||||||
|
<h3>➕ Créer un nouveau pack</h3>
|
||||||
|
<div style="margin-top:20px;">
|
||||||
|
<label style="display:block;color:#94a3b8;margin-bottom:5px;">Nom du pack</label>
|
||||||
|
<input type="text" id="packName" class="select-input" placeholder="Ex: Corrections SAP">
|
||||||
|
<label style="display:block;color:#94a3b8;margin:15px 0 5px;">Description</label>
|
||||||
|
<textarea id="packDescription" class="select-input" rows="3" placeholder="Description optionnelle..."></textarea>
|
||||||
|
<label style="display:block;color:#94a3b8;margin:15px 0 5px;">Catégorie</label>
|
||||||
|
<input type="text" id="packCategory" class="select-input" placeholder="Ex: erp, web, desktop">
|
||||||
|
<div style="display:flex;gap:10px;margin-top:20px;">
|
||||||
|
<button class="btn btn-success" onclick="createCorrectionPack()">✅ Créer</button>
|
||||||
|
<button class="btn btn-secondary" onclick="closeCreatePackModal()">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab: Apprentissage -->
|
||||||
|
<div id="tab-learning" class="tab-content">
|
||||||
|
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🧠</span> Apprentissage & Corpus</h2>
|
||||||
|
<p style="color:#64748b;font-size:13px;">Statistiques d'apprentissage et évolution du corpus de données</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" onclick="refreshLearningStats()">🔄 Actualiser</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-4">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statCorpusSize">-</div>
|
||||||
|
<div class="stat-label">Taille corpus</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statTrainedSessions">-</div>
|
||||||
|
<div class="stat-label">Sessions traitées</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statHealingSuccess">-</div>
|
||||||
|
<div class="stat-label">Self-healing</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="statLearningRate">-</div>
|
||||||
|
<div class="stat-label">Taux apprentissage</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">📈</span> Évolution du corpus</h2>
|
||||||
|
<canvas id="corpusChart" height="250"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">🎯</span> Performance self-healing</h2>
|
||||||
|
<canvas id="healingChart" height="250"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">📊</span> Répartition par type d'action</h2>
|
||||||
|
<div id="actionTypeStats" style="padding:15px;">
|
||||||
|
<div class="loading"><div class="spinner"></div>Chargement...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2><span class="icon">🏆</span> Top corrections appliquées</h2>
|
||||||
|
<div id="topCorrections" style="padding:15px;">
|
||||||
|
<div class="loading"><div class="spinner"></div>Chargement...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -494,6 +762,49 @@
|
|||||||
|
|
||||||
.btn-secondary { background: #475569; color: white; }
|
.btn-secondary { background: #475569; color: white; }
|
||||||
.btn-secondary:hover { background: #64748b; }
|
.btn-secondary:hover { background: #64748b; }
|
||||||
|
|
||||||
|
/* Backup section styles */
|
||||||
|
.backup-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.backup-item { display: flex; align-items: center; gap: 15px; padding: 15px; background: #0f172a; border-radius: 10px; border: 1px solid #334155; cursor: pointer; transition: all 0.2s; }
|
||||||
|
.backup-item:hover { border-color: #3b82f6; transform: translateX(5px); }
|
||||||
|
.backup-icon { font-size: 28px; }
|
||||||
|
.backup-info { flex: 1; }
|
||||||
|
.backup-info h4 { color: #e2e8f0; margin-bottom: 3px; }
|
||||||
|
.backup-info p { color: #64748b; font-size: 12px; }
|
||||||
|
|
||||||
|
/* System info styles */
|
||||||
|
.system-info { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.system-info-row { display: flex; justify-content: space-between; padding: 10px 15px; background: #0f172a; border-radius: 6px; }
|
||||||
|
.system-info-row span:first-child { color: #64748b; }
|
||||||
|
.system-info-row span:last-child { color: #e2e8f0; font-weight: 500; }
|
||||||
|
|
||||||
|
/* Correction packs styles */
|
||||||
|
.correction-packs-list { display: flex; flex-direction: column; gap: 12px; }
|
||||||
|
.correction-pack-item { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: #0f172a; border-radius: 10px; border: 1px solid #334155; transition: all 0.2s; }
|
||||||
|
.correction-pack-item:hover { border-color: #3b82f6; }
|
||||||
|
.pack-main { display: flex; align-items: center; gap: 15px; flex: 1; }
|
||||||
|
.pack-icon { font-size: 32px; }
|
||||||
|
.pack-details h4 { color: #e2e8f0; margin-bottom: 5px; }
|
||||||
|
.pack-details p { color: #64748b; font-size: 12px; }
|
||||||
|
.pack-stats { display: flex; gap: 20px; }
|
||||||
|
.pack-stat { text-align: center; }
|
||||||
|
.pack-stat .value { font-size: 18px; font-weight: bold; color: #3b82f6; }
|
||||||
|
.pack-stat .label { font-size: 10px; color: #64748b; text-transform: uppercase; }
|
||||||
|
.pack-actions { display: flex; gap: 8px; margin-left: 20px; }
|
||||||
|
|
||||||
|
/* Learning stats styles */
|
||||||
|
.progress-bar { height: 8px; background: #334155; border-radius: 4px; overflow: hidden; margin-top: 8px; }
|
||||||
|
.progress-fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #8b5cf6); border-radius: 4px; transition: width 0.5s ease; }
|
||||||
|
.stat-bar { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: #0f172a; border-radius: 8px; margin-bottom: 8px; }
|
||||||
|
.stat-bar-label { color: #94a3b8; }
|
||||||
|
.stat-bar-value { color: #3b82f6; font-weight: 600; }
|
||||||
|
|
||||||
|
/* Version backup item */
|
||||||
|
.version-backup-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: #0f172a; border-radius: 8px; margin-bottom: 8px; border: 1px solid #334155; }
|
||||||
|
.version-backup-item:hover { border-color: #3b82f6; }
|
||||||
|
.version-backup-info { flex: 1; }
|
||||||
|
.version-backup-info h5 { color: #e2e8f0; margin-bottom: 3px; font-weight: 500; }
|
||||||
|
.version-backup-info span { color: #64748b; font-size: 12px; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -572,6 +883,10 @@
|
|||||||
if (tabName === 'performance') refreshPerformance();
|
if (tabName === 'performance') refreshPerformance();
|
||||||
if (tabName === 'metrics') { refreshMetrics(); refreshAutomationStatus(); }
|
if (tabName === 'metrics') { refreshMetrics(); refreshAutomationStatus(); }
|
||||||
if (tabName === 'logs') refreshLogs();
|
if (tabName === 'logs') refreshLogs();
|
||||||
|
if (tabName === 'backups') refreshBackupStats();
|
||||||
|
if (tabName === 'system') refreshSystemInfo();
|
||||||
|
if (tabName === 'corrections') refreshCorrectionPacks();
|
||||||
|
if (tabName === 'learning') refreshLearningStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update execution UI
|
// Update execution UI
|
||||||
@@ -1680,6 +1995,527 @@
|
|||||||
setTimeout(() => div.remove(), 4000);
|
setTimeout(() => div.remove(), 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SECTION: Sauvegardes / Backups
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function refreshBackupStats() {
|
||||||
|
try {
|
||||||
|
const data = await fetchJSON('/api/backup/stats');
|
||||||
|
document.getElementById('statWorkflowsBackup').textContent = data.categories?.workflows?.count || 0;
|
||||||
|
document.getElementById('statCorrectionsBackup').textContent = data.categories?.correction_packs?.count || 0;
|
||||||
|
document.getElementById('statModelsBackup').textContent = data.categories?.trained_models?.count || 0;
|
||||||
|
document.getElementById('statSessionsBackup').textContent = data.categories?.sessions?.count || 0;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading backup stats:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadBackup(type) {
|
||||||
|
showNotification(`⬇️ Téléchargement ${type}...`, 'info');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/backup/${type}`);
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `rpa_${type}_${new Date().toISOString().split('T')[0]}.zip`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showNotification(`✅ Backup ${type} téléchargé`, 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFullBackup() {
|
||||||
|
showNotification('⬇️ Génération du backup complet...', 'info');
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/backup/full', { method: 'POST' });
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `rpa_full_backup_${new Date().toISOString().split('T')[0]}.zip`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showNotification('✅ Backup complet téléchargé', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SECTION: Système / Version
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function refreshSystemInfo() {
|
||||||
|
try {
|
||||||
|
// Version info
|
||||||
|
const version = await fetchJSON('/api/version');
|
||||||
|
document.getElementById('sysVersion').textContent = version.version || '-';
|
||||||
|
document.getElementById('sysCommit').textContent = (version.git_commit || '-').substring(0, 7);
|
||||||
|
|
||||||
|
// System info
|
||||||
|
const sysInfo = await fetchJSON('/api/version/system-info');
|
||||||
|
const infoDiv = document.getElementById('systemInfoDetails');
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<div class="system-info-row"><span>Python</span><span>${sysInfo.python_version || '-'}</span></div>
|
||||||
|
<div class="system-info-row"><span>OS</span><span>${sysInfo.os || '-'}</span></div>
|
||||||
|
<div class="system-info-row"><span>Architecture</span><span>${sysInfo.architecture || '-'}</span></div>
|
||||||
|
<div class="system-info-row"><span>CPU Cores</span><span>${sysInfo.cpu_count || '-'}</span></div>
|
||||||
|
<div class="system-info-row"><span>Mémoire</span><span>${sysInfo.memory_total ? Math.round(sysInfo.memory_total / 1024 / 1024 / 1024) + ' GB' : '-'}</span></div>
|
||||||
|
<div class="system-info-row"><span>Démarré le</span><span>${version.build_date ? new Date(version.build_date).toLocaleString('fr-FR') : '-'}</span></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
const update = await fetchJSON('/api/version/check-update');
|
||||||
|
if (update.update_available) {
|
||||||
|
document.getElementById('sysUpdateStatus').innerHTML = `<span style="color:#f59e0b;">⬆️ ${update.update?.version}</span>`;
|
||||||
|
} else {
|
||||||
|
document.getElementById('sysUpdateStatus').innerHTML = '<span style="color:#22c55e;">✅ À jour</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version backups
|
||||||
|
await refreshVersionBackups();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading system info:', e);
|
||||||
|
document.getElementById('systemInfoDetails').innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshVersionBackups() {
|
||||||
|
try {
|
||||||
|
const data = await fetchJSON('/api/version/backups');
|
||||||
|
const list = document.getElementById('versionBackupsList');
|
||||||
|
|
||||||
|
if (!data.backups || data.backups.length === 0) {
|
||||||
|
list.innerHTML = '<p style="color:#64748b;text-align:center;padding:20px;">Aucun point de restauration</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = data.backups.slice(0, 10).map(b => `
|
||||||
|
<div class="version-backup-item">
|
||||||
|
<div class="version-backup-info">
|
||||||
|
<h5>${b.label || 'Point de restauration'}</h5>
|
||||||
|
<span>${new Date(b.created_at || b.timestamp).toLocaleString('fr-FR')}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-warning btn-small" onclick="restoreVersion('${b.id || b.path}')">⏮️ Restaurer</button>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('versionBackupsList').innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createVersionBackup() {
|
||||||
|
const label = prompt('Nom du point de restauration (optionnel):');
|
||||||
|
showNotification('💾 Création du point de restauration...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchJSON('/api/version/create-backup', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ label })
|
||||||
|
});
|
||||||
|
showNotification('✅ Point de restauration créé', 'success');
|
||||||
|
await refreshVersionBackups();
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreVersion(backupId) {
|
||||||
|
if (!confirm('Restaurer ce point ? Les données actuelles seront sauvegardées automatiquement.')) return;
|
||||||
|
|
||||||
|
showNotification('⏮️ Restauration en cours...', 'warning');
|
||||||
|
try {
|
||||||
|
await fetchJSON(`/api/version/rollback/${backupId}`, { method: 'POST' });
|
||||||
|
showNotification('✅ Restauration effectuée. Redémarrage recommandé.', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadUpdatePackage() {
|
||||||
|
const input = document.getElementById('updateFileInput');
|
||||||
|
if (!input.files.length) return;
|
||||||
|
|
||||||
|
const file = input.files[0];
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
showNotification('📤 Upload du package...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/version/upload-update', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
|
||||||
|
const infoDiv = document.getElementById('updatePackageInfo');
|
||||||
|
infoDiv.style.display = 'block';
|
||||||
|
infoDiv.innerHTML = `
|
||||||
|
<p style="color:#22c55e;margin-bottom:10px;">✅ Package uploadé</p>
|
||||||
|
<p style="color:#94a3b8;font-size:13px;">Version: ${data.manifest?.version || '-'}</p>
|
||||||
|
<p style="color:#94a3b8;font-size:13px;">${data.manifest?.description || ''}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
showNotification('✅ Package uploadé avec succès', 'success');
|
||||||
|
await refreshSystemInfo();
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SECTION: Correction Packs
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function refreshCorrectionPacks() {
|
||||||
|
try {
|
||||||
|
// Essayer d'abord l'API VWB (port 5000)
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await fetchJSON('http://localhost:5000/api/correction-packs/stats');
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback sur les stats de backup
|
||||||
|
const backup = await fetchJSON('/api/backup/stats');
|
||||||
|
data = {
|
||||||
|
total_packs: backup.categories?.correction_packs?.count || 0,
|
||||||
|
total_corrections: 0,
|
||||||
|
total_applications: 0,
|
||||||
|
overall_success_rate: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('statPacks').textContent = data.total_packs || 0;
|
||||||
|
document.getElementById('statTotalCorrections').textContent = data.total_corrections || 0;
|
||||||
|
document.getElementById('statApplications').textContent = data.total_applications || 0;
|
||||||
|
document.getElementById('statSuccessRate').textContent = data.overall_success_rate ?
|
||||||
|
Math.round(data.overall_success_rate * 100) + '%' : '-';
|
||||||
|
|
||||||
|
// Charger la liste des packs
|
||||||
|
await loadCorrectionPacksList();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading correction packs:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCorrectionPacksList() {
|
||||||
|
const list = document.getElementById('correctionPacksList');
|
||||||
|
|
||||||
|
try {
|
||||||
|
let packs;
|
||||||
|
try {
|
||||||
|
const data = await fetchJSON('http://localhost:5000/api/correction-packs');
|
||||||
|
packs = data.packs || [];
|
||||||
|
} catch (e) {
|
||||||
|
packs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packs.length === 0) {
|
||||||
|
list.innerHTML = `
|
||||||
|
<div style="text-align:center;padding:40px;color:#64748b;">
|
||||||
|
<div style="font-size:48px;margin-bottom:15px;">📦</div>
|
||||||
|
<p>Aucun pack de corrections</p>
|
||||||
|
<p style="font-size:12px;margin-top:10px;">Créez un pack pour capitaliser les corrections utilisateur</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.innerHTML = packs.map(pack => `
|
||||||
|
<div class="correction-pack-item">
|
||||||
|
<div class="pack-main">
|
||||||
|
<div class="pack-icon">📦</div>
|
||||||
|
<div class="pack-details">
|
||||||
|
<h4>${pack.name || 'Pack sans nom'}</h4>
|
||||||
|
<p>${pack.description || 'Pas de description'} • ${pack.category || 'général'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pack-stats">
|
||||||
|
<div class="pack-stat">
|
||||||
|
<div class="value">${pack.corrections_count || 0}</div>
|
||||||
|
<div class="label">Corrections</div>
|
||||||
|
</div>
|
||||||
|
<div class="pack-stat">
|
||||||
|
<div class="value">${pack.applications_count || 0}</div>
|
||||||
|
<div class="label">Applications</div>
|
||||||
|
</div>
|
||||||
|
<div class="pack-stat">
|
||||||
|
<div class="value">${pack.success_rate ? Math.round(pack.success_rate * 100) + '%' : '-'}</div>
|
||||||
|
<div class="label">Succès</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pack-actions">
|
||||||
|
<button class="btn btn-primary btn-small" onclick="exportPack('${pack.id}')">⬇️</button>
|
||||||
|
<button class="btn btn-danger btn-small" onclick="deletePack('${pack.id}')">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
list.innerHTML = `<p style="color:#ef4444;padding:20px;">Erreur: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCreatePackModal() {
|
||||||
|
document.getElementById('createPackModal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCreatePackModal() {
|
||||||
|
document.getElementById('createPackModal').style.display = 'none';
|
||||||
|
document.getElementById('packName').value = '';
|
||||||
|
document.getElementById('packDescription').value = '';
|
||||||
|
document.getElementById('packCategory').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCorrectionPack() {
|
||||||
|
const name = document.getElementById('packName').value.trim();
|
||||||
|
if (!name) {
|
||||||
|
showNotification('❌ Le nom est requis', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = document.getElementById('packDescription').value.trim();
|
||||||
|
const category = document.getElementById('packCategory').value.trim();
|
||||||
|
|
||||||
|
showNotification('📦 Création du pack...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchJSON('http://localhost:5000/api/correction-packs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name, description, category })
|
||||||
|
});
|
||||||
|
|
||||||
|
showNotification('✅ Pack créé avec succès', 'success');
|
||||||
|
closeCreatePackModal();
|
||||||
|
await refreshCorrectionPacks();
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportPack(packId) {
|
||||||
|
showNotification('⬇️ Export du pack...', 'info');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:5000/api/correction-packs/${packId}/export`);
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `correction_pack_${packId}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showNotification('✅ Pack exporté', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePack(packId) {
|
||||||
|
if (!confirm('Supprimer ce pack ? Cette action est irréversible.')) return;
|
||||||
|
|
||||||
|
showNotification('🗑️ Suppression...', 'warning');
|
||||||
|
try {
|
||||||
|
await fetchJSON(`http://localhost:5000/api/correction-packs/${packId}`, { method: 'DELETE' });
|
||||||
|
showNotification('✅ Pack supprimé', 'success');
|
||||||
|
await refreshCorrectionPacks();
|
||||||
|
} catch (e) {
|
||||||
|
showNotification(`❌ Erreur: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SECTION: Apprentissage / Learning
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
let corpusChart, healingChart;
|
||||||
|
|
||||||
|
async function refreshLearningStats() {
|
||||||
|
try {
|
||||||
|
// Stats depuis l'API de performance
|
||||||
|
const perf = await fetchJSON('/api/system/performance');
|
||||||
|
|
||||||
|
// Stats FAISS
|
||||||
|
const faiss = perf.faiss || {};
|
||||||
|
document.getElementById('statCorpusSize').textContent = faiss.vectors || 0;
|
||||||
|
|
||||||
|
// Stats sessions
|
||||||
|
const status = await fetchJSON('/api/system/status');
|
||||||
|
document.getElementById('statTrainedSessions').textContent = status.sessions_count || 0;
|
||||||
|
|
||||||
|
// Stats self-healing depuis correction packs
|
||||||
|
let healingRate = '-';
|
||||||
|
let learningRate = '-';
|
||||||
|
try {
|
||||||
|
const corrections = await fetchJSON('http://localhost:5000/api/correction-packs/stats');
|
||||||
|
healingRate = corrections.overall_success_rate ?
|
||||||
|
Math.round(corrections.overall_success_rate * 100) + '%' : '-';
|
||||||
|
learningRate = corrections.total_corrections > 0 ?
|
||||||
|
'+' + corrections.total_corrections : '-';
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
document.getElementById('statHealingSuccess').textContent = healingRate;
|
||||||
|
document.getElementById('statLearningRate').textContent = learningRate;
|
||||||
|
|
||||||
|
// Action type stats
|
||||||
|
await loadActionTypeStats();
|
||||||
|
|
||||||
|
// Top corrections
|
||||||
|
await loadTopCorrections();
|
||||||
|
|
||||||
|
// Initialize charts
|
||||||
|
initLearningCharts();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading learning stats:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadActionTypeStats() {
|
||||||
|
const div = document.getElementById('actionTypeStats');
|
||||||
|
|
||||||
|
// Données simulées basées sur les types d'actions disponibles
|
||||||
|
const actionTypes = [
|
||||||
|
{ name: 'click', count: 45, color: '#3b82f6' },
|
||||||
|
{ name: 'type_text', count: 28, color: '#8b5cf6' },
|
||||||
|
{ name: 'wait_for_anchor', count: 15, color: '#22c55e' },
|
||||||
|
{ name: 'scroll', count: 8, color: '#f59e0b' },
|
||||||
|
{ name: 'hotkey', count: 4, color: '#ef4444' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const total = actionTypes.reduce((s, a) => s + a.count, 0);
|
||||||
|
|
||||||
|
div.innerHTML = actionTypes.map(a => {
|
||||||
|
const pct = total > 0 ? Math.round(a.count / total * 100) : 0;
|
||||||
|
return `
|
||||||
|
<div class="stat-bar">
|
||||||
|
<span class="stat-bar-label">${a.name}</span>
|
||||||
|
<span class="stat-bar-value">${a.count} (${pct}%)</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width:${pct}%;background:${a.color};"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTopCorrections() {
|
||||||
|
const div = document.getElementById('topCorrections');
|
||||||
|
|
||||||
|
try {
|
||||||
|
let corrections = [];
|
||||||
|
try {
|
||||||
|
const data = await fetchJSON('http://localhost:5000/api/correction-packs');
|
||||||
|
// Agrégation des corrections les plus utilisées
|
||||||
|
for (const pack of (data.packs || [])) {
|
||||||
|
if (pack.applications_count > 0) {
|
||||||
|
corrections.push({
|
||||||
|
name: pack.name,
|
||||||
|
applications: pack.applications_count,
|
||||||
|
success_rate: pack.success_rate || 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (corrections.length === 0) {
|
||||||
|
div.innerHTML = '<p style="color:#64748b;text-align:center;">Aucune correction appliquée</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
corrections.sort((a, b) => b.applications - a.applications);
|
||||||
|
|
||||||
|
div.innerHTML = corrections.slice(0, 5).map((c, i) => `
|
||||||
|
<div class="stat-bar">
|
||||||
|
<span class="stat-bar-label">${i + 1}. ${c.name}</span>
|
||||||
|
<span class="stat-bar-value">${c.applications} apps (${Math.round(c.success_rate * 100)}%)</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
div.innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLearningCharts() {
|
||||||
|
// Corpus evolution chart
|
||||||
|
const corpusCtx = document.getElementById('corpusChart');
|
||||||
|
if (corpusCtx && !corpusChart) {
|
||||||
|
corpusChart = new Chart(corpusCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ['Sem -4', 'Sem -3', 'Sem -2', 'Sem -1', 'Cette sem'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Taille corpus (vecteurs)',
|
||||||
|
data: [100, 180, 290, 420, 512],
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: { legend: { labels: { color: '#94a3b8' } } },
|
||||||
|
scales: {
|
||||||
|
x: { ticks: { color: '#64748b' }, grid: { color: '#334155' } },
|
||||||
|
y: { ticks: { color: '#64748b' }, grid: { color: '#334155' } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healing performance chart
|
||||||
|
const healingCtx = document.getElementById('healingChart');
|
||||||
|
if (healingCtx && !healingChart) {
|
||||||
|
healingChart = new Chart(healingCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Succès', 'Échec', 'Non tenté'],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Self-healing',
|
||||||
|
data: [75, 15, 10],
|
||||||
|
backgroundColor: ['#22c55e', '#ef4444', '#64748b']
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: { legend: { labels: { color: '#94a3b8' } } },
|
||||||
|
scales: {
|
||||||
|
x: { ticks: { color: '#64748b' }, grid: { color: '#334155' } },
|
||||||
|
y: { ticks: { color: '#64748b' }, grid: { color: '#334155' } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initOverviewChart();
|
initOverviewChart();
|
||||||
@@ -1688,6 +2524,12 @@
|
|||||||
refreshWorkflows();
|
refreshWorkflows();
|
||||||
setInterval(refreshSystemStatus, 10000);
|
setInterval(refreshSystemStatus, 10000);
|
||||||
setInterval(refreshServices, 5000); // Rafraîchir les services toutes les 5s
|
setInterval(refreshServices, 5000); // Rafraîchir les services toutes les 5s
|
||||||
|
|
||||||
|
// Charger les stats des nouvelles sections (en arrière-plan)
|
||||||
|
setTimeout(() => {
|
||||||
|
refreshBackupStats();
|
||||||
|
refreshSystemInfo();
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add CSS animation for notifications
|
// Add CSS animation for notifications
|
||||||
|
|||||||
Reference in New Issue
Block a user