164 lines
8.7 KiB
HTML
164 lines
8.7 KiB
HTML
{% extends 'base.html' %}
|
||
{% block title %}Dashboard Vulnérabilités{% endblock %}
|
||
{% block content %}
|
||
|
||
{% set msg = request.query_params.get('msg', '') %}
|
||
{% if msg == 'refresh_started' %}
|
||
<div class="card p-3 mb-4 bg-blue-900/20 border-blue-500/40 text-blue-300 text-sm">
|
||
Recalcul lancé en arrière-plan. Rafraîchis la page dans 1-2 minutes pour voir les nouveaux chiffres.
|
||
</div>
|
||
{% 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 déjà en cours. Patiente puis rafraîchis.
|
||
</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="grid grid-cols-6 gap-2 mb-4">
|
||
<div class="card p-3 border-cyber-accent">
|
||
<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">
|
||
<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">
|
||
<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">
|
||
<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">
|
||
<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">
|
||
<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 >0)</p>
|
||
</div>
|
||
|
||
{% endif %}
|
||
{% endblock %}
|