seo-generator-server/tests/dashboard/TestDashboard.html

787 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🧪 Dashboard Tests Systématiques</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%);
color: #333;
min-height: 100vh;
}
.dashboard {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-rows: 80px 1fr 50px;
grid-template-columns: 350px 1fr;
height: 100vh;
gap: 2px;
}
.header {
grid-area: header;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
}
.header h1 {
color: #2c3e50;
font-size: 24px;
font-weight: 600;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
}
.status-indicator.running {
background: linear-gradient(45deg, #4CAF50, #45a049);
color: white;
}
.status-indicator.stopped {
background: linear-gradient(45deg, #f44336, #d32f2f);
color: white;
}
.status-indicator.idle {
background: linear-gradient(45deg, #ff9800, #f57c00);
color: white;
}
.sidebar {
grid-area: sidebar;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 20px;
overflow-y: auto;
}
.main {
grid-area: main;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 20px;
overflow-y: auto;
}
.footer {
grid-area: footer;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 12px;
}
.control-panel {
margin-bottom: 30px;
}
.control-group {
margin-bottom: 20px;
}
.control-group h3 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 16px;
font-weight: 600;
}
.btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 8px;
}
.btn-primary {
background: linear-gradient(45deg, #007acc, #0056b3);
color: white;
}
.btn-secondary {
background: linear-gradient(45deg, #6c757d, #5a6268);
color: white;
}
.btn-success {
background: linear-gradient(45deg, #28a745, #218838);
color: white;
}
.btn-danger {
background: linear-gradient(45deg, #dc3545, #c82333);
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 20px;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.stat-value {
font-size: 32px;
font-weight: 700;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 1px;
}
.progress-section {
margin-bottom: 30px;
}
.progress-bar {
background: #e9ecef;
border-radius: 10px;
height: 20px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
background: linear-gradient(45deg, #28a745, #20c997);
height: 100%;
transition: width 0.5s ease;
border-radius: 10px;
}
.log-container {
background: #1a1a1a;
border-radius: 8px;
padding: 20px;
height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.4;
}
.log-entry {
margin-bottom: 5px;
padding: 2px 0;
}
.log-timestamp {
color: #888;
}
.log-level-DEBUG { color: #17a2b8; }
.log-level-INFO { color: #28a745; }
.log-level-WARNING { color: #ffc107; }
.log-level-ERROR { color: #dc3545; }
.log-level-TRACE { color: #6f42c1; }
.validation-results {
margin-top: 30px;
}
.validation-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
}
.validation-card {
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-left: 4px solid #007acc;
}
.validation-card h4 {
margin-bottom: 10px;
color: #2c3e50;
}
.score {
display: inline-block;
padding: 4px 12px;
border-radius: 15px;
font-weight: 600;
font-size: 12px;
color: white;
}
.score.excellent { background: #28a745; }
.score.good { background: #17a2b8; }
.score.moderate { background: #ffc107; color: #000; }
.score.poor { background: #dc3545; }
.module-filter {
margin-bottom: 15px;
}
.module-filter input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
}
.module-list {
max-height: 300px;
overflow-y: auto;
}
.module-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-radius: 6px;
margin-bottom: 5px;
background: #f8f9fa;
font-size: 13px;
}
.module-item:hover {
background: #e9ecef;
}
.module-status {
width: 8px;
height: 8px;
border-radius: 50%;
}
.module-status.passed { background: #28a745; }
.module-status.failed { background: #dc3545; }
.module-status.pending { background: #ffc107; }
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.running-indicator {
animation: pulse 2s infinite;
}
.search-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.search-controls input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.search-controls select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.clear-logs {
background: #6c757d;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
</style>
</head>
<body>
<div class="dashboard">
<header class="header">
<h1>🧪 Dashboard Tests Systématiques</h1>
<div class="status-indicator idle" id="systemStatus">
<span class="status-dot"></span>
<span id="statusText">En attente</span>
</div>
</header>
<aside class="sidebar">
<div class="control-panel">
<div class="control-group">
<h3>🚀 Contrôles</h3>
<button class="btn btn-success" id="runFullSuite">
Suite Complète
</button>
<button class="btn btn-primary" id="runQuickTest">
Test Rapide
</button>
<button class="btn btn-secondary" id="generateTests">
Générer Tests
</button>
<button class="btn btn-danger" id="stopTests" disabled>
Arrêter Tests
</button>
</div>
<div class="control-group">
<h3>📊 Statistiques</h3>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="modulesCount">0</div>
<div class="stat-label">Modules</div>
</div>
<div class="stat-card">
<div class="stat-value" id="testsCount">0</div>
<div class="stat-label">Tests</div>
</div>
<div class="stat-card">
<div class="stat-value" id="coverage">0%</div>
<div class="stat-label">Couverture</div>
</div>
<div class="stat-card">
<div class="stat-value" id="aiScore">0</div>
<div class="stat-label">Score IA</div>
</div>
</div>
</div>
<div class="control-group">
<h3>📦 Modules</h3>
<div class="module-filter">
<input type="text" id="moduleSearch" placeholder="Rechercher module...">
</div>
<div class="module-list" id="moduleList">
<!-- Modules seront ajoutés dynamiquement -->
</div>
</div>
</div>
</aside>
<main class="main">
<div class="progress-section">
<h3>📈 Progression</h3>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
<p id="progressText">Aucun test en cours</p>
</div>
<div class="search-controls">
<input type="text" id="logSearch" placeholder="Rechercher dans les logs...">
<select id="logLevelFilter">
<option value="">Tous niveaux</option>
<option value="ERROR">Erreurs</option>
<option value="WARNING">Avertissements</option>
<option value="INFO">Informations</option>
<option value="DEBUG">Debug</option>
<option value="TRACE">Trace</option>
</select>
<button class="clear-logs" id="clearLogs">Vider</button>
</div>
<div class="log-container" id="logContainer">
<div class="log-entry">
<span class="log-timestamp">[00:00:00]</span>
<span class="log-level-INFO">INFO</span>
- Dashboard initialisé. En attente des tests...
</div>
</div>
<div class="validation-results" id="validationResults">
<h3>🤖 Résultats Validation IA</h3>
<div class="validation-grid" id="validationGrid">
<!-- Résultats de validation seront ajoutés ici -->
</div>
</div>
</main>
<footer class="footer">
<p>© 2025 SEO Generator - Système de Tests Systématiques avec Validation IA</p>
</footer>
</div>
<script>
class TestDashboard {
constructor() {
this.ws = null;
this.currentTestRun = null;
this.modules = new Map();
this.validationResults = new Map();
this.logs = [];
this.filteredLogs = [];
this.initializeUI();
this.connectWebSocket();
this.startPeriodicUpdates();
}
initializeUI() {
// Boutons de contrôle
document.getElementById('runFullSuite').addEventListener('click', () => {
this.runTestSuite('full');
});
document.getElementById('runQuickTest').addEventListener('click', () => {
this.runTestSuite('quick');
});
document.getElementById('generateTests').addEventListener('click', () => {
this.generateTests();
});
document.getElementById('stopTests').addEventListener('click', () => {
this.stopTests();
});
// Recherche modules
document.getElementById('moduleSearch').addEventListener('input', (e) => {
this.filterModules(e.target.value);
});
// Recherche logs
document.getElementById('logSearch').addEventListener('input', (e) => {
this.filterLogs();
});
document.getElementById('logLevelFilter').addEventListener('change', () => {
this.filterLogs();
});
document.getElementById('clearLogs').addEventListener('click', () => {
this.clearLogs();
});
}
connectWebSocket() {
try {
this.ws = new WebSocket('ws://localhost:8082');
this.ws.onopen = () => {
this.addLog('INFO', 'WebSocket connecté au serveur de tests');
this.updateSystemStatus('idle', 'Connecté');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleWebSocketMessage(data);
};
this.ws.onclose = () => {
this.addLog('WARNING', 'WebSocket déconnecté. Tentative de reconnexion...');
this.updateSystemStatus('stopped', 'Déconnecté');
setTimeout(() => this.connectWebSocket(), 5000);
};
this.ws.onerror = (error) => {
this.addLog('ERROR', 'Erreur WebSocket: ' + error.message);
};
} catch (error) {
this.addLog('ERROR', 'Impossible de se connecter au WebSocket: ' + error.message);
this.updateSystemStatus('stopped', 'Erreur connexion');
}
}
handleWebSocketMessage(data) {
switch (data.type) {
case 'log':
this.addLog(data.level, data.message, data.timestamp);
break;
case 'test_started':
this.handleTestStarted(data);
break;
case 'test_progress':
this.handleTestProgress(data);
break;
case 'test_completed':
this.handleTestCompleted(data);
break;
case 'validation_result':
this.handleValidationResult(data);
break;
case 'system_stats':
this.updateSystemStats(data.stats);
break;
}
}
runTestSuite(mode) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
action: 'run_tests',
mode: mode
}));
this.updateSystemStatus('running', `Tests ${mode} en cours`);
document.getElementById('stopTests').disabled = false;
this.addLog('INFO', `Suite de tests ${mode} démarrée`);
}
}
generateTests() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
action: 'generate_tests'
}));
this.addLog('INFO', 'Génération des tests démarrée');
}
}
stopTests() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
action: 'stop_tests'
}));
this.updateSystemStatus('idle', 'Arrêt en cours');
document.getElementById('stopTests').disabled = true;
}
}
handleTestStarted(data) {
this.currentTestRun = data.runId;
this.updateProgress(0, 'Initialisation des tests...');
}
handleTestProgress(data) {
this.updateProgress(data.progress, data.message);
if (data.module) {
this.updateModuleStatus(data.module, data.status);
}
}
handleTestCompleted(data) {
this.updateSystemStatus('idle', 'Tests terminés');
this.updateProgress(100, 'Tests terminés');
document.getElementById('stopTests').disabled = true;
this.updateSystemStats(data.results);
this.addLog('INFO', `Tests terminés: ${data.results.passed} réussis, ${data.results.failed} échoués`);
}
handleValidationResult(data) {
this.validationResults.set(data.module, data.validation);
this.updateValidationDisplay();
}
updateSystemStatus(status, text) {
const indicator = document.getElementById('systemStatus');
const statusText = document.getElementById('statusText');
indicator.className = `status-indicator ${status}`;
statusText.textContent = text;
}
updateProgress(percentage, message) {
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
progressFill.style.width = percentage + '%';
progressText.textContent = message || `${percentage}% terminé`;
}
updateSystemStats(stats) {
if (stats.modules) document.getElementById('modulesCount').textContent = stats.modules;
if (stats.tests) document.getElementById('testsCount').textContent = stats.tests;
if (stats.coverage) document.getElementById('coverage').textContent = stats.coverage + '%';
if (stats.aiScore) document.getElementById('aiScore').textContent = stats.aiScore;
}
updateModuleStatus(moduleName, status) {
this.modules.set(moduleName, status);
this.updateModuleDisplay();
}
updateModuleDisplay() {
const moduleList = document.getElementById('moduleList');
const searchTerm = document.getElementById('moduleSearch').value.toLowerCase();
moduleList.innerHTML = '';
Array.from(this.modules.entries())
.filter(([name]) => name.toLowerCase().includes(searchTerm))
.forEach(([name, status]) => {
const item = document.createElement('div');
item.className = 'module-item';
item.innerHTML = `
<span>${name}</span>
<span class="module-status ${status}"></span>
`;
moduleList.appendChild(item);
});
}
updateValidationDisplay() {
const grid = document.getElementById('validationGrid');
grid.innerHTML = '';
this.validationResults.forEach((validation, module) => {
const card = document.createElement('div');
card.className = 'validation-card';
const scoreClass = this.getScoreClass(validation.overall || 0);
card.innerHTML = `
<h4>${module}</h4>
<p>Score global: <span class="score ${scoreClass}">${validation.overall || 0}/100</span></p>
${validation.quality ? `<p>Qualité: ${validation.quality}/100</p>` : ''}
${validation.antiDetection ? `<p>Anti-détection: ${validation.antiDetection}/100</p>` : ''}
${validation.confidence ? `<p>Confiance: ${Math.round(validation.confidence * 100)}%</p>` : ''}
${validation.error ? `<p style="color: red;">Erreur: ${validation.error}</p>` : ''}
`;
grid.appendChild(card);
});
}
getScoreClass(score) {
if (score >= 85) return 'excellent';
if (score >= 70) return 'good';
if (score >= 50) return 'moderate';
return 'poor';
}
addLog(level, message, timestamp) {
const now = timestamp || new Date();
const timeStr = now.toLocaleTimeString('fr-FR');
const logEntry = {
timestamp: now,
level,
message,
timeStr
};
this.logs.push(logEntry);
// Garder seulement les 500 dernières entrées
if (this.logs.length > 500) {
this.logs = this.logs.slice(-500);
}
this.filterLogs();
}
filterLogs() {
const searchTerm = document.getElementById('logSearch').value.toLowerCase();
const levelFilter = document.getElementById('logLevelFilter').value;
this.filteredLogs = this.logs.filter(log => {
const matchesSearch = !searchTerm || log.message.toLowerCase().includes(searchTerm);
const matchesLevel = !levelFilter || log.level === levelFilter;
return matchesSearch && matchesLevel;
});
this.updateLogDisplay();
}
updateLogDisplay() {
const container = document.getElementById('logContainer');
const wasScrolledToBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
container.innerHTML = this.filteredLogs.slice(-100).map(log => `
<div class="log-entry">
<span class="log-timestamp">[${log.timeStr}]</span>
<span class="log-level-${log.level}">${log.level}</span>
- ${log.message}
</div>
`).join('');
// Auto-scroll si on était en bas
if (wasScrolledToBottom) {
container.scrollTop = container.scrollHeight;
}
}
clearLogs() {
this.logs = [];
this.filteredLogs = [];
this.updateLogDisplay();
this.addLog('INFO', 'Logs effacés');
}
filterModules(searchTerm) {
this.updateModuleDisplay();
}
startPeriodicUpdates() {
setInterval(() => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
action: 'get_status'
}));
}
}, 10000); // Mise à jour toutes les 10 secondes
}
}
// Initialisation du dashboard
document.addEventListener('DOMContentLoaded', () => {
new TestDashboard();
});
</script>
</body>
</html>