Pagination 50/page, recherche hostname, filtre domaine

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalid MOUTAOUAKIL 2026-04-06 16:32:07 +02:00
parent 2864a15817
commit d283e8ab8c
2 changed files with 84 additions and 17 deletions

View File

@ -24,10 +24,13 @@ async def audit_full_list(request: Request, db=Depends(get_db)):
if not can_view(perms, "audit"):
return RedirectResponse(url="/dashboard")
audits = get_latest_audits(db)
filtre = request.query_params.get("filter", "")
search = request.query_params.get("q", "").strip()
domain = request.query_params.get("domain", "")
page = int(request.query_params.get("page", "1"))
per_page = 50
# KPIs
# KPIs (toujours sur tout le jeu)
kpis = db.execute(text("""
SELECT
COUNT(*) as total,
@ -42,43 +45,72 @@ async def audit_full_list(request: Request, db=Depends(get_db)):
)) as disk_warning,
COUNT(*) FILTER (WHERE
uptime LIKE '%month%' OR uptime LIKE '%year%'
OR uptime LIKE '%week%' AND (
OR (uptime LIKE '%week%' AND (
CASE WHEN uptime ~ '(\d+) week' THEN (substring(uptime from '(\d+) week'))::int ELSE 0 END >= 17
)
))
) as uptime_long
FROM server_audit_full
WHERE status = 'ok'
AND id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status = 'ok' ORDER BY hostname, audit_date DESC)
""")).fetchone()
# Filtrer si demande
# Domaines pour le filtre
all_domains = db.execute(text(
"SELECT code, name FROM domains ORDER BY name"
)).fetchall()
# Requete avec filtres
audits = get_latest_audits(db, limit=9999)
# Filtre KPI
if filtre == "reboot":
audits = [a for a in audits if a.reboot_required]
elif filtre == "disk_critical":
ids = db.execute(text("""
ids = {r.id for r in db.execute(text("""
SELECT saf.id FROM server_audit_full saf
WHERE saf.status = 'ok' AND EXISTS (
SELECT 1 FROM jsonb_array_elements(saf.disk_usage) d WHERE (d->>'pct')::int >= 90
) AND saf.id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status = 'ok' ORDER BY hostname, audit_date DESC)
""")).fetchall()
id_set = {r.id for r in ids}
audits = [a for a in audits if a.id in id_set]
""")).fetchall()}
audits = [a for a in audits if a.id in ids]
elif filtre == "disk_warning":
ids = db.execute(text("""
ids = {r.id for r in db.execute(text("""
SELECT saf.id FROM server_audit_full saf
WHERE saf.status = 'ok' AND EXISTS (
SELECT 1 FROM jsonb_array_elements(saf.disk_usage) d WHERE (d->>'pct')::int >= 80
) AND saf.id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status = 'ok' ORDER BY hostname, audit_date DESC)
""")).fetchall()
id_set = {r.id for r in ids}
audits = [a for a in audits if a.id in id_set]
""")).fetchall()}
audits = [a for a in audits if a.id in ids]
elif filtre == "uptime":
audits = [a for a in audits if a.uptime and ("month" in a.uptime or "year" in a.uptime)]
# Filtre domaine
if domain:
domain_servers = {r.hostname for r in db.execute(text("""
SELECT s.hostname FROM servers s
JOIN domain_environments de ON s.domain_env_id = de.id
JOIN domains d ON de.domain_id = d.id
WHERE d.code = :dc
"""), {"dc": domain}).fetchall()}
audits = [a for a in audits if a.hostname in domain_servers]
# Recherche hostname
if search:
q = search.lower()
audits = [a for a in audits if q in a.hostname.lower()]
# Pagination
total_filtered = len(audits)
total_pages = max(1, (total_filtered + per_page - 1) // per_page)
page = max(1, min(page, total_pages))
audits_page = audits[(page - 1) * per_page : page * per_page]
ctx = base_context(request, db, user)
ctx.update({
"app_name": APP_NAME, "audits": audits, "kpis": kpis,
"filter": filtre,
"app_name": APP_NAME, "audits": audits_page, "kpis": kpis,
"filter": filtre, "search": search, "domain": domain,
"all_domains": all_domains,
"page": page, "total_pages": total_pages, "total_filtered": total_filtered,
"msg": request.query_params.get("msg"),
})
return templates.TemplateResponse("audit_full_list.html", ctx)

View File

@ -48,10 +48,25 @@
</a>
</div>
{% if filter %}
<div class="mb-3 text-xs text-gray-400">Filtre actif : <span class="text-cyber-accent">{{ filter }}</span> ({{ audits|length }} serveurs) <a href="/audit-full" class="text-cyber-accent underline">Tout voir</a></div>
<div class="mb-3 text-xs text-gray-400">Filtre actif : <span class="text-cyber-accent">{{ filter }}</span><a href="/audit-full" class="text-cyber-accent underline">Tout voir</a></div>
{% endif %}
{% endif %}
<!-- Recherche + filtre domaine -->
<div class="card p-3 mb-4 flex gap-3 items-center flex-wrap">
<form method="GET" action="/audit-full" class="flex gap-2 items-center flex-1">
{% if filter %}<input type="hidden" name="filter" value="{{ filter }}">{% endif %}
<input type="text" name="q" value="{{ search }}" placeholder="Rechercher un serveur..." class="text-xs py-1 px-3 flex-1 min-w-[200px] font-mono">
<select name="domain" class="text-xs py-1 px-2" onchange="this.form.submit()">
<option value="">Tous les domaines</option>
{% for d in all_domains %}<option value="{{ d.code }}" {% if domain == d.code %}selected{% endif %}>{{ d.name }}</option>{% endfor %}
</select>
<button type="submit" class="btn-primary px-3 py-1 text-xs">Filtrer</button>
{% if search or domain %}<a href="/audit-full{% if filter %}?filter={{ filter }}{% endif %}" class="text-xs text-gray-400 hover:text-cyber-accent">Reset</a>{% endif %}
</form>
<span class="text-xs text-gray-500">{{ total_filtered }} serveur(s)</span>
</div>
{% if audits %}
<div class="card overflow-x-auto">
<table class="w-full table-cyber text-xs">
@ -84,11 +99,31 @@
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if total_pages > 1 %}
<div class="flex justify-between items-center p-3 border-t border-cyber-border">
<span class="text-xs text-gray-500">Page {{ page }}/{{ total_pages }} ({{ total_filtered }} serveurs)</span>
<div class="flex gap-1">
{% if page > 1 %}
<a href="/audit-full?page=1{% if filter %}&filter={{ filter }}{% endif %}{% if search %}&q={{ search }}{% endif %}{% if domain %}&domain={{ domain }}{% endif %}" class="btn-sm bg-cyber-border text-gray-400 px-2 py-1 text-xs">1</a>
{% if page > 2 %}<a href="/audit-full?page={{ page - 1 }}{% if filter %}&filter={{ filter }}{% endif %}{% if search %}&q={{ search }}{% endif %}{% if domain %}&domain={{ domain }}{% endif %}" class="btn-sm bg-cyber-border text-gray-400 px-2 py-1 text-xs">&lt;</a>{% endif %}
{% endif %}
<span class="btn-sm bg-cyber-accent text-black px-2 py-1 text-xs font-bold">{{ page }}</span>
{% if page < total_pages %}
<a href="/audit-full?page={{ page + 1 }}{% if filter %}&filter={{ filter }}{% endif %}{% if search %}&q={{ search }}{% endif %}{% if domain %}&domain={{ domain }}{% endif %}" class="btn-sm bg-cyber-border text-gray-400 px-2 py-1 text-xs">&gt;</a>
<a href="/audit-full?page={{ total_pages }}{% if filter %}&filter={{ filter }}{% endif %}{% if search %}&q={{ search }}{% endif %}{% if domain %}&domain={{ domain }}{% endif %}" class="btn-sm bg-cyber-border text-gray-400 px-2 py-1 text-xs">{{ total_pages }}</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% else %}
<div class="card p-8 text-center text-gray-500">
<p class="text-sm">Aucun audit importe.</p>
<p class="text-sm">Aucun audit{% if search or domain or filter %} correspondant aux filtres{% endif %}.</p>
{% if not search and not domain and not filter %}
<p class="text-xs mt-2">Lancez le standalone sur vos serveurs puis importez le JSON ici.</p>
{% endif %}
</div>
{% endif %}
{% endblock %}