patchcenter/app/templates/qualys_dashboard.html

187 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% block title %}Dashboard Vulnérabilités{% endblock %}
{% block content %}
{% set msg = request.query_params.get('msg', '') %}
{% if is_running or msg == 'refresh_started' %}
<div id="recalc-banner" class="card p-4 mb-4 bg-cyber-accent/10 border-2 border-cyber-accent flex items-center gap-4">
<div style="border:4px solid rgba(0,212,255,.2); border-top:4px solid #00d4ff; border-radius:50%; width:42px; height:42px; animation:spin 0.9s linear infinite; flex-shrink:0"></div>
<div class="flex-1">
<div class="text-cyber-accent font-bold text-base">Recalcul du dashboard en cours...</div>
<div class="text-xs text-gray-400 mt-1">
Calcul des KPI sur ~1300 assets via API Qualys (durée typique : 3 à 5 minutes).
La page se rafraichira automatiquement.
<span id="elapsed" class="font-mono text-cyber-accent ml-2"></span>
</div>
</div>
<form method="POST" action="/qualys/dashboard/cancel" style="flex-shrink:0">
<button class="btn-sm bg-red-900/40 text-red-300 hover:bg-red-900/60" type="submit">Annuler</button>
</form>
</div>
<script>
setTimeout(function() { window.location.href = '/qualys/dashboard'; }, 15000);
(function() {
var t0 = Date.now();
var el = document.getElementById('elapsed');
setInterval(function() {
var s = Math.floor((Date.now() - t0) / 1000);
if (el) el.textContent = '+' + s + 's';
}, 1000);
})();
</script>
{% elif msg == 'already_running' %}
<div class="card p-3 mb-4 bg-yellow-900/20 border-yellow-500/40 text-yellow-300 text-sm">
Un calcul est deja en cours. Patiente puis rafraichis.
</div>
{% endif %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Dashboard Vulnérabilités</h2>
<div class="flex gap-2 items-center">
{% if data.last_run %}
<span class="text-xs text-gray-500">
Dernier calcul : {{ data.last_run.run_at.strftime('%Y-%m-%d %H:%M') }}
({{ data.last_run.asset_count }} assets, {{ data.last_run.duration_sec }}s)
</span>
{% endif %}
<a href="/qualys/dashboard/history" class="btn-sm bg-cyber-border text-cyber-accent">📈 Historique</a>
<form method="POST" action="/qualys/dashboard/refresh" style="display:inline">
<button class="btn-sm bg-cyber-accent text-black" {% if is_running %}disabled{% endif %}
data-loading="Recalcul en cours...|Calcul des KPI sur tous les assets">
{% if is_running %}⏳ En cours...{% else %}🔄 Recalculer{% endif %}
</button>
</form>
</div>
</div>
{% if not data.last_run %}
<div class="card p-6 text-center">
<p class="text-gray-400 mb-4">Aucun snapshot disponible. Lance un premier calcul.</p>
<form method="POST" action="/qualys/dashboard/refresh">
<button class="btn-primary px-6 py-2">🔄 Lancer le premier calcul</button>
</form>
</div>
{% else %}
<!-- KPI bandeau (6 cards) -->
{% set g = data.global %}
<div class="flex gap-2 mb-4 flex-wrap">
<div class="card p-3 border-cyber-accent flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">Total</div>
<div class="text-2xl font-bold text-cyber-accent">{{ g.total if g else 0 }}</div>
<div class="text-xs text-gray-500">serveurs</div>
</div>
<a href="/qualys/search?vuln_filter=critical" class="card p-3 border-red-700 hover:border-red-500 transition flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">🔴 Critique (sev 5)</div>
<div class="text-2xl font-bold text-red-500">{{ g.critical if g else 0 }}</div>
<div class="text-xs text-gray-500">{{ ((g.critical * 100 / g.scanned) | round(1)) if g and g.scanned > 0 else 0 }}%</div>
</a>
<a href="/qualys/search?vuln_filter=high" class="card p-3 border-orange-700 hover:border-orange-500 transition flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">🟠 High (sev 4)</div>
<div class="text-2xl font-bold text-orange-500">{{ g.high if g else 0 }}</div>
<div class="text-xs text-gray-500">{{ ((g.high * 100 / g.scanned) | round(1)) if g and g.scanned > 0 else 0 }}%</div>
</a>
<a href="/qualys/search?vuln_filter=medium" class="card p-3 border-yellow-700 hover:border-yellow-500 transition flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">🟡 Medium (sev 3)</div>
<div class="text-2xl font-bold text-yellow-500">{{ g.medium if g else 0 }}</div>
<div class="text-xs text-gray-500">{{ ((g.medium * 100 / g.scanned) | round(1)) if g and g.scanned > 0 else 0 }}%</div>
</a>
<a href="/qualys/search?vuln_filter=zero" class="card p-3 border-green-700 hover:border-green-500 transition flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">🟢 Sans vuln</div>
<div class="text-2xl font-bold text-green-500">{{ g.sain if g else 0 }}</div>
<div class="text-xs text-gray-500">{{ ((g.sain * 100 / g.scanned) | round(1)) if g and g.scanned > 0 else 0 }}%</div>
</a>
<div class="card p-3 border-gray-700 flex-1" style="min-width:140px">
<div class="text-xs text-gray-500 uppercase">⚫ Non scanné</div>
<div class="text-2xl font-bold text-gray-400">{{ g.non_scanne if g else 0 }}</div>
<div class="text-xs text-gray-500">{{ ((g.non_scanne * 100 / g.total) | round(1)) if g and g.total > 0 else 0 }}% du total</div>
</div>
</div>
{% macro pivot_table(title, items, tag_prefix='') %}
<div class="card p-3 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">{{ title }}</h3>
<table class="w-full text-xs">
<thead class="text-gray-400 border-b border-cyber-border">
<tr>
<th class="text-left py-1">Catégorie</th>
<th class="text-right py-1">Total</th>
<th class="text-right py-1 text-red-400">🔴 Critique</th>
<th class="text-right py-1 text-orange-400">🟠 High</th>
<th class="text-right py-1 text-yellow-400">🟡 Medium</th>
<th class="text-right py-1 text-green-400">🟢 Sain</th>
<th class="text-right py-1 text-gray-500">⚫ Non scanné</th>
<th class="text-right py-1">% vuln</th>
</tr>
</thead>
<tbody>
{% for it in items|sort(attribute='vuln_total', reverse=true) %}
<tr class="border-b border-cyber-border/30 hover:bg-cyber-card/50">
<td class="py-1 font-mono">{{ it.name }}</td>
<td class="text-right py-1">{{ it.total }}</td>
<td class="text-right py-1">
{% if it.critical > 0 %}<a href="/qualys/search?field=tag&search={{ it.name }}&vuln_filter=critical" class="text-red-400 hover:underline">{{ it.critical }}</a>{% else %}-{% endif %}
</td>
<td class="text-right py-1">
{% if it.high > 0 %}<a href="/qualys/search?field=tag&search={{ it.name }}&vuln_filter=high" class="text-orange-400 hover:underline">{{ it.high }}</a>{% else %}-{% endif %}
</td>
<td class="text-right py-1">
{% if it.medium > 0 %}<a href="/qualys/search?field=tag&search={{ it.name }}&vuln_filter=medium" class="text-yellow-400 hover:underline">{{ it.medium }}</a>{% else %}-{% endif %}
</td>
<td class="text-right py-1">
{% if it.sain > 0 %}<a href="/qualys/search?field=tag&search={{ it.name }}&vuln_filter=zero" class="text-green-400 hover:underline">{{ it.sain }}</a>{% else %}-{% endif %}
</td>
<td class="text-right py-1 text-gray-500">{{ it.non_scanne if it.non_scanne > 0 else '-' }}</td>
<td class="text-right py-1 font-bold {% if it.pct_vuln >= 50 %}text-red-400{% elif it.pct_vuln >= 25 %}text-orange-400{% else %}text-green-400{% endif %}">
{{ it.pct_vuln }}%
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{{ pivot_table('Par environnement (ENV-*)', data.env, 'ENV-') }}
{{ pivot_table('Par position réseau (POS-*)', data.pos, 'POS-') }}
{{ pivot_table('Par OS (OS-*)', data.os, 'OS-') }}
{{ pivot_table('Par domaine AD', data.domain, '') }}
<!-- Matrice ENV x POS heatmap -->
<div class="card p-3 mb-4">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Matrice Environnement × Position (% vuln)</h3>
<table class="w-full text-xs">
<thead>
<tr>
<th class="text-left py-1 text-gray-400">ENV \ POS</th>
{% for p in pos_list %}<th class="text-center py-1 text-gray-400 font-mono">{{ p }}</th>{% endfor %}
</tr>
</thead>
<tbody>
{% for e in env_list %}
<tr>
<td class="py-1 font-mono text-cyber-accent">{{ e }}</td>
{% for p in pos_list %}
{% set cell = matrix.get((e,p)) %}
{% if cell and cell.total > 0 %}
<td class="text-center py-1 px-1 {% if cell.pct_vuln >= 50 %}bg-red-900/40{% elif cell.pct_vuln >= 25 %}bg-orange-900/30{% elif cell.pct_vuln > 0 %}bg-yellow-900/20{% else %}bg-green-900/20{% endif %}">
<a href="/qualys/search?field=tag&search={{ e }}" class="hover:underline">
<div class="font-bold">{{ cell.vuln_total }}/{{ cell.scanned }}</div>
<div class="text-[10px] text-gray-500">{{ cell.pct_vuln }}%</div>
</a>
</td>
{% else %}
<td class="text-center py-1 px-1 text-gray-700">-</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-xs text-gray-500 mt-2">Format : <strong>vulnérables / scannés</strong> — couleur = % vulnérables (rouge ≥50%, orange ≥25%, jaune &gt;0)</p>
</div>
{% endif %}
{% endblock %}