- Permissions 100% depuis user_permissions (plus de hardcode) - Middleware injecte perms dans chaque requête - Créneaux auto: 09h-12h30 / 14h-16h45, pas 15min, hprod lun-mar, prod mer-jeu - Assignations par défaut: par domaine, app_type, zone, serveur (table default_assignments) - Auto-liaison app_group: même intervenant recette+prod - Audit Splunk: /var/log/patchcenter_audit.json (JSON one-line par event) - Login/logout/campagnes/prereqs loggés en base + fichier - Page erreur maintenance (500/404) avec contact SecOps - Accents français dans toute lUI - Operator affiché comme Intervenant - Session 1h, redirect / vers dashboard si connecté - Demo mode prereqs (DEMO_MODE=True) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
5.3 KiB
HTML
90 lines
5.3 KiB
HTML
{% extends 'base.html' %}
|
|
{% block title %}Audit Serveurs{% endblock %}
|
|
{% block content %}
|
|
<h2 class="text-xl font-bold text-cyber-accent mb-4">Audit Serveurs <span class="text-sm text-gray-500">({{ stats.total }})</span></h2>
|
|
|
|
<!-- KPIs -->
|
|
<div class="grid grid-cols-8 gap-2 mb-4">
|
|
<a href="/audit" class="card p-2 text-center hover:border-cyber-accent/50 {% if not filter %}border-cyber-accent{% endif %}">
|
|
<div class="text-lg font-bold text-cyber-green">{{ stats.ok }}</div>
|
|
<div class="text-[10px] text-gray-500">Connectes</div>
|
|
</a>
|
|
<a href="/audit?filter=failed" class="card p-2 text-center hover:border-cyber-accent/50 {% if filter == 'failed' %}border-cyber-accent{% endif %}">
|
|
<div class="text-lg font-bold text-cyber-red">{{ stats.failed }}</div>
|
|
<div class="text-[10px] text-gray-500">Échoués</div>
|
|
</a>
|
|
<a href="/audit?filter=disk" class="card p-2 text-center hover:border-cyber-accent/50 {% if filter == 'disk' %}border-cyber-accent{% endif %}">
|
|
<div class="text-lg font-bold text-cyber-yellow">{{ stats.disk_alerts }}</div>
|
|
<div class="text-[10px] text-gray-500">Alerte disque</div>
|
|
</a>
|
|
<a href="/audit?filter=no_autostart" class="card p-2 text-center hover:border-cyber-accent/50 {% if filter == 'no_autostart' %}border-cyber-accent{% endif %}">
|
|
<div class="text-lg font-bold text-cyber-yellow">{{ stats.no_autostart }}</div>
|
|
<div class="text-[10px] text-gray-500">Sans auto-start</div>
|
|
</a>
|
|
<a href="/audit?filter=failed_svc" class="card p-2 text-center hover:border-cyber-accent/50 {% if filter == 'failed_svc' %}border-cyber-accent{% endif %}">
|
|
<div class="text-lg font-bold text-cyber-red">{{ stats.failed_svc }}</div>
|
|
<div class="text-[10px] text-gray-500">Svc en echec</div>
|
|
</a>
|
|
<div class="card p-2 text-center">
|
|
<div class="text-lg font-bold text-cyber-green">{{ stats.qualys_ok }}</div>
|
|
<div class="text-[10px] text-gray-500">Qualys OK</div>
|
|
</div>
|
|
<div class="card p-2 text-center">
|
|
<div class="text-lg font-bold text-cyber-green">{{ stats.s1_ok }}</div>
|
|
<div class="text-[10px] text-gray-500">SentinelOne OK</div>
|
|
</div>
|
|
<div class="card p-2 text-center">
|
|
<form method="GET" class="flex gap-1">
|
|
<input type="text" name="search" value="{{ search or '' }}" placeholder="Hostname" class="text-xs py-1 px-2 w-full">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="card overflow-x-auto">
|
|
<table class="w-full table-cyber">
|
|
<thead><tr>
|
|
<th class="text-left p-2">Hostname</th>
|
|
<th class="p-2">Statut</th>
|
|
<th class="p-2">Connexion</th>
|
|
<th class="p-2">Kernel</th>
|
|
<th class="p-2">Uptime</th>
|
|
<th class="p-2">Disque</th>
|
|
<th class="p-2">Qualys</th>
|
|
<th class="p-2">S1</th>
|
|
<th class="p-2">Sans auto</th>
|
|
<th class="p-2">Svc KO</th>
|
|
<th class="p-2">Detail</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for e in entries %}
|
|
<tr class="{% if e.status != 'OK' %}bg-red-900/10{% elif e.disk_alert %}bg-yellow-900/10{% endif %}">
|
|
<td class="p-2 font-mono text-sm text-cyber-accent">{{ e.hostname }}</td>
|
|
<td class="p-2 text-center"><span class="badge {% if e.status == 'OK' %}badge-green{% else %}badge-red{% endif %}">{{ e.status[:10] }}</span></td>
|
|
<td class="p-2 text-center text-[10px] text-gray-400">{% if e.resolved_fqdn %}{{ e.resolved_fqdn[:25] }}{% else %}-{% endif %}</td>
|
|
<td class="p-2 text-center text-[10px] text-gray-400">{{ (e.kernel or '-')[:20] }}</td>
|
|
<td class="p-2 text-center text-[10px] text-gray-400">{{ (e.uptime or '-')[:15] }}</td>
|
|
<td class="p-2 text-center">
|
|
{% if e.disk_alert %}<span class="badge badge-red" title="{{ e.disk_detail[:80] if e.disk_detail else '' }}">ALERTE</span>
|
|
{% elif e.status == 'OK' %}<span class="text-cyber-green text-xs">OK</span>
|
|
{% else %}-{% endif %}
|
|
</td>
|
|
<td class="p-2 text-center">{% if e.qualys_active %}<span class="text-cyber-green text-xs">OK</span>{% else %}<span class="text-cyber-red text-xs">KO</span>{% endif %}</td>
|
|
<td class="p-2 text-center">{% if e.sentinelone_active %}<span class="text-cyber-green text-xs">OK</span>{% else %}<span class="text-cyber-red text-xs">KO</span>{% endif %}</td>
|
|
<td class="p-2 text-center text-[10px]">{% if e.running_not_enabled %}<span class="text-cyber-yellow" title="{{ e.running_not_enabled[:100] }}">{{ e.running_not_enabled.split('\n')|length }}</span>{% else %}-{% endif %}</td>
|
|
<td class="p-2 text-center text-[10px]">{% if e.failed_services %}<span class="text-cyber-red">{{ e.failed_services[:20] }}</span>{% else %}-{% endif %}</td>
|
|
<td class="p-2 text-center">
|
|
<button class="btn-sm bg-cyber-border text-cyber-accent"
|
|
hx-get="/audit/{{ e.id }}" hx-target="#audit-detail" hx-swap="innerHTML"
|
|
onclick="document.getElementById('audit-detail').style.display='block'; window.scrollTo({top:0,behavior:'smooth'})">Voir</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Panel detail -->
|
|
<div id="audit-detail" class="card mt-4 p-5" style="display:none"></div>
|
|
{% endblock %}
|