Historique patching : filtres OS/zone/domaine/intervenant + colonnes table
This commit is contained in:
parent
14f809335e
commit
2ab2ceabba
@ -14,6 +14,8 @@ templates = Jinja2Templates(directory="app/templates")
|
|||||||
async def patch_history_page(request: Request, db=Depends(get_db),
|
async def patch_history_page(request: Request, db=Depends(get_db),
|
||||||
year: int = Query(None), week: int = Query(None),
|
year: int = Query(None), week: int = Query(None),
|
||||||
hostname: str = Query(None), source: str = Query(None),
|
hostname: str = Query(None), source: str = Query(None),
|
||||||
|
os_family: str = Query(None), zone: str = Query(None),
|
||||||
|
domain: str = Query(None), intervenant: str = Query(None),
|
||||||
page: int = Query(1)):
|
page: int = Query(1)):
|
||||||
user = get_current_user(request)
|
user = get_current_user(request)
|
||||||
if not user:
|
if not user:
|
||||||
@ -87,6 +89,25 @@ async def patch_history_page(request: Request, db=Depends(get_db),
|
|||||||
) u GROUP BY week_num ORDER BY week_num
|
) u GROUP BY week_num ORDER BY week_num
|
||||||
"""), {"y": year}).fetchall()
|
"""), {"y": year}).fetchall()
|
||||||
|
|
||||||
|
# Listes pour les filtres (selon annee courante)
|
||||||
|
filter_opts = {}
|
||||||
|
filter_opts["os"] = [r.os for r in db.execute(text("""
|
||||||
|
SELECT DISTINCT s.os_family as os FROM servers s
|
||||||
|
WHERE s.os_family IS NOT NULL AND s.os_family <> ''
|
||||||
|
ORDER BY 1
|
||||||
|
""")).fetchall()]
|
||||||
|
filter_opts["zones"] = [r.zone for r in db.execute(text("""
|
||||||
|
SELECT DISTINCT z.name as zone FROM zones z ORDER BY 1
|
||||||
|
""")).fetchall()]
|
||||||
|
filter_opts["domains"] = [r.dom for r in db.execute(text("""
|
||||||
|
SELECT DISTINCT d.name as dom FROM domains d ORDER BY 1
|
||||||
|
""")).fetchall()]
|
||||||
|
filter_opts["intervenants"] = [r.interv for r in db.execute(text("""
|
||||||
|
SELECT DISTINCT intervenant_name as interv FROM patch_history
|
||||||
|
WHERE intervenant_name IS NOT NULL AND intervenant_name <> ''
|
||||||
|
ORDER BY 1
|
||||||
|
""")).fetchall()]
|
||||||
|
|
||||||
where_ph = ["EXTRACT(YEAR FROM ph.date_patch)=:y"]
|
where_ph = ["EXTRACT(YEAR FROM ph.date_patch)=:y"]
|
||||||
where_qw = ["qr.year=:y", "qe.status='patched'"]
|
where_qw = ["qr.year=:y", "qe.status='patched'"]
|
||||||
params = {"y": year, "limit": per_page, "offset": offset}
|
params = {"y": year, "limit": per_page, "offset": offset}
|
||||||
@ -99,6 +120,22 @@ async def patch_history_page(request: Request, db=Depends(get_db),
|
|||||||
where_ph.append("s.hostname ILIKE :h")
|
where_ph.append("s.hostname ILIKE :h")
|
||||||
where_qw.append("s.hostname ILIKE :h")
|
where_qw.append("s.hostname ILIKE :h")
|
||||||
params["h"] = f"%{hostname}%"
|
params["h"] = f"%{hostname}%"
|
||||||
|
if os_family:
|
||||||
|
where_ph.append("s.os_family=:os")
|
||||||
|
where_qw.append("s.os_family=:os")
|
||||||
|
params["os"] = os_family
|
||||||
|
if zone:
|
||||||
|
where_ph.append("z.name=:zn")
|
||||||
|
where_qw.append("z.name=:zn")
|
||||||
|
params["zn"] = zone
|
||||||
|
if domain:
|
||||||
|
where_ph.append("d.name=:dm")
|
||||||
|
where_qw.append("d.name=:dm")
|
||||||
|
params["dm"] = domain
|
||||||
|
if intervenant:
|
||||||
|
where_ph.append("ph.intervenant_name=:iv")
|
||||||
|
where_qw.append("1=0") # quickwin n'a pas ce champ
|
||||||
|
params["iv"] = intervenant
|
||||||
if source == "import":
|
if source == "import":
|
||||||
where_ph.append("ph.campaign_id IS NULL")
|
where_ph.append("ph.campaign_id IS NULL")
|
||||||
elif source == "standard":
|
elif source == "standard":
|
||||||
@ -107,24 +144,29 @@ async def patch_history_page(request: Request, db=Depends(get_db),
|
|||||||
wc_ph = " AND ".join(where_ph)
|
wc_ph = " AND ".join(where_ph)
|
||||||
wc_qw = " AND ".join(where_qw)
|
wc_qw = " AND ".join(where_qw)
|
||||||
|
|
||||||
skip_qw = source in ("import", "standard")
|
skip_qw = source in ("import", "standard") or bool(intervenant)
|
||||||
skip_ph = source == "quickwin"
|
skip_ph = source == "quickwin"
|
||||||
|
|
||||||
|
ph_joins = """
|
||||||
|
JOIN servers s ON ph.server_id=s.id
|
||||||
|
LEFT JOIN zones z ON s.zone_id=z.id
|
||||||
|
LEFT JOIN domain_environments de ON s.domain_env_id=de.id
|
||||||
|
LEFT JOIN domains d ON de.domain_id=d.id
|
||||||
|
LEFT JOIN campaigns c ON ph.campaign_id=c.id
|
||||||
|
"""
|
||||||
|
qw_joins = """
|
||||||
|
JOIN quickwin_runs qr ON qe.run_id=qr.id
|
||||||
|
JOIN servers s ON qe.server_id=s.id
|
||||||
|
LEFT JOIN zones z ON s.zone_id=z.id
|
||||||
|
LEFT JOIN domain_environments de ON s.domain_env_id=de.id
|
||||||
|
LEFT JOIN domains d ON de.domain_id=d.id
|
||||||
|
"""
|
||||||
|
|
||||||
count_parts = []
|
count_parts = []
|
||||||
if not skip_ph:
|
if not skip_ph:
|
||||||
count_parts.append(f"""
|
count_parts.append(f"SELECT COUNT(*) FROM patch_history ph {ph_joins} WHERE {wc_ph}")
|
||||||
SELECT COUNT(*) FROM patch_history ph
|
|
||||||
JOIN servers s ON ph.server_id=s.id
|
|
||||||
LEFT JOIN campaigns c ON ph.campaign_id=c.id
|
|
||||||
WHERE {wc_ph}
|
|
||||||
""")
|
|
||||||
if not skip_qw:
|
if not skip_qw:
|
||||||
count_parts.append(f"""
|
count_parts.append(f"SELECT COUNT(*) FROM quickwin_entries qe {qw_joins} WHERE {wc_qw}")
|
||||||
SELECT COUNT(*) FROM quickwin_entries qe
|
|
||||||
JOIN quickwin_runs qr ON qe.run_id=qr.id
|
|
||||||
JOIN servers s ON qe.server_id=s.id
|
|
||||||
WHERE {wc_qw}
|
|
||||||
""")
|
|
||||||
count_sql = " + ".join(f"({p})" for p in count_parts) if count_parts else "0"
|
count_sql = " + ".join(f"({p})" for p in count_parts) if count_parts else "0"
|
||||||
total_filtered = db.execute(text(f"SELECT {count_sql}"), params).scalar()
|
total_filtered = db.execute(text(f"SELECT {count_sql}"), params).scalar()
|
||||||
|
|
||||||
@ -132,34 +174,33 @@ async def patch_history_page(request: Request, db=Depends(get_db),
|
|||||||
if not skip_ph:
|
if not skip_ph:
|
||||||
union_parts.append(f"""
|
union_parts.append(f"""
|
||||||
SELECT s.id as sid, s.hostname, s.os_family, s.etat,
|
SELECT s.id as sid, s.hostname, s.os_family, s.etat,
|
||||||
ph.date_patch, ph.status, ph.notes,
|
ph.date_patch, ph.status, ph.notes, ph.intervenant_name,
|
||||||
z.name as zone,
|
z.name as zone, d.name as domain_name,
|
||||||
CASE WHEN ph.campaign_id IS NULL THEN 'import'
|
CASE WHEN ph.campaign_id IS NULL THEN 'import'
|
||||||
ELSE COALESCE(c.campaign_type, 'standard') END as source_type,
|
ELSE COALESCE(c.campaign_type, 'standard') END as source_type,
|
||||||
c.id as campaign_id, c.label as campaign_label,
|
c.id as campaign_id, c.label as campaign_label,
|
||||||
NULL::int as run_id, NULL::text as run_label
|
NULL::int as run_id, NULL::text as run_label
|
||||||
FROM patch_history ph
|
FROM patch_history ph {ph_joins}
|
||||||
JOIN servers s ON ph.server_id=s.id
|
|
||||||
LEFT JOIN zones z ON s.zone_id=z.id
|
|
||||||
LEFT JOIN campaigns c ON ph.campaign_id=c.id
|
|
||||||
WHERE {wc_ph}
|
WHERE {wc_ph}
|
||||||
""")
|
""")
|
||||||
if not skip_qw:
|
if not skip_qw:
|
||||||
union_parts.append(f"""
|
union_parts.append(f"""
|
||||||
SELECT s.id as sid, s.hostname, s.os_family, s.etat,
|
SELECT s.id as sid, s.hostname, s.os_family, s.etat,
|
||||||
qe.patch_date as date_patch, qe.status, qe.notes,
|
qe.patch_date as date_patch, qe.status, qe.notes,
|
||||||
z.name as zone,
|
NULL::text as intervenant_name,
|
||||||
|
z.name as zone, d.name as domain_name,
|
||||||
'quickwin' as source_type,
|
'quickwin' as source_type,
|
||||||
NULL::int as campaign_id, NULL::text as campaign_label,
|
NULL::int as campaign_id, NULL::text as campaign_label,
|
||||||
qr.id as run_id, qr.label as run_label
|
qr.id as run_id, qr.label as run_label
|
||||||
FROM quickwin_entries qe
|
FROM quickwin_entries qe {qw_joins}
|
||||||
JOIN quickwin_runs qr ON qe.run_id=qr.id
|
|
||||||
JOIN servers s ON qe.server_id=s.id
|
|
||||||
LEFT JOIN zones z ON s.zone_id=z.id
|
|
||||||
WHERE {wc_qw}
|
WHERE {wc_qw}
|
||||||
""")
|
""")
|
||||||
if not union_parts:
|
if not union_parts:
|
||||||
union_parts.append("SELECT NULL::int as sid, NULL as hostname, NULL as os_family, NULL as etat, NULL::timestamptz as date_patch, NULL as status, NULL as notes, NULL as zone, NULL as source_type, NULL::int as campaign_id, NULL as campaign_label, NULL::int as run_id, NULL as run_label WHERE 1=0")
|
union_parts.append("""SELECT NULL::int as sid, NULL as hostname, NULL as os_family, NULL as etat,
|
||||||
|
NULL::timestamptz as date_patch, NULL as status, NULL as notes, NULL as intervenant_name,
|
||||||
|
NULL as zone, NULL as domain_name, NULL as source_type,
|
||||||
|
NULL::int as campaign_id, NULL as campaign_label, NULL::int as run_id, NULL as run_label
|
||||||
|
WHERE 1=0""")
|
||||||
|
|
||||||
union_sql = " UNION ALL ".join(union_parts)
|
union_sql = " UNION ALL ".join(union_parts)
|
||||||
rows = db.execute(text(f"""
|
rows = db.execute(text(f"""
|
||||||
@ -180,6 +221,8 @@ async def patch_history_page(request: Request, db=Depends(get_db),
|
|||||||
"request": request, "user": user, "app_name": APP_NAME,
|
"request": request, "user": user, "app_name": APP_NAME,
|
||||||
"kpis": kpis, "by_week": by_week, "by_source": by_source,
|
"kpis": kpis, "by_week": by_week, "by_source": by_source,
|
||||||
"rows": rows, "year": year, "week": week, "hostname": hostname,
|
"rows": rows, "year": year, "week": week, "hostname": hostname,
|
||||||
"source": source, "page": page, "per_page": per_page,
|
"source": source, "os_family": os_family, "zone": zone,
|
||||||
|
"domain": domain, "intervenant": intervenant,
|
||||||
|
"filter_opts": filter_opts, "page": page, "per_page": per_page,
|
||||||
"total_filtered": total_filtered, "years": [y.y for y in years],
|
"total_filtered": total_filtered, "years": [y.y for y in years],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -78,10 +78,26 @@
|
|||||||
<select name="source" class="text-xs py-1 px-2">
|
<select name="source" class="text-xs py-1 px-2">
|
||||||
<option value="">Toutes sources</option>
|
<option value="">Toutes sources</option>
|
||||||
<option value="import" {% if source == 'import' %}selected{% endif %}>Import xlsx</option>
|
<option value="import" {% if source == 'import' %}selected{% endif %}>Import xlsx</option>
|
||||||
<option value="standard" {% if source == 'standard' %}selected{% endif %}>Campagne standard</option>
|
<option value="standard" {% if source == 'standard' %}selected{% endif %}>Campagne std</option>
|
||||||
<option value="quickwin" {% if source == 'quickwin' %}selected{% endif %}>QuickWin</option>
|
<option value="quickwin" {% if source == 'quickwin' %}selected{% endif %}>QuickWin</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="text" name="hostname" value="{{ hostname or '' }}" placeholder="Hostname..." class="text-xs py-1 px-2" style="width:180px">
|
<select name="os_family" class="text-xs py-1 px-2">
|
||||||
|
<option value="">Tous OS</option>
|
||||||
|
{% for o in filter_opts.os %}<option value="{{ o }}" {% if os_family == o %}selected{% endif %}>{{ o }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
<select name="zone" class="text-xs py-1 px-2">
|
||||||
|
<option value="">Toutes zones</option>
|
||||||
|
{% for z in filter_opts.zones %}<option value="{{ z }}" {% if zone == z %}selected{% endif %}>{{ z }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
<select name="domain" class="text-xs py-1 px-2">
|
||||||
|
<option value="">Tous domaines</option>
|
||||||
|
{% for d in filter_opts.domains %}<option value="{{ d }}" {% if domain == d %}selected{% endif %}>{{ d }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
<select name="intervenant" class="text-xs py-1 px-2">
|
||||||
|
<option value="">Tous intervenants</option>
|
||||||
|
{% for i in filter_opts.intervenants %}<option value="{{ i }}" {% if intervenant == i %}selected{% endif %}>{{ i }}</option>{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="text" name="hostname" value="{{ hostname or '' }}" placeholder="Hostname..." class="text-xs py-1 px-2" style="width:140px">
|
||||||
<button type="submit" class="btn-primary px-3 py-1 text-xs">Filtrer</button>
|
<button type="submit" class="btn-primary px-3 py-1 text-xs">Filtrer</button>
|
||||||
<a href="/patching/historique?year={{ year }}" class="text-xs text-gray-500 hover:text-cyber-accent">Reset</a>
|
<a href="/patching/historique?year={{ year }}" class="text-xs text-gray-500 hover:text-cyber-accent">Reset</a>
|
||||||
<span class="text-xs text-gray-500 ml-auto">{{ total_filtered }} résultat{{ 's' if total_filtered != 1 }}</span>
|
<span class="text-xs text-gray-500 ml-auto">{{ total_filtered }} résultat{{ 's' if total_filtered != 1 }}</span>
|
||||||
@ -95,9 +111,11 @@
|
|||||||
<th class="p-2 text-left">Hostname</th>
|
<th class="p-2 text-left">Hostname</th>
|
||||||
<th class="p-2 text-center">OS</th>
|
<th class="p-2 text-center">OS</th>
|
||||||
<th class="p-2 text-center">Zone</th>
|
<th class="p-2 text-center">Zone</th>
|
||||||
|
<th class="p-2 text-center">Domaine</th>
|
||||||
<th class="p-2 text-center">État</th>
|
<th class="p-2 text-center">État</th>
|
||||||
<th class="p-2 text-center">Date</th>
|
<th class="p-2 text-center">Date</th>
|
||||||
<th class="p-2 text-center">Semaine</th>
|
<th class="p-2 text-center">Sem.</th>
|
||||||
|
<th class="p-2 text-center">Intervenant</th>
|
||||||
<th class="p-2 text-center">Source</th>
|
<th class="p-2 text-center">Source</th>
|
||||||
<th class="p-2 text-center">Status</th>
|
<th class="p-2 text-center">Status</th>
|
||||||
<th class="p-2 text-left">Notes</th>
|
<th class="p-2 text-left">Notes</th>
|
||||||
@ -108,9 +126,11 @@
|
|||||||
<td class="p-2 font-mono text-cyber-accent"><a href="/servers/{{ r.sid }}" class="hover:underline">{{ r.hostname }}</a></td>
|
<td class="p-2 font-mono text-cyber-accent"><a href="/servers/{{ r.sid }}" class="hover:underline">{{ r.hostname }}</a></td>
|
||||||
<td class="p-2 text-center text-gray-400">{{ (r.os_family or '-')[:6] }}</td>
|
<td class="p-2 text-center text-gray-400">{{ (r.os_family or '-')[:6] }}</td>
|
||||||
<td class="p-2 text-center"><span class="badge {% if r.zone == 'DMZ' %}badge-red{% else %}badge-gray{% endif %}">{{ r.zone or '-' }}</span></td>
|
<td class="p-2 text-center"><span class="badge {% if r.zone == 'DMZ' %}badge-red{% else %}badge-gray{% endif %}">{{ r.zone or '-' }}</span></td>
|
||||||
|
<td class="p-2 text-center text-gray-300">{{ (r.domain_name or '-')[:10] }}</td>
|
||||||
<td class="p-2 text-center"><span class="badge {% if r.etat == 'Production' %}badge-green{% else %}badge-yellow{% endif %}">{{ (r.etat or '-')[:6] }}</span></td>
|
<td class="p-2 text-center"><span class="badge {% if r.etat == 'Production' %}badge-green{% else %}badge-yellow{% endif %}">{{ (r.etat or '-')[:6] }}</span></td>
|
||||||
<td class="p-2 text-center text-gray-300">{{ r.date_patch.strftime('%Y-%m-%d %H:%M') if r.date_patch else '-' }}</td>
|
<td class="p-2 text-center text-gray-300">{{ r.date_patch.strftime('%Y-%m-%d %H:%M') if r.date_patch else '-' }}</td>
|
||||||
<td class="p-2 text-center text-gray-400">{% if r.date_patch %}S{{ r.date_patch.strftime('%V') }}{% else %}-{% endif %}</td>
|
<td class="p-2 text-center text-gray-400">{% if r.date_patch %}S{{ r.date_patch.strftime('%V') }}{% else %}-{% endif %}</td>
|
||||||
|
<td class="p-2 text-center text-gray-300">{{ r.intervenant_name or '-' }}</td>
|
||||||
<td class="p-2 text-center">
|
<td class="p-2 text-center">
|
||||||
{% if r.source_type == 'import' %}<span class="badge" style="background:#1e3a5f;color:#60a5fa;">xlsx</span>
|
{% if r.source_type == 'import' %}<span class="badge" style="background:#1e3a5f;color:#60a5fa;">xlsx</span>
|
||||||
{% elif r.source_type == 'standard' %}<a href="/campaigns/{{ r.campaign_id }}" class="badge" style="background:#164e63;color:#22d3ee;text-decoration:none">{{ r.campaign_label or 'Campagne' }}</a>
|
{% elif r.source_type == 'standard' %}<a href="/campaigns/{{ r.campaign_id }}" class="badge" style="background:#164e63;color:#22d3ee;text-decoration:none">{{ r.campaign_label or 'Campagne' }}</a>
|
||||||
@ -118,11 +138,11 @@
|
|||||||
{% else %}<span class="badge badge-gray">{{ r.source_type or '?' }}</span>{% endif %}
|
{% else %}<span class="badge badge-gray">{{ r.source_type or '?' }}</span>{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="p-2 text-center"><span class="badge {% if r.status == 'ok' or r.status == 'patched' %}badge-green{% elif r.status == 'ko' or r.status == 'failed' %}badge-red{% else %}badge-yellow{% endif %}">{{ r.status }}</span></td>
|
<td class="p-2 text-center"><span class="badge {% if r.status == 'ok' or r.status == 'patched' %}badge-green{% elif r.status == 'ko' or r.status == 'failed' %}badge-red{% else %}badge-yellow{% endif %}">{{ r.status }}</span></td>
|
||||||
<td class="p-2 text-gray-400" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ r.notes or '' }}">{{ (r.notes or '-')[:50] }}</td>
|
<td class="p-2 text-gray-400" style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ r.notes or '' }}">{{ (r.notes or '-')[:40] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if not rows %}
|
{% if not rows %}
|
||||||
<tr><td colspan="9" class="p-6 text-center text-gray-500">Aucun event de patching pour ce filtre</td></tr>
|
<tr><td colspan="11" class="p-6 text-center text-gray-500">Aucun event de patching pour ce filtre</td></tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -131,9 +151,10 @@
|
|||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if total_filtered > per_page %}
|
{% if total_filtered > per_page %}
|
||||||
<div class="flex justify-center gap-2 mt-4">
|
<div class="flex justify-center gap-2 mt-4">
|
||||||
{% if page > 1 %}<a href="?year={{ year }}{% if week %}&week={{ week }}{% endif %}{% if source %}&source={{ source }}{% endif %}{% if hostname %}&hostname={{ hostname }}{% endif %}&page={{ page - 1 }}" class="btn-sm bg-cyber-border text-gray-300 px-3 py-1 text-xs">← Précédent</a>{% endif %}
|
{% set qs = 'year=' ~ year ~ ('&week=' ~ week if week else '') ~ ('&source=' ~ source if source else '') ~ ('&os_family=' ~ os_family if os_family else '') ~ ('&zone=' ~ zone if zone else '') ~ ('&domain=' ~ domain if domain else '') ~ ('&intervenant=' ~ intervenant if intervenant else '') ~ ('&hostname=' ~ hostname if hostname else '') %}
|
||||||
|
{% if page > 1 %}<a href="?{{ qs }}&page={{ page - 1 }}" class="btn-sm bg-cyber-border text-gray-300 px-3 py-1 text-xs">← Précédent</a>{% endif %}
|
||||||
<span class="text-xs text-gray-500 py-1">Page {{ page }} / {{ ((total_filtered - 1) // per_page) + 1 }}</span>
|
<span class="text-xs text-gray-500 py-1">Page {{ page }} / {{ ((total_filtered - 1) // per_page) + 1 }}</span>
|
||||||
{% if page * per_page < total_filtered %}<a href="?year={{ year }}{% if week %}&week={{ week }}{% endif %}{% if source %}&source={{ source }}{% endif %}{% if hostname %}&hostname={{ hostname }}{% endif %}&page={{ page + 1 }}" class="btn-sm bg-cyber-border text-gray-300 px-3 py-1 text-xs">Suivant →</a>{% endif %}
|
{% if page * per_page < total_filtered %}<a href="?{{ qs }}&page={{ page + 1 }}" class="btn-sm bg-cyber-border text-gray-300 px-3 py-1 text-xs">Suivant →</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
10
deploy/migrations/2026-04-17_patch_history_intervenant.sql
Normal file
10
deploy/migrations/2026-04-17_patch_history_intervenant.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- Migration 2026-04-17 : ajout colonne intervenant_name a patch_history
|
||||||
|
-- pour stocker le nom d'intervenant libre provenant du xlsx (ex "Khalid", "Thierno")
|
||||||
|
-- sans FK users (car ne correspond pas forcement a un user patchcenter)
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE patch_history ADD COLUMN IF NOT EXISTS intervenant_name varchar(100);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ph_intervenant_name ON patch_history (intervenant_name);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@ -119,7 +119,7 @@ def collect_events(wb, hosts):
|
|||||||
stats = {"histo_2025_s1": 0, "histo_2025_s2": 0,
|
stats = {"histo_2025_s1": 0, "histo_2025_s2": 0,
|
||||||
"weekly": 0, "no_server": 0, "weekly_no_color": 0}
|
"weekly": 0, "no_server": 0, "weekly_no_color": 0}
|
||||||
|
|
||||||
# --- Histo-2025 : col L (12) date S1, col M (13) flag S1, col O (15) date S2, col P (16) flag S2
|
# --- Histo-2025 : col B (2) Intervenant, col L (12) date S1, col M (13) flag S1, col O (15) date S2, col P (16) flag S2
|
||||||
if "Histo-2025" in wb.sheetnames:
|
if "Histo-2025" in wb.sheetnames:
|
||||||
ws = wb["Histo-2025"]
|
ws = wb["Histo-2025"]
|
||||||
for row_idx in range(2, ws.max_row + 1):
|
for row_idx in range(2, ws.max_row + 1):
|
||||||
@ -131,12 +131,16 @@ def collect_events(wb, hosts):
|
|||||||
stats["no_server"] += 1
|
stats["no_server"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
interv = ws.cell(row=row_idx, column=2).value
|
||||||
|
interv = str(interv).strip() if interv else None
|
||||||
|
|
||||||
date_s1 = parse_date_cell(ws.cell(row=row_idx, column=12).value)
|
date_s1 = parse_date_cell(ws.cell(row=row_idx, column=12).value)
|
||||||
flag_s1 = ws.cell(row=row_idx, column=13).value
|
flag_s1 = ws.cell(row=row_idx, column=13).value
|
||||||
if flag_s1 and isinstance(flag_s1, int) and flag_s1 >= 1:
|
if flag_s1 and isinstance(flag_s1, int) and flag_s1 >= 1:
|
||||||
dt = date_s1 or datetime(2025, 6, 30, 0, 0)
|
dt = date_s1 or datetime(2025, 6, 30, 0, 0)
|
||||||
events.append({"sid": sid, "dt": dt, "status": "ok",
|
events.append({"sid": sid, "dt": dt, "status": "ok",
|
||||||
"notes": f"Histo-2025 S1 (x{flag_s1})"})
|
"notes": f"Histo-2025 S1 (x{flag_s1})",
|
||||||
|
"interv": interv})
|
||||||
stats["histo_2025_s1"] += 1
|
stats["histo_2025_s1"] += 1
|
||||||
|
|
||||||
date_s2 = parse_date_cell(ws.cell(row=row_idx, column=15).value)
|
date_s2 = parse_date_cell(ws.cell(row=row_idx, column=15).value)
|
||||||
@ -144,7 +148,8 @@ def collect_events(wb, hosts):
|
|||||||
if flag_s2 and isinstance(flag_s2, int) and flag_s2 >= 1:
|
if flag_s2 and isinstance(flag_s2, int) and flag_s2 >= 1:
|
||||||
dt = date_s2 or datetime(2025, 12, 31, 0, 0)
|
dt = date_s2 or datetime(2025, 12, 31, 0, 0)
|
||||||
events.append({"sid": sid, "dt": dt, "status": "ok",
|
events.append({"sid": sid, "dt": dt, "status": "ok",
|
||||||
"notes": f"Histo-2025 S2 (x{flag_s2})"})
|
"notes": f"Histo-2025 S2 (x{flag_s2})",
|
||||||
|
"interv": interv})
|
||||||
stats["histo_2025_s2"] += 1
|
stats["histo_2025_s2"] += 1
|
||||||
|
|
||||||
# --- Weekly sheets S02..S52 : nom colore VERT = patche (2026)
|
# --- Weekly sheets S02..S52 : nom colore VERT = patche (2026)
|
||||||
@ -170,6 +175,9 @@ def collect_events(wb, hosts):
|
|||||||
stats["no_server"] += 1
|
stats["no_server"] += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
interv = ws.cell(row=row_idx, column=2).value
|
||||||
|
interv = str(interv).strip() if interv else None
|
||||||
|
|
||||||
# col N (14) = Date, col O (15) = Heure
|
# col N (14) = Date, col O (15) = Heure
|
||||||
date_val = ws.cell(row=row_idx, column=14).value
|
date_val = ws.cell(row=row_idx, column=14).value
|
||||||
hour_val = ws.cell(row=row_idx, column=15).value
|
hour_val = ws.cell(row=row_idx, column=15).value
|
||||||
@ -180,7 +188,8 @@ def collect_events(wb, hosts):
|
|||||||
# sinon : heure = 00:00 par defaut (deja dans dt_base)
|
# sinon : heure = 00:00 par defaut (deja dans dt_base)
|
||||||
|
|
||||||
events.append({"sid": sid, "dt": dt_base, "status": "ok",
|
events.append({"sid": sid, "dt": dt_base, "status": "ok",
|
||||||
"notes": f"Semaine {wk:02d} 2026"})
|
"notes": f"Semaine {wk:02d} 2026",
|
||||||
|
"interv": interv})
|
||||||
stats["weekly"] += 1
|
stats["weekly"] += 1
|
||||||
|
|
||||||
return events, stats
|
return events, stats
|
||||||
@ -235,8 +244,8 @@ def main():
|
|||||||
skipped += 1
|
skipped += 1
|
||||||
continue
|
continue
|
||||||
conn.execute(text("""
|
conn.execute(text("""
|
||||||
INSERT INTO patch_history (server_id, date_patch, status, notes)
|
INSERT INTO patch_history (server_id, date_patch, status, notes, intervenant_name)
|
||||||
VALUES (:sid, :dt, :status, :notes)
|
VALUES (:sid, :dt, :status, :notes, :interv)
|
||||||
"""), ev)
|
"""), ev)
|
||||||
inserted += 1
|
inserted += 1
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user