Export CSV avec filtres (domaine/zone/recherche/KPI), BOM UTF-8
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c42708db75
commit
df03852c86
@ -1,7 +1,7 @@
|
||||
"""Router Audit Complet — import JSON, liste, detail, carte flux, carte applicative"""
|
||||
import json
|
||||
from fastapi import APIRouter, Request, Depends, UploadFile, File
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import text
|
||||
from ..dependencies import get_db, get_current_user, get_user_perms, can_view, base_context
|
||||
@ -192,6 +192,80 @@ async def audit_full_import(request: Request, db=Depends(get_db),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audit-full/export-csv")
|
||||
async def audit_full_export_csv(request: Request, db=Depends(get_db)):
|
||||
user = get_current_user(request)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
import io, csv
|
||||
filtre = request.query_params.get("filter", "")
|
||||
search = request.query_params.get("q", "").strip()
|
||||
domain = request.query_params.get("domain", "")
|
||||
|
||||
audits = get_latest_audits(db, limit=9999)
|
||||
|
||||
# Memes filtres que la page liste
|
||||
if filtre == "reboot":
|
||||
audits = [a for a in audits if a.reboot_required]
|
||||
elif filtre == "uptime":
|
||||
audits = [a for a in audits if a.uptime and ("month" in a.uptime or "year" in a.uptime)]
|
||||
elif filtre and filtre.startswith("app_"):
|
||||
app_patterns = {
|
||||
"app_postgres": "postgres", "app_mariadb": "mariadb|mysqld",
|
||||
"app_hana": "hdb|sapstart|HANA", "app_oracle": "ora_pmon|oracle",
|
||||
"app_httpd": "httpd", "app_nginx": "nginx", "app_haproxy": "haproxy",
|
||||
"app_tomcat": "tomcat|catalina", "app_nodejs": "node",
|
||||
"app_redis": "redis", "app_mongodb": "mongod",
|
||||
"app_elastic": "elasticsearch|opensearch", "app_container": "docker|podman",
|
||||
"app_java": "java|\\.jar",
|
||||
}
|
||||
pattern = app_patterns.get(filtre, "")
|
||||
if pattern:
|
||||
ids = {r.id for r in db.execute(text("""
|
||||
SELECT id FROM server_audit_full
|
||||
WHERE status = 'ok'
|
||||
AND (services::text ~* :pat OR listen_ports::text ~* :pat OR processes::text ~* :pat)
|
||||
AND id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status = 'ok' ORDER BY hostname, audit_date DESC)
|
||||
"""), {"pat": pattern}).fetchall()}
|
||||
audits = [a for a in audits if a.id in ids]
|
||||
if domain:
|
||||
zone_servers = {r.hostname for r in db.execute(text(
|
||||
"SELECT s.hostname FROM servers s JOIN zones z ON s.zone_id = z.id WHERE z.name = :name"
|
||||
), {"name": domain}).fetchall()}
|
||||
if zone_servers:
|
||||
audits = [a for a in audits if a.hostname in zone_servers]
|
||||
else:
|
||||
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]
|
||||
if search:
|
||||
q = search.lower()
|
||||
audits = [a for a in audits if q in a.hostname.lower()]
|
||||
|
||||
# Generer CSV
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output, delimiter=";")
|
||||
writer.writerow(["Hostname", "OS", "Kernel", "Uptime", "Services", "Processus",
|
||||
"Ports", "Connexions", "Reboot requis", "Date audit"])
|
||||
for a in audits:
|
||||
writer.writerow([
|
||||
a.hostname, a.os_release or "", a.kernel or "", a.uptime or "",
|
||||
a.svc_count, a.proc_count, a.port_count, a.conn_count,
|
||||
"Oui" if a.reboot_required else "Non",
|
||||
a.audit_date.strftime("%Y-%m-%d %H:%M") if a.audit_date else "",
|
||||
])
|
||||
|
||||
output.seek(0)
|
||||
return StreamingResponse(
|
||||
iter(["\ufeff" + output.getvalue()]),
|
||||
media_type="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=audit_serveurs.csv"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audit-full/flow-map", response_class=HTMLResponse)
|
||||
async def audit_full_flow_map(request: Request, db=Depends(get_db)):
|
||||
user = get_current_user(request)
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
<input type="file" name="file" accept=".json" class="text-xs" required>
|
||||
<button type="submit" class="btn-primary px-4 py-2 text-sm" data-loading="Import en cours...|Insertion des donnees">Importer JSON</button>
|
||||
</form>
|
||||
<a href="/audit-full/export-csv{% if filter %}?filter={{ filter }}{% endif %}{% if search %}&q={{ search }}{% endif %}{% if domain %}&domain={{ domain }}{% endif %}" class="btn-sm bg-cyber-green text-black px-4 py-2">Exporter CSV</a>
|
||||
<a href="/audit-full/flow-map" class="btn-sm bg-cyber-border text-cyber-accent px-4 py-2">Carte flux</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user