- Stats DMZ (cliquable vers filtre zone) - Patched 2026, never patched, last week (depuis patch_history Excel) - Couverture patching = patched / patchable - KPIs cards cliquables (lien vers /servers filtre pre-applique) - Fix alias stats.eol -> stats.obsolete
203 lines
13 KiB
HTML
203 lines
13 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Dashboard{% endblock %}
|
|
{% block content %}
|
|
<h2 class="text-xl font-bold text-cyber-accent mb-4">Dashboard</h2>
|
|
|
|
<!-- KPIs generaux -->
|
|
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;">
|
|
<a href="/servers" class="card p-3 text-center hover:bg-cyber-hover" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-accent">{{ stats.total_servers }}</div><div class="text-xs text-gray-500">Serveurs</div></a>
|
|
<a href="/servers?owner=secops&etat=Production" class="card p-3 text-center hover:bg-cyber-hover" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-green">{{ stats.patchable }}</div><div class="text-xs text-gray-500">Patchables SecOps</div></a>
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-white">{{ stats.linux }} / {{ stats.windows }}</div><div class="text-xs text-gray-500">Linux / Windows</div></div>
|
|
<a href="/servers?zone=DMZ" class="card p-3 text-center hover:bg-cyber-hover" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-red">{{ stats.dmz }}</div><div class="text-xs text-gray-500">DMZ</div></a>
|
|
<a href="/qualys/agents" class="card p-3 text-center hover:bg-cyber-hover" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-green">{{ stats.qualys_active }}</div><div class="text-xs text-gray-500">Agents actifs</div></a>
|
|
<a href="/qualys/agents" class="card p-3 text-center hover:bg-cyber-hover" style="flex:1;min-width:0"><div class="text-2xl font-bold {% if stats.qualys_no_agent > 0 %}text-cyber-red{% else %}text-cyber-green{% endif %}">{{ stats.qualys_no_agent }}</div><div class="text-xs text-gray-500">Sans agent</div></a>
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-red">{{ stats.eol }}</div><div class="text-xs text-gray-500">EOL</div></div>
|
|
</div>
|
|
|
|
<!-- KPI Patching depuis patch_history (Excel Plan de Patching 2026) -->
|
|
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;">
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-green">{{ stats.patched_history_2026 }}</div><div class="text-xs text-gray-500">Serveurs patchés 2026</div></div>
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-accent">{{ stats.patch_events_2026 }}</div><div class="text-xs text-gray-500">Events patching 2026</div></div>
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold {% if stats.never_patched_2026 > 0 %}text-cyber-red{% else %}text-cyber-green{% endif %}">{{ stats.never_patched_2026 }}</div><div class="text-xs text-gray-500">Jamais patchés 2026 (prod)</div></div>
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-yellow">S{{ stats.last_patch_week or '-' }}</div><div class="text-xs text-gray-500">Dernière semaine</div></div>
|
|
{% set pct_patched = (stats.patched_history_2026 / stats.patchable * 100)|int if stats.patchable > 0 else 0 %}
|
|
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold {% if pct_patched >= 80 %}text-cyber-green{% elif pct_patched >= 50 %}text-cyber-yellow{% else %}text-cyber-red{% endif %}">{{ pct_patched }}%</div><div class="text-xs text-gray-500">Couverture 2026</div></div>
|
|
</div>
|
|
|
|
<!-- ═══════ PATCHING 2026 ═══════ -->
|
|
{% if patch_stats %}
|
|
<div class="card p-4 mb-4">
|
|
<h3 class="text-sm font-bold text-cyber-accent mb-3">Patching 2026</h3>
|
|
|
|
<!-- KPIs patching -->
|
|
<div style="display:flex;flex-wrap:nowrap;gap:6px;margin-bottom:12px;">
|
|
<a href="/audit-full" class="card p-2 text-center hover:bg-cyber-hover" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-cyber-accent">{{ patch_stats.audited }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">Audités</div>
|
|
</a>
|
|
<a href="/audit-full?filter=app_patch2026" class="card p-2 text-center hover:bg-cyber-hover" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-cyber-green">{{ patch_stats.patched_2026 }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">Patchés 2026</div>
|
|
</a>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-cyber-green">{{ patch_stats.patched_once }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">1+ fois</div>
|
|
</div>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-blue-400">{{ patch_stats.patched_twice }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">2+ fois</div>
|
|
</div>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-purple-400">{{ patch_stats.patched_thrice }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">3+ fois</div>
|
|
</div>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-cyber-yellow">{{ patch_stats.patched_2025_only }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">2025 seul</div>
|
|
</div>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold text-cyber-red">{{ patch_stats.never_patched }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">Jamais</div>
|
|
</div>
|
|
<div class="card p-2 text-center" style="flex:1;min-width:0;background:#111827;">
|
|
<div class="text-xl font-bold {% if patch_stats.needs_reboot > 0 %}text-cyber-red{% else %}text-cyber-green{% endif %}">{{ patch_stats.needs_reboot }}</div>
|
|
<div style="font-size:10px;" class="text-gray-500">Reboot</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Barre de progression globale -->
|
|
{% set pct = (patch_stats.patched_2026 / patch_stats.audited * 100)|int if patch_stats.audited > 0 else 0 %}
|
|
<div class="mb-4">
|
|
<div class="flex justify-between text-xs mb-1">
|
|
<span class="text-gray-400">Couverture patching 2026</span>
|
|
<span class="font-bold {% if pct >= 80 %}text-cyber-green{% elif pct >= 50 %}text-cyber-yellow{% else %}text-cyber-red{% endif %}">{{ pct }}%</span>
|
|
</div>
|
|
<div style="height:8px;background:#1f2937;border-radius:4px;overflow:hidden;">
|
|
<div style="height:100%;width:{{ pct }}%;background:{% if pct >= 80 %}#22c55e{% elif pct >= 50 %}#eab308{% else %}#ef4444{% endif %};border-radius:4px;transition:width 0.5s;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Graphe frequence par semaine -->
|
|
{% if patch_weekly %}
|
|
<div class="mb-4">
|
|
<div class="text-xs text-gray-500 mb-2">Serveurs patches par semaine</div>
|
|
<div style="display:flex;align-items:flex-end;gap:2px;height:120px;">
|
|
{% set max_cnt = patch_weekly|map(attribute='cnt')|max %}
|
|
{% for w in patch_weekly %}
|
|
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;" title="{{ w.week }}: {{ w.cnt }} serveurs">
|
|
<div style="font-size:9px;color:#94a3b8;margin-bottom:2px;">{{ w.cnt }}</div>
|
|
<div style="width:100%;background:#22c55e;border-radius:2px 2px 0 0;min-height:2px;height:{{ (w.cnt / max_cnt * 100)|int }}%;opacity:0.8;"></div>
|
|
<div style="font-size:8px;color:#6b7280;margin-top:2px;transform:rotate(-45deg);white-space:nowrap;">{{ w.week }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Par domaine -->
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<div class="text-xs text-gray-500 mb-2">Par domaine</div>
|
|
<table class="w-full table-cyber text-xs">
|
|
<thead><tr><th class="text-left p-1">Domaine</th><th class="p-1">Total</th><th class="p-1">Patché</th><th class="p-1">2x</th><th class="p-1">%</th></tr></thead>
|
|
<tbody>
|
|
{% for d in patch_by_domain %}
|
|
{% set pct_d = (d.patched / d.total * 100)|int if d.total > 0 else 0 %}
|
|
<tr>
|
|
<td class="p-1">{{ d.domain }}</td>
|
|
<td class="p-1 text-center">{{ d.total }}</td>
|
|
<td class="p-1 text-center text-cyber-green">{{ d.patched }}</td>
|
|
<td class="p-1 text-center text-blue-400">{{ d.patched_twice }}</td>
|
|
<td class="p-1 text-center">
|
|
<span class="{% if pct_d >= 80 %}text-cyber-green{% elif pct_d >= 50 %}text-cyber-yellow{% else %}text-cyber-red{% endif %} font-bold">{{ pct_d }}%</span>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div>
|
|
<!-- Par environnement -->
|
|
<div class="text-xs text-gray-500 mb-2">Par environnement</div>
|
|
<table class="w-full table-cyber text-xs mb-4">
|
|
<thead><tr><th class="text-left p-1">Env</th><th class="p-1">Total</th><th class="p-1">Patché</th><th class="p-1">%</th></tr></thead>
|
|
<tbody>
|
|
{% for e in patch_by_env %}
|
|
{% set pct_e = (e.patched / e.total * 100)|int if e.total > 0 else 0 %}
|
|
<tr>
|
|
<td class="p-1">{{ e.env }}</td>
|
|
<td class="p-1 text-center">{{ e.total }}</td>
|
|
<td class="p-1 text-center text-cyber-green">{{ e.patched }}</td>
|
|
<td class="p-1 text-center"><span class="{% if pct_e >= 80 %}text-cyber-green{% elif pct_e >= 50 %}text-cyber-yellow{% else %}text-cyber-red{% endif %} font-bold">{{ pct_e }}%</span></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Par zone -->
|
|
<div class="text-xs text-gray-500 mb-2">Par zone réseau</div>
|
|
<table class="w-full table-cyber text-xs">
|
|
<thead><tr><th class="text-left p-1">Zone</th><th class="p-1">Total</th><th class="p-1">Patché</th><th class="p-1">Jamais</th><th class="p-1">%</th></tr></thead>
|
|
<tbody>
|
|
{% for z in patch_by_zone %}
|
|
{% set pct_z = (z.patched / z.total * 100)|int if z.total > 0 else 0 %}
|
|
<tr>
|
|
<td class="p-1">{{ z.zone }}</td>
|
|
<td class="p-1 text-center">{{ z.total }}</td>
|
|
<td class="p-1 text-center text-cyber-green">{{ z.patched }}</td>
|
|
<td class="p-1 text-center text-cyber-red">{{ z.never }}</td>
|
|
<td class="p-1 text-center"><span class="{% if pct_z >= 80 %}text-cyber-green{% elif pct_z >= 50 %}text-cyber-yellow{% else %}text-cyber-red{% endif %} font-bold">{{ pct_z }}%</span></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Par domaine (inventaire) -->
|
|
<div class="card p-4 mb-4">
|
|
<h3 class="text-sm font-bold text-cyber-accent mb-3">Inventaire par domaine</h3>
|
|
<table class="w-full table-cyber text-xs">
|
|
<thead><tr><th class="text-left p-2">Domaine</th><th class="p-2">Total</th><th class="p-2">Actifs</th><th class="p-2">Linux</th><th class="p-2">Windows</th></tr></thead>
|
|
<tbody>
|
|
{% for d in domains %}
|
|
<tr>
|
|
<td class="p-2 font-medium">{{ d.name }}</td>
|
|
<td class="p-2 text-center">{{ d.total }}</td>
|
|
<td class="p-2 text-center text-cyber-green">{{ d.actifs }}</td>
|
|
<td class="p-2 text-center">{{ d.linux }}</td>
|
|
<td class="p-2 text-center">{{ d.windows }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Par tier + quick stats -->
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="card p-4">
|
|
<h3 class="text-sm font-bold text-cyber-accent mb-3">Par tier</h3>
|
|
<div class="flex gap-3">
|
|
{% for t in tiers %}
|
|
<div class="flex-1 text-center p-3 rounded" style="background: {% if t[0] == 'tier0' %}#ff336622{% elif t[0] == 'tier1' %}#ff880022{% elif t[0] == 'tier2' %}#ffcc0022{% else %}#00ff8822{% endif %}">
|
|
<div class="text-lg font-bold">{{ t[1] }}</div>
|
|
<div class="text-xs text-gray-400">{{ t[0] }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="card p-4">
|
|
<h3 class="text-sm font-bold text-cyber-accent mb-3">Quick stats</h3>
|
|
<div class="space-y-2 text-xs">
|
|
<div class="flex justify-between"><span class="text-gray-500">Décommissionnés</span><span class="text-cyber-red font-bold">{{ stats.decom }}</span></div>
|
|
<div class="flex justify-between"><span class="text-gray-500">EOL</span><span class="text-cyber-red font-bold">{{ stats.eol }}</span></div>
|
|
<div class="flex justify-between"><span class="text-gray-500">Assets Qualys</span><span class="text-cyber-accent font-bold">{{ stats.qualys_assets }}</span></div>
|
|
<div class="flex justify-between"><span class="text-gray-500">Tags Qualys</span><span class="text-cyber-yellow font-bold">{{ stats.qualys_tags }}</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|