Added plan.md with complete architecture for format-agnostic content generation: - Support for Markdown, HTML, Plain Text, JSON formats - New FormatExporter module with neutral data structure - Integration strategy with existing ContentAssembly and ArticleStorage - Bonus features: SEO metadata generation, readability scoring, WordPress Gutenberg format - Implementation roadmap with 4 phases (6h total estimated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
787 lines
28 KiB
HTML
787 lines
28 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> |