patchcenter/app/templates/planning.html
Khalid MOUTAOUAKIL 833c4fc3d2 Quick Win delete, UI planning/specifics reorganises, accents retires
- Safe Patching: bouton supprimer campagne (admin only)
- Safe Patching: boutons nouvelle campagne et planning en haut
- Safe Patching: message suppression dans les notifications
- Planning: formulaire ajouter deplace apres le Gantt (compact)
- Planning: accents retires des messages flash
- Specifics: formulaire ajouter deplace en haut avant le tableau
- Specifics: colonne Wave retiree, colonnes Stop/Start renommees

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:44:22 +02:00

230 lines
13 KiB
HTML

{% extends 'base.html' %}
{% block title %}Planning Patching {{ year }}{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-cyber-accent">Planning Patching {{ year }}</h2>
<div class="flex gap-2 items-center">
<a href="?year={{ year - 1 }}" class="btn-sm bg-cyber-border text-gray-300">{{ year - 1 }}</a>
<a href="?year={{ year + 1 }}" class="btn-sm bg-cyber-border text-gray-300">{{ year + 1 }}</a>
<!-- Dupliquer (admin/coordinateur) -->
{% if entries and perms.planning in ('edit', 'admin') %}
<form method="POST" action="/planning/duplicate" class="flex gap-1 items-center ml-4">
<input type="hidden" name="source_year" value="{{ year }}">
<input type="number" name="target_year" value="{{ year + 1 }}" class="text-xs py-1 px-2 w-20">
<button type="submit" class="btn-sm bg-cyber-accent text-black" onclick="return confirm('Dupliquer {{ year }} vers cette annee ?')">Dupliquer vers</button>
</form>
{% endif %}
</div>
</div>
{% if msg %}
<div class="mb-4 p-2 rounded text-sm {% if msg in ('exists', 'err_week', 'err_domain', 'err_past', 'err_past_wed') %}bg-red-900/30 text-cyber-red{% else %}bg-green-900/30 text-cyber-green{% endif %}">
{% if msg == 'add' %}Entree ajoutee.{% elif msg == 'edit' %}Entree modifiee.{% elif msg == 'delete' %}Entree supprimee.{% elif msg == 'duplicate' %}Planning duplique avec succes.{% elif msg == 'exists' %}L'annee cible contient deja des entrees. Supprimez-les d'abord.{% elif msg == 'err_week' %}Numero de semaine invalide (1-53).{% elif msg == 'err_domain' %}Domaine requis pour une entree ouverte.{% elif msg == 'err_past' %}Impossible d'ajouter dans le passe (semaine deja ecoulee).{% elif msg == 'err_past_wed' %}Semaine en cours : ajout possible uniquement lundi et mardi (MEP urgente).{% endif %}
</div>
{% endif %}
<!-- Legende -->
<div class="flex gap-4 mb-4 text-xs items-center flex-wrap">
<div class="flex items-center gap-1"><span class="inline-block w-3 h-3 rounded" style="background:#1e3a8a"></span> Cycle 1</div>
<div class="flex items-center gap-1"><span class="inline-block w-3 h-3 rounded" style="background:#7c3aed"></span> Cycle 2</div>
<div class="flex items-center gap-1"><span class="inline-block w-3 h-3 rounded" style="background:#166534"></span> Cycle 3</div>
<div class="flex items-center gap-1"><span class="inline-block w-3 h-3 rounded" style="background:#f59e0b33"></span> Gel</div>
<div class="flex items-center gap-1"><span class="inline-block w-3 h-3 rounded" style="background:#D4A0A0"></span> DMZ (continu)</div>
<span class="text-gray-500 ml-2">HPROD = hors-prod | PROD = production | pilot = prod pilote</span>
</div>
<!-- Gantt -->
<div class="card overflow-x-auto">
<table class="table-cyber" style="min-width:1600px">
<!-- Mois header -->
<thead>
<tr>
<th class="p-2 text-left sticky left-0 bg-cyber-card z-10" style="min-width:140px">Domaine</th>
{% for m in months %}
<th colspan="{% if loop.index in [1,3,5,7,8,10,12] %}5{% elif loop.index == 2 %}4{% else %}4{% endif %}" class="p-1 text-center text-xs">{{ m }}</th>
{% endfor %}
</tr>
<!-- Semaines header -->
<tr>
<th class="p-1 sticky left-0 bg-cyber-card z-10"></th>
{% for w in weeks %}
<th class="p-0 text-center" style="width:22px;min-width:22px">
<span class="text-[9px] {% if w in freeze_weeks %}text-cyber-yellow{% else %}text-gray-600{% endif %}">{{ w }}</span>
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for dom in domains %}
<tr>
<td class="p-2 sticky left-0 bg-cyber-card z-10 border-r border-cyber-border">
<div class="flex items-center gap-2">
<span class="inline-block w-2 h-6 rounded-sm" style="background:{{ domain_colors.get(dom.code, '#666') }}"></span>
<div>
<span class="font-bold text-sm" style="color:{{ domain_colors.get(dom.code, '#999') }}">{{ dom.name }}</span>
<span class="text-[10px] text-gray-500 ml-1">({{ dom.srv_count }})</span>
</div>
</div>
</td>
{% for w in weeks %}
{% set entry = grid.get(dom.code, {}).get(w) %}
<td class="p-0 text-center" style="width:22px;min-width:22px">
{% if w in freeze_weeks %}
<div class="h-6" style="background:#f59e0b15" title="Gel S{{ w }}"></div>
{% elif entry %}
{% set bg = '#1e3a8a' %}
{% if entry.cycle == 2 %}{% set bg = '#7c3aed' %}{% endif %}
{% if entry.cycle == 3 %}{% set bg = '#166534' %}{% endif %}
{% if dom.code == 'DMZ' %}{% set bg = '#5f3737' %}{% endif %}
<div class="h-6 rounded-sm flex items-center justify-center cursor-pointer hover:opacity-80"
style="background:{{ bg }}"
title="S{{ w }} — {{ dom.name }} {{ entry.env_scope }}{% if entry.note %} — {{ entry.note }}{% endif %}">
<span class="text-[8px] text-white/80">
{% if entry.env_scope == 'prod' %}P{% elif entry.env_scope == 'hprod' %}H{% elif entry.env_scope == 'prod_pilot' %}PP{% elif entry.env_scope == 'all' %}A{% endif %}
</span>
</div>
{% else %}
<div class="h-6"></div>
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if perms.planning in ('edit', 'admin') %}
<div class="card p-3 mt-4 mb-4">
<form method="POST" action="/planning/add" class="flex gap-2 items-end flex-wrap">
<input type="hidden" name="year" value="{{ year }}">
<span class="text-xs text-cyber-accent font-bold">Ajouter :</span>
<input type="number" name="week_number" min="1" max="53" value="{{ default_week }}" class="text-xs py-1 px-2 w-14" required placeholder="Sem">
<select name="domain_code" class="text-xs py-1 px-2">
<option value="">- (gel)</option>
{% for d in all_domains %}<option value="{{ d.code }}">{{ d.name }}</option>{% endfor %}
</select>
<select name="env_scope" class="text-xs py-1 px-2">
{% for es in env_scopes %}<option value="{{ es }}">{{ es }}</option>{% endfor %}
</select>
<input type="number" name="cycle" min="1" max="4" class="text-xs py-1 px-2 w-14" placeholder="Cycle">
<select name="status" class="text-xs py-1 px-2">
{% for st in statuses %}<option value="{{ st }}">{{ st }}</option>{% endfor %}
</select>
<input type="text" name="note" class="text-xs py-1 px-2 flex-1" placeholder="Note">
<button type="submit" class="btn-primary px-3 py-1 text-sm">Ajouter</button>
</form>
</div>
{% endif %}
<!-- Detail par cycle -->
<div class="grid grid-cols-3 gap-4 mt-6">
{% for cycle_num in [1, 2, 3] %}
<div class="card p-4">
<h3 class="text-sm font-bold text-cyber-accent mb-3">Cycle {{ cycle_num }}</h3>
<div class="space-y-1 text-xs">
{% for w in weeks %}
{% for dom in domains %}
{% set entry = grid.get(dom.code, {}).get(w) %}
{% if entry and entry.cycle == cycle_num and dom.code != 'DMZ' %}
<div class="flex justify-between items-center py-0.5">
<div class="flex items-center gap-2">
<span class="text-gray-500 w-7">S{{ '%02d' % w }}</span>
<span class="inline-block w-2 h-2 rounded-full" style="background:{{ domain_colors.get(dom.code, '#666') }}"></span>
<span>{{ dom.name }}</span>
<span class="badge {% if entry.env_scope == 'prod' %}badge-green{% elif entry.env_scope == 'hprod' %}badge-yellow{% else %}badge-blue{% endif %}">{{ entry.env_scope }}</span>
</div>
{% if entry.note %}<span class="text-gray-600 truncate ml-1" style="max-width:100px" title="{{ entry.note }}">{{ entry.note[:20] }}</span>{% endif %}
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Tableau editable -->
<div x-data="{ editing: null }" class="card mt-6 overflow-x-auto">
<div class="flex justify-between items-center p-4 border-b border-cyber-border">
<h3 class="text-sm font-bold text-cyber-accent">Donnees planning {{ year }} ({{ entries|length }} entrees)</h3>
</div>
<table class="w-full table-cyber text-sm">
<thead><tr>
<th class="p-2">Sem.</th>
<th class="p-2">Dates</th>
<th class="p-2">Domaine</th>
<th class="p-2">Env</th>
<th class="p-2">Cycle</th>
<th class="p-2">Statut</th>
<th class="text-left p-2">Note</th>
{% if perms.planning in ('edit', 'admin') %}<th class="p-2">Actions</th>{% endif %}
</tr></thead>
<tbody>
{% for e in entries %}
<tr class="{% if e.status == 'freeze' %}bg-yellow-900/10{% endif %}">
<!-- Mode lecture -->
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center font-mono text-xs">{{ e.week_code }}</td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center text-xs text-gray-500">{{ e.week_start.strftime('%d/%m') }} - {{ e.week_end.strftime('%d/%m') }}</td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center"><span style="color:{{ domain_colors.get(e.domain_code or '', '#666') }}">{{ e.domain_name or '-' }}</span></td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center"><span class="badge {% if e.env_scope == 'prod' %}badge-green{% elif e.env_scope == 'hprod' %}badge-yellow{% else %}badge-blue{% endif %}">{{ e.env_scope }}</span></td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center">{{ e.cycle or '-' }}</td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center"><span class="badge {% if e.status == 'freeze' %}badge-yellow{% elif e.status == 'open' %}badge-green{% else %}badge-gray{% endif %}">{{ e.status }}</span></td>
</template>
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-xs text-gray-400">{{ e.note or '' }}</td>
</template>
{% if perms.planning in ('edit', 'admin') %}
<template x-if="editing !== {{ e.id }}">
<td class="p-2 text-center">
<button @click="editing = {{ e.id }}" class="btn-sm bg-cyber-border text-cyber-accent">Edit</button>
</td>
</template>
<!-- Mode edition -->
<template x-if="editing === {{ e.id }}">
<td colspan="8" class="p-2">
<form method="POST" action="/planning/{{ e.id }}/edit" class="flex gap-2 items-center flex-wrap">
<span class="font-mono text-xs text-gray-500">{{ e.week_code }}</span>
<select name="domain_code" class="text-xs py-1 px-2">
<option value="">-</option>
{% for d in all_domains %}<option value="{{ d.code }}" {% if d.code == e.domain_code %}selected{% endif %}>{{ d.name }}</option>{% endfor %}
</select>
<select name="env_scope" class="text-xs py-1 px-2">
{% for es in env_scopes %}<option value="{{ es }}" {% if es == e.env_scope %}selected{% endif %}>{{ es }}</option>{% endfor %}
</select>
<input type="number" name="cycle" value="{{ e.cycle or '' }}" placeholder="Cycle" class="text-xs py-1 px-2 w-16" min="1" max="4">
<select name="status" class="text-xs py-1 px-2">
{% for st in statuses %}<option value="{{ st }}" {% if st == e.status %}selected{% endif %}>{{ st }}</option>{% endfor %}
</select>
<input type="text" name="note" value="{{ e.note or '' }}" placeholder="Note" class="text-xs py-1 px-2 flex-1">
<button type="submit" class="btn-sm bg-cyber-accent text-black">OK</button>
<button type="button" @click="editing = null" class="btn-sm bg-cyber-border text-gray-400">X</button>
</form>
<form method="POST" action="/planning/{{ e.id }}/delete" class="inline ml-2">
<button type="submit" class="btn-sm bg-red-900/30 text-cyber-red" onclick="return confirm('Supprimer ?')">Suppr</button>
</form>
</td>
</template>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}