128 lines
5.5 KiB
HTML
128 lines
5.5 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Audit en cours{% endblock %}
|
|
{% block content %}
|
|
<div class="flex justify-between items-center mb-4">
|
|
<div>
|
|
<a href="/audit" class="text-xs text-gray-500 hover:text-gray-300">← Retour audit</a>
|
|
<h2 class="text-xl font-bold text-cyber-accent" id="page-title">Audit en cours...</h2>
|
|
</div>
|
|
<div class="flex gap-2" id="actions-zone" style="display:none">
|
|
<a href="/audit/realtime/results/{{ job_id }}" class="btn-secondary px-4 py-2 text-sm">Voir détails</a>
|
|
<form method="POST" action="/audit/realtime/save">
|
|
<button class="btn-primary px-4 py-2 text-sm" onclick="return confirm('Mettre à jour la base avec ces résultats ?')">Mettre à jour la base</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KPIs -->
|
|
<div style="display:flex;gap:8px;margin-bottom:16px">
|
|
<div class="card p-3 text-center" style="flex:1">
|
|
<div class="text-2xl font-bold text-cyber-accent" id="kpi-total">{{ total }}</div>
|
|
<div class="text-xs text-gray-500">Total</div>
|
|
</div>
|
|
<div class="card p-3 text-center" style="flex:1">
|
|
<div class="text-2xl font-bold text-cyber-green" id="kpi-ok">0</div>
|
|
<div class="text-xs text-gray-500">Connectés</div>
|
|
</div>
|
|
<div class="card p-3 text-center" style="flex:1">
|
|
<div class="text-2xl font-bold text-cyber-red" id="kpi-fail">0</div>
|
|
<div class="text-xs text-gray-500">Échoués</div>
|
|
</div>
|
|
<div class="card p-3 text-center" style="flex:1">
|
|
<div class="text-2xl font-bold text-gray-400 font-mono" id="kpi-timer">0s</div>
|
|
<div class="text-xs text-gray-500">Temps</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barre de progression -->
|
|
<div class="card p-4 mb-4">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-xs text-gray-400" id="progress-text">0 / {{ total }}</span>
|
|
<span class="text-xs text-gray-500" id="progress-pct">0%</span>
|
|
</div>
|
|
<div style="background:#1e293b;border-radius:4px;height:10px;overflow:hidden">
|
|
<div id="progress-bar" style="height:100%;background:#00ffc8;width:0%;transition:width 0.5s ease"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tableau progression par serveur -->
|
|
<div class="card overflow-hidden">
|
|
<table class="w-full table-cyber text-xs">
|
|
<thead><tr>
|
|
<th class="p-2 text-left">Hostname</th>
|
|
<th class="p-2">Étape</th>
|
|
<th class="p-2 text-left">Détail</th>
|
|
</tr></thead>
|
|
<tbody id="progress-body"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
var JOB_ID = '{{ job_id }}';
|
|
var TOTAL = {{ total }};
|
|
var _pollTimer = null;
|
|
|
|
var STAGE_MAP = {
|
|
'pending': {label: 'En attente', cls: 'badge-gray', icon: '●'},
|
|
'resolving': {label: 'Résolution DNS', cls: 'badge-yellow', icon: '↻'},
|
|
'connecting': {label: 'Connexion SSH', cls: 'badge-yellow', icon: '↻'},
|
|
'auditing': {label: 'Audit', cls: 'badge-yellow', icon: '↻'},
|
|
'success': {label: 'OK', cls: 'badge-green', icon: '✓'},
|
|
'failed': {label: 'Échec', cls: 'badge-red', icon: '✗'},
|
|
};
|
|
|
|
function stageBadge(stage) {
|
|
var s = STAGE_MAP[stage] || {label: stage, cls: 'badge-gray', icon: '?'};
|
|
var anim = (stage !== 'pending' && stage !== 'success' && stage !== 'failed')
|
|
? ' style="animation:pulse 1.5s ease-in-out infinite"' : '';
|
|
return '<span class="badge ' + s.cls + '"' + anim + '>' + s.icon + ' ' + s.label + '</span>';
|
|
}
|
|
|
|
function poll() {
|
|
fetch('/audit/realtime/status/' + JOB_ID, {credentials: 'same-origin'})
|
|
.then(function(r){ return r.json(); })
|
|
.then(function(data){
|
|
if (!data.ok) return;
|
|
|
|
var pct = TOTAL > 0 ? Math.round((data.done / TOTAL) * 100) : 0;
|
|
document.getElementById('progress-bar').style.width = pct + '%';
|
|
document.getElementById('progress-text').textContent = data.done + ' / ' + TOTAL;
|
|
document.getElementById('progress-pct').textContent = pct + '%';
|
|
document.getElementById('kpi-timer').textContent = data.elapsed + 's';
|
|
|
|
var ok = 0, fail = 0;
|
|
var hostnames = Object.keys(data.servers).sort();
|
|
var rows = '';
|
|
hostnames.forEach(function(hn){
|
|
var s = data.servers[hn];
|
|
if (s.stage === 'success') ok++;
|
|
else if (s.stage === 'failed') fail++;
|
|
rows += '<tr class="border-t border-cyber-border/30">';
|
|
rows += '<td class="p-2 font-mono">' + hn + '</td>';
|
|
rows += '<td class="p-2 text-center">' + stageBadge(s.stage) + '</td>';
|
|
rows += '<td class="p-2 text-gray-400 text-xs">' + (s.detail || '') + '</td>';
|
|
rows += '</tr>';
|
|
});
|
|
document.getElementById('progress-body').innerHTML = rows;
|
|
document.getElementById('kpi-ok').textContent = ok;
|
|
document.getElementById('kpi-fail').textContent = fail;
|
|
|
|
if (data.finished) {
|
|
if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; }
|
|
document.getElementById('page-title').innerHTML =
|
|
'Audit terminé — <span class="text-cyber-green">' + ok + ' OK</span> / <span class="text-cyber-red">' + fail + ' échec(s)</span>';
|
|
document.getElementById('progress-bar').style.background = fail > 0 ? '#ff3366' : '#00ffc8';
|
|
document.getElementById('actions-zone').style.display = 'flex';
|
|
}
|
|
})
|
|
.catch(function(){});
|
|
}
|
|
|
|
poll();
|
|
_pollTimer = setInterval(poll, 2000);
|
|
</script>
|
|
<style>
|
|
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
|
|
</style>
|
|
{% endblock %}
|