patchcenter/app/templates/audit_realtime_progress.html
Admin MPCZ 3f47fea8e6 Audit: jobs background paralleles + progression live
- Audit global/realtime: threads paralleles, job_id retourne immediat
- /audit/realtime/progress/{job_id}: KPIs + barre progression + tableau live
- Polling AJAX toutes les 2s, etapes animees (DNS/SSH/Audit/OK)
- PRETTY_NAME correction: extraction via grep -E 'PRETTY_NAME' + cut
- OS version: normalisation lors de save_audit_to_db (Debian GNU/Linux -> Debian X (Bookworm))
- Mise a jour base: itop sync bidirectionnel avec push OS version

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:51:05 +02:00

127 lines
5.4 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">&larr; 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">
<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: '&#9679;'},
'resolving': {label: 'Résolution DNS', cls: 'badge-yellow', icon: '&#8635;'},
'connecting': {label: 'Connexion SSH', cls: 'badge-yellow', icon: '&#8635;'},
'auditing': {label: 'Audit', cls: 'badge-yellow', icon: '&#8635;'},
'success': {label: 'OK', cls: 'badge-green', icon: '&#10003;'},
'failed': {label: 'Échec', cls: 'badge-red', icon: '&#10007;'},
};
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 %}