Servers: filtre licence (active/obsolete/els/sans licence)
This commit is contained in:
parent
2a11a27675
commit
e2b984c2c4
96
app/routers/patch_history.py
Normal file
96
app/routers/patch_history.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""Router Historique patching — vue de patch_history"""
|
||||
from fastapi import APIRouter, Request, Depends, Query
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import text
|
||||
from ..dependencies import get_db, get_current_user
|
||||
from ..config import APP_NAME
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
|
||||
@router.get("/patching/historique", response_class=HTMLResponse)
|
||||
async def patch_history_page(request: Request, db=Depends(get_db),
|
||||
year: int = Query(None), week: int = Query(None),
|
||||
hostname: str = Query(None), page: int = Query(1)):
|
||||
user = get_current_user(request)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
from datetime import datetime
|
||||
if not year:
|
||||
year = datetime.now().year
|
||||
|
||||
per_page = 100
|
||||
offset = (page - 1) * per_page
|
||||
|
||||
# KPIs
|
||||
kpis = {}
|
||||
kpis["total"] = db.execute(text(
|
||||
"SELECT COUNT(*) FROM patch_history WHERE EXTRACT(YEAR FROM date_patch)=:y"
|
||||
), {"y": year}).scalar()
|
||||
kpis["servers"] = db.execute(text(
|
||||
"SELECT COUNT(DISTINCT server_id) FROM patch_history WHERE EXTRACT(YEAR FROM date_patch)=:y"
|
||||
), {"y": year}).scalar()
|
||||
kpis["patchables"] = db.execute(text(
|
||||
"SELECT COUNT(*) FROM servers WHERE etat='Production' AND patch_os_owner='secops'"
|
||||
)).scalar()
|
||||
kpis["never"] = db.execute(text("""
|
||||
SELECT COUNT(*) FROM servers s
|
||||
WHERE s.etat='Production' AND s.patch_os_owner='secops'
|
||||
AND NOT EXISTS (SELECT 1 FROM patch_history ph
|
||||
WHERE ph.server_id=s.id AND EXTRACT(YEAR FROM ph.date_patch)=:y)
|
||||
"""), {"y": year}).scalar()
|
||||
kpis["coverage_pct"] = round((kpis["servers"] / kpis["patchables"] * 100), 1) if kpis["patchables"] else 0
|
||||
|
||||
# Par semaine
|
||||
by_week = db.execute(text("""
|
||||
SELECT TO_CHAR(date_patch, 'IW') as week_num,
|
||||
COUNT(DISTINCT server_id) as servers
|
||||
FROM patch_history
|
||||
WHERE EXTRACT(YEAR FROM date_patch)=:y
|
||||
GROUP BY TO_CHAR(date_patch, 'IW')
|
||||
ORDER BY week_num
|
||||
"""), {"y": year}).fetchall()
|
||||
|
||||
# Filtres
|
||||
where = ["EXTRACT(YEAR FROM ph.date_patch)=:y"]
|
||||
params = {"y": year, "limit": per_page, "offset": offset}
|
||||
if week:
|
||||
where.append("EXTRACT(WEEK FROM ph.date_patch)=:wk")
|
||||
params["wk"] = week
|
||||
if hostname:
|
||||
where.append("s.hostname ILIKE :h")
|
||||
params["h"] = f"%{hostname}%"
|
||||
wc = " AND ".join(where)
|
||||
|
||||
total_filtered = db.execute(text(
|
||||
f"SELECT COUNT(*) FROM patch_history ph JOIN servers s ON ph.server_id=s.id WHERE {wc}"
|
||||
), params).scalar()
|
||||
|
||||
rows = db.execute(text(f"""
|
||||
SELECT s.id as sid, s.hostname, s.os_family, s.etat,
|
||||
ph.date_patch, ph.status, ph.notes,
|
||||
z.name as zone
|
||||
FROM patch_history ph
|
||||
JOIN servers s ON ph.server_id = s.id
|
||||
LEFT JOIN zones z ON s.zone_id = z.id
|
||||
WHERE {wc}
|
||||
ORDER BY ph.date_patch DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
"""), params).fetchall()
|
||||
|
||||
# Années dispo
|
||||
years = db.execute(text("""
|
||||
SELECT DISTINCT EXTRACT(YEAR FROM date_patch)::int as y
|
||||
FROM patch_history ORDER BY y DESC
|
||||
""")).fetchall()
|
||||
|
||||
return templates.TemplateResponse("patch_history.html", {
|
||||
"request": request, "user": user, "app_name": APP_NAME,
|
||||
"kpis": kpis, "by_week": by_week, "rows": rows,
|
||||
"year": year, "week": week, "hostname": hostname,
|
||||
"page": page, "per_page": per_page, "total_filtered": total_filtered,
|
||||
"years": [y.y for y in years],
|
||||
})
|
||||
@ -20,7 +20,7 @@ async def servers_list(request: Request, db=Depends(get_db),
|
||||
domain: str = Query(None), env: str = Query(None),
|
||||
tier: str = Query(None), etat: str = Query(None),
|
||||
os: str = Query(None), owner: str = Query(None),
|
||||
zone: str = Query(None),
|
||||
zone: str = Query(None), licence: str = Query(None),
|
||||
application: str = Query(None), application_id: int = Query(None),
|
||||
search: str = Query(None), page: int = Query(1),
|
||||
sort: str = Query("hostname"), sort_dir: str = Query("asc")):
|
||||
@ -29,7 +29,7 @@ async def servers_list(request: Request, db=Depends(get_db),
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os,
|
||||
"owner": owner, "zone": zone,
|
||||
"owner": owner, "zone": zone, "licence": licence,
|
||||
"application": application, "application_id": application_id,
|
||||
"search": search}
|
||||
servers, total = list_servers(db, filters, page, sort=sort, sort_dir=sort_dir)
|
||||
|
||||
@ -132,6 +132,11 @@ def list_servers(db, filters, page=1, per_page=50, sort="hostname", sort_dir="as
|
||||
params["zone"] = filters["zone"]
|
||||
if filters.get("owner"):
|
||||
where.append("s.patch_os_owner = :owner"); params["owner"] = filters["owner"]
|
||||
if filters.get("licence"):
|
||||
if filters["licence"] == "__null__":
|
||||
where.append("s.licence_support IS NULL")
|
||||
else:
|
||||
where.append("s.licence_support = :licence"); params["licence"] = filters["licence"]
|
||||
if filters.get("application_id"):
|
||||
where.append("s.application_id = :app_id"); params["app_id"] = filters["application_id"]
|
||||
elif filters.get("application"):
|
||||
|
||||
@ -53,6 +53,12 @@
|
||||
<option value="ipop" {% if filters.owner == 'ipop' %}selected{% endif %}>ipop</option>
|
||||
<option value="na" {% if filters.owner == 'na' %}selected{% endif %}>na</option>
|
||||
</select>
|
||||
<select name="licence" onchange="this.form.submit()"><option value="">Licence</option>
|
||||
<option value="active" {% if filters.licence == 'active' %}selected{% endif %}>active</option>
|
||||
<option value="obsolete" {% if filters.licence == 'obsolete' %}selected{% endif %}>obsolete (EOL)</option>
|
||||
<option value="els" {% if filters.licence == 'els' %}selected{% endif %}>els</option>
|
||||
<option value="__null__" {% if filters.licence == '__null__' %}selected{% endif %}>(Sans licence)</option>
|
||||
</select>
|
||||
<select name="application" onchange="this.form.submit()" style="max-width:200px"><option value="">Solution app.</option>
|
||||
{% for a in applications_list %}<option value="{{ a.application_name }}" {% if filters.application == a.application_name %}selected{% endif %}>{{ a.application_name }} ({{ a.c }})</option>{% endfor %}
|
||||
</select>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user