patchcenter/app/templates/qualys_dashboard.html

164 lines
8.7 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 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 &gt;0)</p>
</div>
{% endif %}
{% endblock %}