Pagination 50/page, recherche hostname, filtre domaine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2864a15817
commit
d283e8ab8c
@ -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)
|
||||
|
||||
@ -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"><</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">></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 %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user