GUI : vue Reseau (devis switch) + bouton Copier
Nouvel onglet lecture seule affichant le devis make devis-reseau (VLANs+SVIs+ACLs) via GET /api/devis-reseau, avec bouton Copier. node --check OK, endpoint eprouve (2 tenants, 121 lignes). Un operateur genere et transmet la config sans CLI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
97670dd27c
commit
0580c931a7
2 changed files with 46 additions and 1 deletions
|
|
@ -16,6 +16,10 @@
|
|||
émet la config Cisco-like du réseau convergé — **VLANs + SVIs (passerelles) + ACLs d'isolation
|
||||
inter-tenant** — dérivée des nomenclatures, jamais saisie à la main (toujours synchrone avec
|
||||
le plan). One-shot précédemment ; maintenant reproductible.
|
||||
- **GUI : vue « Réseau » (devis switch) + bouton Copier.** Nouvel onglet lecture seule qui
|
||||
affiche le devis `make devis-reseau` (VLANs + SVIs + ACLs), via `GET /api/devis-reseau`, avec
|
||||
un bouton **Copier** (prêt à coller sur le switch). Un opérateur génère et transmet la config
|
||||
sans CLI.
|
||||
- **GUI : erreurs de déploiement en LANGAGE CLAIR.** Quand une action (créer/vérifier/déployer)
|
||||
échoue, le GUI n'oblige plus à lire le dump Ansible : un encadré résume les tâches en erreur —
|
||||
**étape · hôte · type · message clair** — avec un bouton **« Copier »** (pour transmettre le
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from urllib.parse import urlparse
|
|||
|
||||
import yaml
|
||||
|
||||
import devis_reseau
|
||||
from inventory_rules import (
|
||||
GROUPE_HOTES_ACTIFS,
|
||||
GROUPE_HOTES_PLANIFIES,
|
||||
|
|
@ -916,6 +917,8 @@ HTML = r"""<!doctype html>
|
|||
.re-hote { color: var(--teal); font-weight: 700; }
|
||||
.re-type { color: var(--muted); font-size: 11px; }
|
||||
.re-msg { background: var(--surface-3); border-radius: 6px; padding: 4px 8px; margin-top: 3px; font-family: ui-monospace, monospace; font-size: 11.5px; white-space: pre-wrap; word-break: break-word; }
|
||||
/* Devis réseau (config switch) */
|
||||
.devis-reseau { margin: 8px 0 0; padding: 12px 14px; background: var(--surface-3); border-radius: 8px; font-family: ui-monospace, monospace; font-size: 11.5px; line-height: 1.5; white-space: pre; overflow: auto; max-height: calc(100vh - 210px); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -933,6 +936,7 @@ HTML = r"""<!doctype html>
|
|||
<button type="button" id="btn-vue-domaines" onclick="setVue('domaines')">Domaines</button>
|
||||
<button type="button" id="btn-vue-flux" onclick="setVue('flux')">Flux</button>
|
||||
<button type="button" id="btn-vue-couches" onclick="setVue('couches')">Couches</button>
|
||||
<button type="button" id="btn-vue-reseau" onclick="setVue('reseau')">Réseau</button>
|
||||
</div>
|
||||
<span class="recherche"><input id="filtre" type="search" placeholder="Filtrer…" oninput="filtrer(this.value)"></span>
|
||||
<button type="button" class="fantome" onclick="ouvrirIntrants()">⚙ Intrants</button>
|
||||
|
|
@ -1152,6 +1156,7 @@ HTML = r"""<!doctype html>
|
|||
document.getElementById('btn-vue-domaines').classList.toggle('on', v === 'domaines');
|
||||
document.getElementById('btn-vue-flux').classList.toggle('on', v === 'flux');
|
||||
document.getElementById('btn-vue-couches').classList.toggle('on', v === 'couches');
|
||||
document.getElementById('btn-vue-reseau').classList.toggle('on', v === 'reseau');
|
||||
dessinerGrilles();
|
||||
dessinerDetail(); // le panneau droit reflète la sélection de la vue courante
|
||||
majIndicateurSauvegarde();
|
||||
|
|
@ -1176,6 +1181,7 @@ HTML = r"""<!doctype html>
|
|||
if (vuePrincipale === 'domaines') { dessinerDomaines(); return; }
|
||||
if (vuePrincipale === 'flux') { dessinerFlux(); return; }
|
||||
if (vuePrincipale === 'couches') { dessinerCouches(); return; }
|
||||
if (vuePrincipale === 'reseau') { dessinerReseau(); return; }
|
||||
dessinerServeurs();
|
||||
}
|
||||
|
||||
|
|
@ -1191,6 +1197,10 @@ HTML = r"""<!doctype html>
|
|||
<div><span class="chip chi-tls-cible">tls-cible</span> TLS visé (roadmap)</div>
|
||||
<div><span class="chip chi-clair">clair</span> non chiffré (local / edge)</div></div>
|
||||
<div class="hint-dep" style="margin-top:10px">Lecture seule. Éditer les <code>meta/flux.yml</code> des rôles, puis <code>make flux</code>.</div></div>`;
|
||||
} else if (vuePrincipale === 'reseau') {
|
||||
d.innerHTML = `<div class="detail"><div class="section-tete">Devis réseau</div>
|
||||
<div class="hint-dep">VLANs + SVIs (passerelles) + ACLs d'isolation inter-tenant, dérivés des nomenclatures <code>ip-miroir</code> (VLAN = 1000 + index×10 + zone, unique sur le trunk).</div>
|
||||
<div class="hint-dep" style="margin-top:10px">Lecture seule, régénéré du plan — <code>make devis-reseau</code>. À adapter à ta plateforme (NX-OS/IOS-XE, HSRP, route par défaut vers OPNsense, port de trunk).</div></div>`;
|
||||
} else {
|
||||
d.innerHTML = `<div class="detail"><div class="section-tete">Reconstruction</div>
|
||||
<div class="hint-dep">L'ordre part du socle et remonte : chaque couche suppose la précédente debout. Le tri intra-couche vient du graphe des dépendances.</div>
|
||||
|
|
@ -1215,6 +1225,31 @@ HTML = r"""<!doctype html>
|
|||
<tbody>${lignes}</tbody></table></div>`;
|
||||
}
|
||||
|
||||
let devisReseauTexte = '';
|
||||
async function dessinerReseau() {
|
||||
const cible = document.getElementById('grilles');
|
||||
cible.innerHTML = '<div class="vide" style="padding:14px">Génération du devis…</div>';
|
||||
try {
|
||||
const rep = await fetch('/api/devis-reseau', {headers: {'X-Jeton': JETON}});
|
||||
const data = await rep.json();
|
||||
if (!rep.ok) { cible.innerHTML = '<div class="vide" style="padding:14px">' + echapper(data.erreur || 'Indisponible.') + '</div>'; return; }
|
||||
devisReseauTexte = data.config || '';
|
||||
if (!devisReseauTexte) { cible.innerHTML = '<div class="vide" style="padding:14px">Aucune instance fédérée (schéma ip-miroir) trouvée.</div>'; return; }
|
||||
const tenants = (data.tenants || []).map(t => echapper(t.nom) + ' (idx ' + t.index + ')').join(' · ');
|
||||
cible.innerHTML = `<div class="section-grille">
|
||||
<div class="section-tete">Devis config réseau — switches
|
||||
<span class="hint-dep" style="margin-left:auto">${tenants}</span>
|
||||
<button type="button" class="fantome" style="margin-left:10px" onclick="copierDevis()">Copier</button></div>
|
||||
<pre class="devis-reseau">${echapper(devisReseauTexte)}</pre></div>`;
|
||||
} catch (e) {
|
||||
cible.innerHTML = '<div class="vide" style="padding:14px">Erreur : ' + echapper(e.message) + '</div>';
|
||||
}
|
||||
}
|
||||
function copierDevis() {
|
||||
if (navigator.clipboard) navigator.clipboard.writeText(devisReseauTexte)
|
||||
.then(() => message('Devis réseau copié — prêt à coller sur le switch.', 'ok'), () => {});
|
||||
}
|
||||
|
||||
function dessinerCouches() {
|
||||
const cible = document.getElementById('grilles');
|
||||
if (!couchesRegistre.length) { cible.innerHTML = '<div class="vide" style="padding:14px">Ordre de déploiement indisponible.</div>'; return; }
|
||||
|
|
@ -1795,7 +1830,7 @@ HTML = r"""<!doctype html>
|
|||
|
||||
function dessinerDetail() {
|
||||
// Le panneau de droite reflète la sélection de la VUE COURANTE.
|
||||
if (vuePrincipale === 'flux' || vuePrincipale === 'couches') return legendeLecture();
|
||||
if (vuePrincipale === 'flux' || vuePrincipale === 'couches' || vuePrincipale === 'reseau') return legendeLecture();
|
||||
if (vuePrincipale === 'applications') return inspecteurApplication();
|
||||
if (vuePrincipale === 'bases') return inspecteurBase();
|
||||
if (vuePrincipale === 'domaines') return inspecteurDomaine();
|
||||
|
|
@ -2332,6 +2367,12 @@ class Gestionnaire(BaseHTTPRequestHandler):
|
|||
self.repondre_json(200, inventaire_api(self.inventaire))
|
||||
elif chemin == "/api/sondes":
|
||||
self.repondre_json(200, sonder_hotes(self.inventaire))
|
||||
elif chemin == "/api/devis-reseau":
|
||||
tenants = devis_reseau.decouvrir()
|
||||
self.repondre_json(200, {
|
||||
"config": devis_reseau.generer(tenants) if tenants else "",
|
||||
"tenants": [{"nom": nom, "index": n["index"]} for nom, _, n in tenants],
|
||||
})
|
||||
else:
|
||||
self.repondre_json(404, {"erreur": "Introuvable"})
|
||||
except Exception as exc:
|
||||
|
|
|
|||
Loading…
Reference in a new issue