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),
|
domain: str = Query(None), env: str = Query(None),
|
||||||
tier: str = Query(None), etat: str = Query(None),
|
tier: str = Query(None), etat: str = Query(None),
|
||||||
os: str = Query(None), owner: 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),
|
application: str = Query(None), application_id: int = Query(None),
|
||||||
search: str = Query(None), page: int = Query(1),
|
search: str = Query(None), page: int = Query(1),
|
||||||
sort: str = Query("hostname"), sort_dir: str = Query("asc")):
|
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")
|
return RedirectResponse(url="/login")
|
||||||
|
|
||||||
filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os,
|
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,
|
"application": application, "application_id": application_id,
|
||||||
"search": search}
|
"search": search}
|
||||||
servers, total = list_servers(db, filters, page, sort=sort, sort_dir=sort_dir)
|
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"]
|
params["zone"] = filters["zone"]
|
||||||
if filters.get("owner"):
|
if filters.get("owner"):
|
||||||
where.append("s.patch_os_owner = :owner"); params["owner"] = filters["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"):
|
if filters.get("application_id"):
|
||||||
where.append("s.application_id = :app_id"); params["app_id"] = filters["application_id"]
|
where.append("s.application_id = :app_id"); params["app_id"] = filters["application_id"]
|
||||||
elif filters.get("application"):
|
elif filters.get("application"):
|
||||||
|
|||||||
@ -53,6 +53,12 @@
|
|||||||
<option value="ipop" {% if filters.owner == 'ipop' %}selected{% endif %}>ipop</option>
|
<option value="ipop" {% if filters.owner == 'ipop' %}selected{% endif %}>ipop</option>
|
||||||
<option value="na" {% if filters.owner == 'na' %}selected{% endif %}>na</option>
|
<option value="na" {% if filters.owner == 'na' %}selected{% endif %}>na</option>
|
||||||
</select>
|
</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>
|
<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 %}
|
{% 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>
|
</select>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user