Histogramme patching: barres empilées vert (patché) + rouge (annulé/reporté), total affiché
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c05ec932db
commit
4517dcdd39
@ -288,11 +288,14 @@ async def audit_full_patching(request: Request, db=Depends(get_db)):
|
||||
patch_weekly = []
|
||||
if year == 2026:
|
||||
patch_weekly = db.execute(text(
|
||||
"SELECT last_patch_week as week, COUNT(*) as cnt FROM server_audit_full"
|
||||
" WHERE status IN ('ok','partial') AND last_patch_year = 2026 AND last_patch_week IS NOT NULL"
|
||||
" AND (patch_status_2026 = 'patched' OR patch_status_2026 IS NULL)"
|
||||
" AND id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status IN ('ok','partial') ORDER BY hostname, audit_date DESC)"
|
||||
" GROUP BY last_patch_week ORDER BY last_patch_week"
|
||||
"SELECT pw.week, SUM(pw.patched) as patched, SUM(pw.cancelled) as cancelled FROM ("
|
||||
" SELECT unnest(string_to_array(patch_weeks_2026, ',')) as week,"
|
||||
" CASE WHEN patch_status_2026 = 'patched' THEN 1 ELSE 0 END as patched,"
|
||||
" CASE WHEN patch_status_2026 IN ('cancelled','reported') THEN 1 ELSE 0 END as cancelled"
|
||||
" FROM server_audit_full"
|
||||
" WHERE status IN ('ok','partial') AND patch_weeks_2026 IS NOT NULL AND patch_weeks_2026 != ''"
|
||||
" AND id IN (SELECT DISTINCT ON (hostname) id FROM server_audit_full WHERE status IN ('ok','partial') ORDER BY hostname, audit_date DESC)"
|
||||
") pw WHERE pw.week != '' GROUP BY pw.week ORDER BY pw.week"
|
||||
)).fetchall()
|
||||
|
||||
all_domains = db.execute(text("SELECT code, name, 'domain' as type FROM domains ORDER BY name")).fetchall()
|
||||
|
||||
@ -59,13 +59,18 @@
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
{% if patch_weekly %}
|
||||
<div class="card p-3">
|
||||
<div class="text-xs text-gray-500 mb-2">Dernière semaine de patch par serveur</div>
|
||||
<div class="text-xs text-gray-500 mb-2">Serveurs par semaine <span class="text-cyber-green">vert=patché</span> <span class="text-cyber-red">rouge=annulé/reporté</span></div>
|
||||
<div style="display:flex;align-items:flex-end;gap:2px;height:100px;">
|
||||
{% set max_cnt = patch_weekly|map(attribute='cnt')|max %}
|
||||
{% set max_cnt = patch_weekly|map(attribute='patched')|map('int')|max %}
|
||||
{% for w in patch_weekly %}
|
||||
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;" title="{{ w.week }}: {{ w.cnt }}">
|
||||
<div style="font-size:8px;color:#94a3b8;">{{ w.cnt }}</div>
|
||||
<div style="width:100%;background:#22c55e;border-radius:2px 2px 0 0;min-height:2px;height:{{ (w.cnt / max_cnt * 100)|int }}%;opacity:0.8;"></div>
|
||||
{% set total = (w.patched|int) + (w.cancelled|int) %}
|
||||
{% set max_total = max_cnt if max_cnt > 0 else 1 %}
|
||||
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;" title="{{ w.week }}: {{ w.patched }} patchés, {{ w.cancelled }} annulés">
|
||||
<div style="font-size:8px;color:#94a3b8;">{{ total }}</div>
|
||||
<div style="width:100%;display:flex;flex-direction:column;justify-content:flex-end;height:{{ (total / max_total * 100)|int }}%;min-height:2px;">
|
||||
{% if w.cancelled|int > 0 %}<div style="width:100%;background:#ef4444;min-height:2px;height:{{ (w.cancelled|int / total * 100)|int }}%;opacity:0.8;border-radius:2px 2px 0 0;"></div>{% endif %}
|
||||
<div style="width:100%;background:#22c55e;min-height:2px;flex:1;opacity:0.8;{% if w.cancelled|int == 0 %}border-radius:2px 2px 0 0;{% endif %}"></div>
|
||||
</div>
|
||||
<div style="font-size:7px;color:#6b7280;margin-top:2px;transform:rotate(-45deg);white-space:nowrap;">{{ w.week }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user