- Split quickwin services: prereq, snapshot, log services - Add referentiel router and template - QuickWin detail: prereq/snapshot terminal divs for production - Server edit partial updates - QuickWin correspondance and logs templates - Base template updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
409 lines
20 KiB
HTML
409 lines
20 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Référentiel{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div>
|
|
<h1 class="text-xl font-bold" style="color:#00d4ff">Référentiel</h1>
|
|
<p class="text-xs text-gray-500">Gestion centralisée des domaines, environnements, zones et associations</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% set msg = request.query_params.get('msg', '') %}
|
|
{% set detail = request.query_params.get('detail', '') %}
|
|
{% if msg == 'added' %}
|
|
<div style="background:#1a5a2e;color:#8f8;padding:8px 16px;border-radius:6px;margin-bottom:12px;font-size:0.85rem">
|
|
Elément ajouté avec succès.
|
|
</div>
|
|
{% elif msg == 'updated' %}
|
|
<div style="background:#1a5a2e;color:#8f8;padding:8px 16px;border-radius:6px;margin-bottom:12px;font-size:0.85rem">
|
|
Elément mis à jour.
|
|
</div>
|
|
{% elif msg == 'deleted' %}
|
|
<div style="background:#5a3a1a;color:#ffcc00;padding:8px 16px;border-radius:6px;margin-bottom:12px;font-size:0.85rem">
|
|
Elément supprimé.
|
|
</div>
|
|
{% elif msg == 'nodelete' %}
|
|
<div style="background:#5a1a1a;color:#ff3366;padding:8px 16px;border-radius:6px;margin-bottom:12px;font-size:0.85rem">
|
|
Suppression impossible : {{ detail }} serveur(s) lié(s). Dissociez-les d'abord.
|
|
</div>
|
|
{% elif msg == 'exists' %}
|
|
<div style="background:#5a3a1a;color:#ffcc00;padding:8px 16px;border-radius:6px;margin-bottom:12px;font-size:0.85rem">
|
|
Cette association domaine × environnement existe déjà.
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Onglets -->
|
|
<div style="display:flex;gap:4px;margin-bottom:16px">
|
|
{% for t, label in [('domains','Domaines'), ('envs','Environnements'), ('assocs','Associations'), ('zones','Zones'), ('dns','Domaines DNS')] %}
|
|
<a href="/referentiel?tab={{ t }}"
|
|
style="padding:8px 20px;border-radius:8px 8px 0 0;font-size:0.85rem;font-weight:600;
|
|
{% if tab == t %}background:#1e3a5f;color:#00d4ff;border-bottom:2px solid #00d4ff{% else %}background:#111827;color:#94a3b8{% endif %}">
|
|
{{ label }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- ONGLET DOMAINES -->
|
|
<!-- ============================================================ -->
|
|
{% if tab == 'domains' %}
|
|
<div class="card">
|
|
<table class="table-cyber w-full">
|
|
<thead><tr>
|
|
<th class="px-2 py-2" style="width:40px">ID</th>
|
|
<th class="px-2 py-2">Nom</th>
|
|
<th class="px-2 py-2" style="width:70px">Code</th>
|
|
<th class="px-2 py-2">Description</th>
|
|
<th class="px-2 py-2" style="width:70px">Ordre</th>
|
|
<th class="px-2 py-2" style="width:70px">Actif</th>
|
|
<th class="px-2 py-2" style="width:80px">Serveurs</th>
|
|
<th class="px-2 py-2" style="width:140px">Actions</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for d in domains %}
|
|
<tr id="dom-row-{{ d.id }}">
|
|
<form method="post" action="/referentiel/domains/{{ d.id }}/edit">
|
|
<td class="px-2 py-2 text-xs text-gray-500">{{ d.id }}</td>
|
|
<td class="px-2 py-2"><input type="text" name="name" value="{{ d.name }}" style="width:100%" required></td>
|
|
<td class="px-2 py-2"><input type="text" name="code" value="{{ d.code }}" style="width:100%;text-transform:uppercase" required></td>
|
|
<td class="px-2 py-2"><input type="text" name="description" value="{{ d.description or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2"><input type="number" name="display_order" value="{{ d.display_order }}" style="width:100%"></td>
|
|
<td class="px-2 py-2 text-center">
|
|
<input type="checkbox" name="is_active" {% if d.is_active %}checked{% endif %}>
|
|
</td>
|
|
<td class="px-2 py-2 text-center">
|
|
<span class="badge badge-blue">{{ dom_srv_counts.get(d.id, 0) }}</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-center" style="white-space:nowrap">
|
|
<input type="hidden" name="default_excludes" value="{{ d.default_excludes or '' }}">
|
|
<input type="hidden" name="default_patch_window" value="{{ d.default_patch_window or '' }}">
|
|
<button type="submit" class="btn-sm" style="background:#00d4ff22;color:#00d4ff;padding:3px 10px">Sauver</button>
|
|
</form>
|
|
{% if dom_srv_counts.get(d.id, 0) == 0 %}
|
|
<form method="post" action="/referentiel/domains/{{ d.id }}/delete" style="display:inline"
|
|
onsubmit="return confirm('Supprimer le domaine {{ d.name }} ?')">
|
|
<button type="submit" class="btn-sm" style="background:#ff336622;color:#ff3366;padding:3px 10px">Suppr</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if can_modify %}
|
|
<div class="card mt-3" style="padding:12px 16px">
|
|
<h3 class="text-sm font-bold mb-2" style="color:#00ff88">Ajouter un domaine</h3>
|
|
<form method="post" action="/referentiel/domains/add" style="display:flex;gap:10px;align-items:end;flex-wrap:wrap">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom *</label>
|
|
<input type="text" name="name" required style="width:160px" placeholder="Infrastructure">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Code *</label>
|
|
<input type="text" name="code" required style="width:80px;text-transform:uppercase" placeholder="INF">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Description</label>
|
|
<input type="text" name="description" style="width:200px">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Excludes par défaut</label>
|
|
<input type="text" name="default_excludes" style="width:200px">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Fenêtre patch</label>
|
|
<input type="text" name="default_patch_window" style="width:120px" placeholder="mardi 22h">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Ordre</label>
|
|
<input type="number" name="display_order" value="0" style="width:60px">
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="padding:6px 18px;font-size:0.85rem">Ajouter</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- ONGLET ENVIRONNEMENTS -->
|
|
<!-- ============================================================ -->
|
|
{% elif tab == 'envs' %}
|
|
<div class="card">
|
|
<table class="table-cyber w-full">
|
|
<thead><tr>
|
|
<th class="px-2 py-2" style="width:40px">ID</th>
|
|
<th class="px-2 py-2">Nom</th>
|
|
<th class="px-2 py-2" style="width:80px">Code</th>
|
|
<th class="px-2 py-2" style="width:80px">Serveurs</th>
|
|
<th class="px-2 py-2" style="width:140px">Actions</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for e in envs %}
|
|
<tr>
|
|
<form method="post" action="/referentiel/envs/{{ e.id }}/edit">
|
|
<td class="px-2 py-2 text-xs text-gray-500">{{ e.id }}</td>
|
|
<td class="px-2 py-2"><input type="text" name="name" value="{{ e.name }}" style="width:100%" required></td>
|
|
<td class="px-2 py-2"><input type="text" name="code" value="{{ e.code }}" style="width:100%;text-transform:uppercase" required></td>
|
|
<td class="px-2 py-2 text-center">
|
|
<span class="badge badge-blue">{{ env_srv_counts.get(e.id, 0) }}</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-center" style="white-space:nowrap">
|
|
<button type="submit" class="btn-sm" style="background:#00d4ff22;color:#00d4ff;padding:3px 10px">Sauver</button>
|
|
</form>
|
|
{% if env_srv_counts.get(e.id, 0) == 0 %}
|
|
<form method="post" action="/referentiel/envs/{{ e.id }}/delete" style="display:inline"
|
|
onsubmit="return confirm('Supprimer l environnement {{ e.name }} ?')">
|
|
<button type="submit" class="btn-sm" style="background:#ff336622;color:#ff3366;padding:3px 10px">Suppr</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if can_modify %}
|
|
<div class="card mt-3" style="padding:12px 16px">
|
|
<h3 class="text-sm font-bold mb-2" style="color:#00ff88">Ajouter un environnement</h3>
|
|
<form method="post" action="/referentiel/envs/add" style="display:flex;gap:10px;align-items:end">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom *</label>
|
|
<input type="text" name="name" required style="width:200px" placeholder="Production">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Code *</label>
|
|
<input type="text" name="code" required style="width:80px;text-transform:uppercase" placeholder="PRD">
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="padding:6px 18px;font-size:0.85rem">Ajouter</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- ONGLET ASSOCIATIONS -->
|
|
<!-- ============================================================ -->
|
|
{% elif tab == 'assocs' %}
|
|
<div class="card">
|
|
<div class="table-wrap" style="max-height:60vh;overflow-y:auto">
|
|
<table class="table-cyber w-full">
|
|
<thead style="position:sticky;top:0;z-index:1"><tr>
|
|
<th class="px-2 py-2" style="width:40px">ID</th>
|
|
<th class="px-2 py-2">Domaine</th>
|
|
<th class="px-2 py-2">Environnement</th>
|
|
<th class="px-2 py-2">Responsable</th>
|
|
<th class="px-2 py-2">Email resp.</th>
|
|
<th class="px-2 py-2">Référent</th>
|
|
<th class="px-2 py-2">Email réf.</th>
|
|
<th class="px-2 py-2" style="width:60px">Actif</th>
|
|
<th class="px-2 py-2" style="width:60px">Srv</th>
|
|
<th class="px-2 py-2" style="width:140px">Actions</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for a in assocs %}
|
|
<tr>
|
|
<form method="post" action="/referentiel/assocs/{{ a.id }}/edit">
|
|
<td class="px-2 py-2 text-xs text-gray-500">{{ a.id }}</td>
|
|
<td class="px-2 py-2" style="color:#00d4ff;font-weight:600">{{ a.domain_name }}</td>
|
|
<td class="px-2 py-2" style="color:#a78bfa">{{ a.env_name }}</td>
|
|
<td class="px-2 py-2"><input type="text" name="responsable_nom" value="{{ a.responsable_nom or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2"><input type="text" name="responsable_email" value="{{ a.responsable_email or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2"><input type="text" name="referent_nom" value="{{ a.referent_nom or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2"><input type="text" name="referent_email" value="{{ a.referent_email or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2 text-center">
|
|
<input type="checkbox" name="is_active" {% if a.is_active %}checked{% endif %}>
|
|
</td>
|
|
<td class="px-2 py-2 text-center">
|
|
<span class="badge badge-blue">{{ a.nb_servers or 0 }}</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-center" style="white-space:nowrap">
|
|
<input type="hidden" name="patch_window" value="{{ a.patch_window or '' }}">
|
|
<input type="hidden" name="patch_excludes" value="{{ a.patch_excludes or '' }}">
|
|
<button type="submit" class="btn-sm" style="background:#00d4ff22;color:#00d4ff;padding:3px 10px">Sauver</button>
|
|
</form>
|
|
<form method="post" action="/referentiel/assocs/{{ a.id }}/delete" style="display:inline"
|
|
onsubmit="return confirm('Supprimer {{ a.domain_name }} x {{ a.env_name }} ?')">
|
|
<button type="submit" class="btn-sm" style="background:#ff336622;color:#ff3366;padding:3px 10px">Suppr</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% if not assocs %}
|
|
<tr><td colspan="10" class="px-2 py-8 text-center text-gray-500">Aucune association</td></tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{% if can_modify %}
|
|
<div class="card mt-3" style="padding:12px 16px">
|
|
<h3 class="text-sm font-bold mb-2" style="color:#00ff88">Ajouter une association</h3>
|
|
<form method="post" action="/referentiel/assocs/add" style="display:flex;gap:10px;align-items:end;flex-wrap:wrap">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Domaine *</label>
|
|
<select name="domain_id" required style="width:180px">
|
|
<option value="">-- Choisir --</option>
|
|
{% for d in domains %}<option value="{{ d.id }}">{{ d.name }} ({{ d.code }})</option>{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Environnement *</label>
|
|
<select name="environment_id" required style="width:180px">
|
|
<option value="">-- Choisir --</option>
|
|
{% for e in envs %}<option value="{{ e.id }}">{{ e.name }} ({{ e.code }})</option>{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Responsable</label>
|
|
<input type="text" name="responsable_nom" style="width:140px">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Email resp.</label>
|
|
<input type="email" name="responsable_email" style="width:180px">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Référent</label>
|
|
<input type="text" name="referent_nom" style="width:140px">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Email réf.</label>
|
|
<input type="email" name="referent_email" style="width:180px">
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="padding:6px 18px;font-size:0.85rem">Ajouter</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- ONGLET ZONES -->
|
|
<!-- ============================================================ -->
|
|
{% elif tab == 'zones' %}
|
|
<div class="card">
|
|
<table class="table-cyber w-full">
|
|
<thead><tr>
|
|
<th class="px-2 py-2" style="width:40px">ID</th>
|
|
<th class="px-2 py-2">Nom</th>
|
|
<th class="px-2 py-2">Description</th>
|
|
<th class="px-2 py-2" style="width:70px">DMZ</th>
|
|
<th class="px-2 py-2" style="width:80px">Serveurs</th>
|
|
<th class="px-2 py-2" style="width:140px">Actions</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for z in zones %}
|
|
<tr>
|
|
<form method="post" action="/referentiel/zones/{{ z.id }}/edit">
|
|
<td class="px-2 py-2 text-xs text-gray-500">{{ z.id }}</td>
|
|
<td class="px-2 py-2"><input type="text" name="name" value="{{ z.name }}" style="width:100%" required></td>
|
|
<td class="px-2 py-2"><input type="text" name="description" value="{{ z.description or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2 text-center">
|
|
<input type="checkbox" name="is_dmz" {% if z.is_dmz %}checked{% endif %}>
|
|
</td>
|
|
<td class="px-2 py-2 text-center">
|
|
<span class="badge badge-blue">{{ zone_srv_counts.get(z.id, 0) }}</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-center" style="white-space:nowrap">
|
|
<button type="submit" class="btn-sm" style="background:#00d4ff22;color:#00d4ff;padding:3px 10px">Sauver</button>
|
|
</form>
|
|
{% if zone_srv_counts.get(z.id, 0) == 0 %}
|
|
<form method="post" action="/referentiel/zones/{{ z.id }}/delete" style="display:inline"
|
|
onsubmit="return confirm('Supprimer la zone {{ z.name }} ?')">
|
|
<button type="submit" class="btn-sm" style="background:#ff336622;color:#ff3366;padding:3px 10px">Suppr</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if can_modify %}
|
|
<div class="card mt-3" style="padding:12px 16px">
|
|
<h3 class="text-sm font-bold mb-2" style="color:#00ff88">Ajouter une zone</h3>
|
|
<form method="post" action="/referentiel/zones/add" style="display:flex;gap:10px;align-items:end">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Nom *</label>
|
|
<input type="text" name="name" required style="width:160px" placeholder="LAN">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Description</label>
|
|
<input type="text" name="description" style="width:250px">
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:4px;padding-bottom:2px">
|
|
<input type="checkbox" name="is_dmz" id="new-dmz">
|
|
<label for="new-dmz" class="text-xs text-gray-400">DMZ</label>
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="padding:6px 18px;font-size:0.85rem">Ajouter</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- ONGLET DOMAINES DNS -->
|
|
<!-- ============================================================ -->
|
|
{% elif tab == 'dns' %}
|
|
<div class="card">
|
|
<div class="p-3" style="border-bottom:1px solid #1e3a5f">
|
|
<p class="text-xs text-gray-500">Suffixes DNS utilisés dans le champ <code>domain_ltd</code> des serveurs (ex: sanef.groupe, sanef-rec.fr)</p>
|
|
</div>
|
|
<table class="table-cyber w-full">
|
|
<thead><tr>
|
|
<th class="px-2 py-2" style="width:40px">ID</th>
|
|
<th class="px-2 py-2">Nom (suffixe DNS)</th>
|
|
<th class="px-2 py-2">Description</th>
|
|
<th class="px-2 py-2" style="width:70px">Actif</th>
|
|
<th class="px-2 py-2" style="width:80px">Serveurs</th>
|
|
<th class="px-2 py-2" style="width:140px">Actions</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
{% for d in dns_domains %}
|
|
<tr>
|
|
<form method="post" action="/referentiel/dns/{{ d.id }}/edit">
|
|
<td class="px-2 py-2 text-xs text-gray-500">{{ d.id }}</td>
|
|
<td class="px-2 py-2"><input type="text" name="name" value="{{ d.name }}" style="width:100%" required></td>
|
|
<td class="px-2 py-2"><input type="text" name="description" value="{{ d.description or '' }}" style="width:100%"></td>
|
|
<td class="px-2 py-2 text-center">
|
|
<input type="checkbox" name="is_active" {% if d.is_active %}checked{% endif %}>
|
|
</td>
|
|
<td class="px-2 py-2 text-center">
|
|
<span class="badge badge-blue">{{ dns_srv_counts.get(d.id, 0) }}</span>
|
|
</td>
|
|
<td class="px-2 py-2 text-center" style="white-space:nowrap">
|
|
<button type="submit" class="btn-sm" style="background:#00d4ff22;color:#00d4ff;padding:3px 10px">Sauver</button>
|
|
</form>
|
|
{% if dns_srv_counts.get(d.id, 0) == 0 %}
|
|
<form method="post" action="/referentiel/dns/{{ d.id }}/delete" style="display:inline"
|
|
onsubmit="return confirm('Supprimer {{ d.name }} ?')">
|
|
<button type="submit" class="btn-sm" style="background:#ff336622;color:#ff3366;padding:3px 10px">Suppr</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if can_modify %}
|
|
<div class="card mt-3" style="padding:12px 16px">
|
|
<h3 class="text-sm font-bold mb-2" style="color:#00ff88">Ajouter un domaine DNS</h3>
|
|
<form method="post" action="/referentiel/dns/add" style="display:flex;gap:10px;align-items:end">
|
|
<div>
|
|
<label class="text-xs text-gray-500">Suffixe DNS *</label>
|
|
<input type="text" name="name" required style="width:200px" placeholder="sanef.groupe">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs text-gray-500">Description</label>
|
|
<input type="text" name="description" style="width:250px" placeholder="Domaine production SANEF">
|
|
</div>
|
|
<button type="submit" class="btn-primary" style="padding:6px 18px;font-size:0.85rem">Ajouter</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
{% endblock %}
|