patchcenter/app/templates/dashboard.html
Khalid MOUTAOUAKIL ed23cc3fb6 Dashboard patching 2026: KPIs, barre progression, graphe semaines, domaine/env/zone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:54:00 +02:00

191 lines
11 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:nowrap;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-accent">{{ stats.total_servers }}</div><div class="text-xs text-gray-500">Serveurs</div></div>
<div class="card p-3 text-center" 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></div>
<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>
<div class="card p-3 text-center" style="flex:1;min-width:0"><div class="text-2xl font-bold text-cyber-yellow">{{ stats.qualys_tags }}</div><div class="text-xs text-gray-500">Tags Qualys</div></div>
<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>
<!-- ═══════ 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">Audites</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">Patches 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">Patche</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">Patche</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 reseau</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">Patche</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">Decommissionnes</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 %}