feat(patching/iexec): boutons et stepper avec etats visuels (gris pending / orange en cours / vert done / rouge failed) - cascade automatique selon resultats accumules + animation pulse pour running
This commit is contained in:
parent
0f5296ab40
commit
830eaaa519
@ -13,19 +13,44 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# ─── Stepper ─── #}
|
{# ─── Stepper ─── #}
|
||||||
<div class="flex items-center mb-4 gap-2 text-xs">
|
<style>
|
||||||
<span class="px-3 py-1 rounded bg-cyber-yellow/20 text-cyber-yellow font-bold">1. Vérifications</span>
|
.step-pill { padding:.35rem .75rem; border-radius:.5rem; font-weight:bold;
|
||||||
<span class="text-gray-500">→</span>
|
font-size:.7rem; transition:all .2s; border:1px solid transparent; }
|
||||||
<span class="px-3 py-1 rounded bg-cyber-green/20 text-cyber-green font-bold">2. Snapshot</span>
|
.step-pill.s-pending { background:rgba(75,85,99,.15); color:#6b7280;
|
||||||
<span class="text-gray-500">→</span>
|
border-color:rgba(75,85,99,.3); opacity:.6; }
|
||||||
<span class="px-3 py-1 rounded bg-cyber-blue/20 text-cyber-blue font-bold">3. Patch yum</span>
|
.step-pill.s-current { background:rgba(245,158,11,.18); color:#f59e0b;
|
||||||
|
border-color:#f59e0b; box-shadow:0 0 12px rgba(245,158,11,.35); }
|
||||||
|
.step-pill.s-done { background:rgba(34,197,94,.18); color:#22c55e;
|
||||||
|
border-color:rgba(34,197,94,.5); }
|
||||||
|
.step-pill.s-failed { background:rgba(239,68,68,.2); color:#ef4444;
|
||||||
|
border-color:#ef4444; box-shadow:0 0 10px rgba(239,68,68,.35); }
|
||||||
|
.step-pill.s-running { background:rgba(245,158,11,.25); color:#f59e0b;
|
||||||
|
border-color:#f59e0b; animation: pulseIt 1.2s infinite; }
|
||||||
|
@keyframes pulseIt { 0%,100% { opacity:1 } 50% { opacity:.55 } }
|
||||||
|
</style>
|
||||||
|
<div class="flex items-center mb-4 gap-1 text-xs flex-wrap" id="stepper">
|
||||||
|
<span data-step="check" class="step-pill s-current">1 Vérif</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="snap" class="step-pill s-pending">2 Snapshot</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="dry" class="step-pill s-pending">3a Dry-run</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="pre" class="step-pill s-pending">3b Pre-capt</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="patch" class="step-pill s-pending">3c Patch</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="reboot" class="step-pill s-pending">3e Reboot</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="recon" class="step-pill s-pending">3f Reconn.</span>
|
||||||
|
<span class="text-gray-600">→</span>
|
||||||
|
<span data-step="post" class="step-pill s-pending">3g Post-cmp</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card p-3 mb-4">
|
<div class="card p-3 mb-4">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<h3 class="text-sm font-bold text-cyber-accent">Step 1 — Vérifications pré-patching</h3>
|
<h3 class="text-sm font-bold text-cyber-accent">Step 1 — Vérifications pré-patching</h3>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button id="btn-run-all" class="btn-primary px-3 py-1 text-xs">Lancer les checks</button>
|
<button id="btn-run-all" class="step-btn s-current" title="Lance les vérifs DNS+SSH+Disque+Satellite">1 Lancer les vérifs</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mb-3">
|
<p class="text-xs text-gray-500 mb-3">
|
||||||
@ -116,30 +141,35 @@
|
|||||||
</style>
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-btn { padding:.5rem 1rem; border-radius:.5rem; font-weight:bold;
|
||||||
|
font-size:.75rem; transition:all .15s; border:1px solid transparent;
|
||||||
|
white-space:nowrap; cursor:pointer; }
|
||||||
|
.step-btn:disabled { cursor:not-allowed; }
|
||||||
|
.step-btn.s-pending { background:rgba(75,85,99,.15); color:#6b7280;
|
||||||
|
border-color:rgba(75,85,99,.3); opacity:.55; }
|
||||||
|
.step-btn.s-current { background:rgba(245,158,11,.2); color:#f59e0b;
|
||||||
|
border-color:#f59e0b; box-shadow:0 0 14px rgba(245,158,11,.4); }
|
||||||
|
.step-btn.s-current:hover { background:rgba(245,158,11,.35); }
|
||||||
|
.step-btn.s-done { background:rgba(34,197,94,.2); color:#22c55e;
|
||||||
|
border-color:rgba(34,197,94,.5); }
|
||||||
|
.step-btn.s-done:hover { background:rgba(34,197,94,.35); }
|
||||||
|
.step-btn.s-failed { background:rgba(239,68,68,.2); color:#ef4444;
|
||||||
|
border-color:#ef4444; box-shadow:0 0 12px rgba(239,68,68,.4); }
|
||||||
|
.step-btn.s-failed:hover { background:rgba(239,68,68,.35); }
|
||||||
|
.step-btn.s-running { background:rgba(245,158,11,.3); color:#f59e0b;
|
||||||
|
border-color:#f59e0b; animation: pulseIt 1.2s infinite; }
|
||||||
|
</style>
|
||||||
<div class="flex justify-between items-center mt-4 flex-wrap gap-2">
|
<div class="flex justify-between items-center mt-4 flex-wrap gap-2">
|
||||||
<span id="run-summary" class="text-xs text-gray-400"></span>
|
<span id="run-summary" class="text-xs text-gray-400"></span>
|
||||||
<div class="flex gap-2 flex-wrap">
|
<div class="flex gap-2 flex-wrap">
|
||||||
<button id="btn-step2" class="btn-sm bg-cyber-green/20 text-cyber-green px-4 py-2 text-xs" disabled>
|
<button id="btn-step2" class="step-btn s-pending" disabled title="Snapshot vCenter">2 Snapshot</button>
|
||||||
→ Step 2 (snapshot vCenter)
|
<button id="btn-dryrun" class="step-btn s-pending" disabled title="yum update --assumeno : simule sans appliquer">3a Dry-run</button>
|
||||||
</button>
|
<button id="btn-pre" class="step-btn s-pending" disabled title="Capture services + ports avant patch">3b Pre-capt.</button>
|
||||||
<button id="btn-dryrun" class="btn-sm bg-cyber-yellow/20 text-cyber-yellow px-4 py-2 text-xs" disabled title="yum update --assumeno : simule sans appliquer">
|
<button id="btn-step3" class="step-btn s-pending" disabled title="yum update -y : applique réellement (double confirmation)">3c Patcher</button>
|
||||||
→ 3a Dry-run
|
<button id="btn-reboot" class="step-btn s-pending" disabled title="shutdown -r +1 sur les serveurs patchés (double confirmation)">3e Reboot</button>
|
||||||
</button>
|
<button id="btn-recon" class="step-btn s-pending" disabled title="Polle TCP/22 + SSH jusqu'à reconnexion">3f Wait reconn.</button>
|
||||||
<button id="btn-pre" class="btn-sm bg-cyber-yellow/20 text-cyber-yellow px-4 py-2 text-xs" disabled title="Capture services + ports avant patch">
|
<button id="btn-post" class="step-btn s-pending" disabled title="Compare services/ports avant/après patch">3g Post-cmp.</button>
|
||||||
→ 3b Pre-capt.
|
|
||||||
</button>
|
|
||||||
<button id="btn-step3" class="btn-sm bg-cyber-blue/20 text-cyber-blue px-4 py-2 text-xs" disabled title="yum update -y : applique réellement les patchs">
|
|
||||||
→ 3c Patcher
|
|
||||||
</button>
|
|
||||||
<button id="btn-reboot" class="btn-sm bg-cyber-red/20 text-cyber-red px-4 py-2 text-xs" disabled title="shutdown -r +1 sur les serveurs patchés (double confirmation)">
|
|
||||||
→ 3e Reboot
|
|
||||||
</button>
|
|
||||||
<button id="btn-recon" class="btn-sm bg-cyber-yellow/20 text-cyber-yellow px-4 py-2 text-xs" disabled title="Polle TCP/22 + SSH jusqu'à reconnexion">
|
|
||||||
→ 3f Wait reconn.
|
|
||||||
</button>
|
|
||||||
<button id="btn-post" class="btn-sm bg-cyber-blue/20 text-cyber-blue px-4 py-2 text-xs" disabled title="Compare services/ports avant/après patch (à lancer après reconnexion)">
|
|
||||||
→ 3g Post-cmp.
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -235,7 +265,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
btnRun.addEventListener('click', async () => {
|
btnRun.addEventListener('click', async () => {
|
||||||
btnRun.disabled = true; btnStep2.disabled = true;
|
setBtnState(btnRun, 'running'); setStepState('check', 'running');
|
||||||
summary.textContent = 'Lancement…';
|
summary.textContent = 'Lancement…';
|
||||||
const trs = Array.from(tbody.querySelectorAll('tr[data-row-id]'));
|
const trs = Array.from(tbody.querySelectorAll('tr[data-row-id]'));
|
||||||
let okCount = 0, warnCount = 0, koCount = 0, naCount = 0;
|
let okCount = 0, warnCount = 0, koCount = 0, naCount = 0;
|
||||||
@ -247,8 +277,7 @@
|
|||||||
else koCount++;
|
else koCount++;
|
||||||
}
|
}
|
||||||
summary.innerHTML = '✓ ' + okCount + ' OK · ⚠ ' + warnCount + ' warn · ✗ ' + koCount + ' KO · ⊘ ' + naCount + ' N/A';
|
summary.innerHTML = '✓ ' + okCount + ' OK · ⚠ ' + warnCount + ' warn · ✗ ' + koCount + ' KO · ⊘ ' + naCount + ' N/A';
|
||||||
btnRun.disabled = false;
|
refreshStepButtons();
|
||||||
btnStep2.disabled = (okCount === 0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Terminal global (toutes les étapes) ───
|
// ─── Terminal global (toutes les étapes) ───
|
||||||
@ -394,21 +423,77 @@
|
|||||||
refreshStepButtons();
|
refreshStepButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── États visuels boutons + stepper ───
|
||||||
|
function setBtnState(btn, state){
|
||||||
|
if (!btn) return;
|
||||||
|
btn.classList.remove('s-pending','s-current','s-done','s-failed','s-running');
|
||||||
|
btn.classList.add('s-' + state);
|
||||||
|
btn.disabled = (state === 'pending' || state === 'running');
|
||||||
|
}
|
||||||
|
function setStepState(name, state){
|
||||||
|
const el = document.querySelector('#stepper [data-step="' + name + '"]');
|
||||||
|
if (!el) return;
|
||||||
|
el.classList.remove('s-pending','s-current','s-done','s-failed','s-running');
|
||||||
|
el.classList.add('s-' + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcul de l'état de chaque étape selon les données accumulées
|
||||||
|
function deriveState(prevDoneCount, ownAttempted, ownOkCount){
|
||||||
|
// pending : étape précédente pas encore validée
|
||||||
|
if (prevDoneCount === 0) return 'pending';
|
||||||
|
// pas encore tenté : étape actuelle (à faire)
|
||||||
|
if (!ownAttempted) return 'current';
|
||||||
|
// tenté : ok si au moins 1 OK, failed sinon
|
||||||
|
return ownOkCount > 0 ? 'done' : 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
function refreshStepButtons(){
|
function refreshStepButtons(){
|
||||||
const trs = Array.from(tbody.querySelectorAll('tr[data-row-id]'));
|
const trs = Array.from(tbody.querySelectorAll('tr[data-row-id]'));
|
||||||
const ckOk = trs.filter(tr => tr._checkData && tr._checkData.overall === 'ok');
|
const checkAttempted = trs.some(tr => tr._checkData);
|
||||||
const snapOk = trs.filter(tr => tr._snapData && tr._snapData.ok);
|
const ckOk = trs.filter(tr => tr._checkData && tr._checkData.overall === 'ok').length;
|
||||||
const dryOk = trs.filter(tr => tr._dryData && tr._dryData.ok);
|
const snapAttempted = trs.some(tr => tr._snapData);
|
||||||
const preOk = trs.filter(tr => tr._preData && tr._preData.ok);
|
const snapOk = trs.filter(tr => tr._snapData && tr._snapData.ok).length;
|
||||||
const patchOk = trs.filter(tr => tr._patchData && tr._patchData.ok);
|
const dryAttempted = trs.some(tr => tr._dryData);
|
||||||
const recOk = trs.filter(tr => tr._reconData && tr._reconData.ok);
|
const dryOk = trs.filter(tr => tr._dryData && tr._dryData.ok).length;
|
||||||
btnStep2.disabled = (ckOk.length === 0);
|
const preAttempted = trs.some(tr => tr._preData);
|
||||||
btnDryrun.disabled = (snapOk.length === 0);
|
const preOk = trs.filter(tr => tr._preData && tr._preData.ok).length;
|
||||||
btnPre.disabled = (dryOk.length === 0);
|
const patchAttempted = trs.some(tr => tr._patchData);
|
||||||
btnStep3.disabled = (preOk.length === 0);
|
const patchOk = trs.filter(tr => tr._patchData && tr._patchData.ok).length;
|
||||||
btnReboot.disabled = (patchOk.length === 0);
|
const rebootAttempted = trs.some(tr => tr._rebootData);
|
||||||
btnRecon.disabled = (patchOk.length === 0);
|
const rebootOk = trs.filter(tr => tr._rebootData && tr._rebootData.ok).length;
|
||||||
btnPost.disabled = (recOk.length === 0 && patchOk.length === 0);
|
const reconAttempted = trs.some(tr => tr._reconData);
|
||||||
|
const reconOk = trs.filter(tr => tr._reconData && tr._reconData.ok).length;
|
||||||
|
const postAttempted = trs.some(tr => tr._postData);
|
||||||
|
const postOk = trs.filter(tr => tr._postData && tr._postData.ok).length;
|
||||||
|
|
||||||
|
// Step 1 (check) : toujours dispo. current si pas tenté, done si OK, failed si tenté KO
|
||||||
|
const checkState = !checkAttempted ? 'current' : (ckOk > 0 ? 'done' : 'failed');
|
||||||
|
setBtnState(btnRun, checkState);
|
||||||
|
setStepState('check', checkState);
|
||||||
|
|
||||||
|
// Cascade : chaque étape dépend du `done` de la précédente
|
||||||
|
const snapState = deriveState(ckOk, snapAttempted, snapOk);
|
||||||
|
setBtnState(btnStep2, snapState); setStepState('snap', snapState);
|
||||||
|
|
||||||
|
const dryState = deriveState(snapOk, dryAttempted, dryOk);
|
||||||
|
setBtnState(btnDryrun, dryState); setStepState('dry', dryState);
|
||||||
|
|
||||||
|
const preState = deriveState(dryOk, preAttempted, preOk);
|
||||||
|
setBtnState(btnPre, preState); setStepState('pre', preState);
|
||||||
|
|
||||||
|
const patchState = deriveState(preOk, patchAttempted, patchOk);
|
||||||
|
setBtnState(btnStep3, patchState); setStepState('patch', patchState);
|
||||||
|
|
||||||
|
const rebootState = deriveState(patchOk, rebootAttempted, rebootOk);
|
||||||
|
setBtnState(btnReboot, rebootState); setStepState('reboot', rebootState);
|
||||||
|
|
||||||
|
const reconState = deriveState(patchOk, reconAttempted, reconOk);
|
||||||
|
setBtnState(btnRecon, reconState); setStepState('recon', reconState);
|
||||||
|
|
||||||
|
// Post-cmp dispo dès qu'on a soit recon OK (idéal après reboot) soit patch OK
|
||||||
|
const postPrevDone = Math.max(reconOk, patchOk);
|
||||||
|
const postState = deriveState(postPrevDone, postAttempted, postOk);
|
||||||
|
setBtnState(btnPost, postState); setStepState('post', postState);
|
||||||
}
|
}
|
||||||
|
|
||||||
btnStep2.addEventListener('click', async () => {
|
btnStep2.addEventListener('click', async () => {
|
||||||
@ -416,8 +501,7 @@
|
|||||||
const okTrs = trs.filter(tr => tr._checkData && tr._checkData.overall === 'ok');
|
const okTrs = trs.filter(tr => tr._checkData && tr._checkData.overall === 'ok');
|
||||||
if (!okTrs.length) { alert('Aucune ligne avec verdict OK'); return; }
|
if (!okTrs.length) { alert('Aucune ligne avec verdict OK'); return; }
|
||||||
if (!confirm('Lancer snapshot vCenter pour ' + okTrs.length + ' serveur(s) ?\n(nom = <intervenant>_YYYY-MM-DD_avant_patch)')) return;
|
if (!confirm('Lancer snapshot vCenter pour ' + okTrs.length + ' serveur(s) ?\n(nom = <intervenant>_YYYY-MM-DD_avant_patch)')) return;
|
||||||
btnStep2.disabled = true;
|
setBtnState(btnStep2, 'running'); setStepState('snap', 'running');
|
||||||
btnRun.disabled = true;
|
|
||||||
let okCount = 0, koCount = 0;
|
let okCount = 0, koCount = 0;
|
||||||
for (const tr of okTrs) {
|
for (const tr of okTrs) {
|
||||||
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
@ -449,7 +533,6 @@
|
|||||||
termLine('✗', 'exception : ' + e.message);
|
termLine('✗', 'exception : ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
btnRun.disabled = false;
|
|
||||||
summary.innerHTML += ' · Snapshot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
summary.innerHTML += ' · Snapshot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
||||||
refreshStepButtons();
|
refreshStepButtons();
|
||||||
});
|
});
|
||||||
@ -459,7 +542,7 @@
|
|||||||
const targets = trs.filter(tr => tr._snapData && tr._snapData.ok);
|
const targets = trs.filter(tr => tr._snapData && tr._snapData.ok);
|
||||||
if (!targets.length) { alert('Aucun serveur avec snapshot OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec snapshot OK'); return; }
|
||||||
if (!confirm('Lancer dry-run yum (simulation) sur ' + targets.length + ' serveur(s) ?\nLog en temps réel dans le terminal.')) return;
|
if (!confirm('Lancer dry-run yum (simulation) sur ' + targets.length + ' serveur(s) ?\nLog en temps réel dans le terminal.')) return;
|
||||||
btnDryrun.disabled = true; btnStep3.disabled = true;
|
setBtnState(btnDryrun, 'running'); setStepState('dry', 'running');
|
||||||
let okCount = 0, koCount = 0;
|
let okCount = 0, koCount = 0;
|
||||||
for (const tr of targets) {
|
for (const tr of targets) {
|
||||||
const host = tr.querySelector('td:nth-child(2)').textContent.trim()
|
const host = tr.querySelector('td:nth-child(2)').textContent.trim()
|
||||||
@ -478,7 +561,7 @@
|
|||||||
const targets = trs.filter(tr => tr._dryData && tr._dryData.ok);
|
const targets = trs.filter(tr => tr._dryData && tr._dryData.ok);
|
||||||
if (!targets.length) { alert('Aucun serveur avec dry-run OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec dry-run OK'); return; }
|
||||||
if (!confirm('Capture services+ports avant patch sur ' + targets.length + ' serveur(s) ?')) return;
|
if (!confirm('Capture services+ports avant patch sur ' + targets.length + ' serveur(s) ?')) return;
|
||||||
btnPre.disabled = true; btnStep3.disabled = true;
|
setBtnState(btnPre, 'running'); setStepState('pre', 'running');
|
||||||
let okCount = 0, koCount = 0;
|
let okCount = 0, koCount = 0;
|
||||||
for (const tr of targets) {
|
for (const tr of targets) {
|
||||||
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
@ -516,7 +599,7 @@
|
|||||||
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
||||||
if (!confirm('⚠ REBOOT ⚠\n\nDéclencher `shutdown -r +1` sur ' + targets.length + ' serveur(s) ?\n(le reboot effectif a lieu dans 1 minute)')) return;
|
if (!confirm('⚠ REBOOT ⚠\n\nDéclencher `shutdown -r +1` sur ' + targets.length + ' serveur(s) ?\n(le reboot effectif a lieu dans 1 minute)')) return;
|
||||||
if (!confirm('Vraiment ? Liste des hôtes :\n' + targets.map(tr => tr.querySelector('td:nth-child(3)').textContent.trim()).join('\n') + '\n\nConfirmer le reboot ?')) return;
|
if (!confirm('Vraiment ? Liste des hôtes :\n' + targets.map(tr => tr.querySelector('td:nth-child(3)').textContent.trim()).join('\n') + '\n\nConfirmer le reboot ?')) return;
|
||||||
btnReboot.disabled = true;
|
setBtnState(btnReboot, 'running'); setStepState('reboot', 'running');
|
||||||
let okCount = 0, koCount = 0;
|
let okCount = 0, koCount = 0;
|
||||||
for (const tr of targets) {
|
for (const tr of targets) {
|
||||||
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
@ -544,6 +627,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary.innerHTML += ' · Reboot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
summary.innerHTML += ' · Reboot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
||||||
|
refreshStepButtons();
|
||||||
});
|
});
|
||||||
|
|
||||||
btnRecon.addEventListener('click', async () => {
|
btnRecon.addEventListener('click', async () => {
|
||||||
@ -551,7 +635,7 @@
|
|||||||
const targets = trs.filter(tr => tr._patchData && tr._patchData.ok);
|
const targets = trs.filter(tr => tr._patchData && tr._patchData.ok);
|
||||||
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
||||||
if (!confirm('Attendre la reconnexion (TCP/22 + SSH) sur ' + targets.length + ' serveur(s) ?\nPoll toutes les 10s, timeout 10 min par serveur.')) return;
|
if (!confirm('Attendre la reconnexion (TCP/22 + SSH) sur ' + targets.length + ' serveur(s) ?\nPoll toutes les 10s, timeout 10 min par serveur.')) return;
|
||||||
btnRecon.disabled = true;
|
setBtnState(btnRecon, 'running'); setStepState('recon', 'running');
|
||||||
const startTs = Date.now();
|
const startTs = Date.now();
|
||||||
// Pour chaque target, polling indépendant
|
// Pour chaque target, polling indépendant
|
||||||
await Promise.all(targets.map(async (tr) => {
|
await Promise.all(targets.map(async (tr) => {
|
||||||
@ -597,7 +681,7 @@
|
|||||||
const targets = trs.filter(tr => tr._patchData && tr._patchData.ok);
|
const targets = trs.filter(tr => tr._patchData && tr._patchData.ok);
|
||||||
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec patch OK'); return; }
|
||||||
if (!confirm('Comparer services+ports avant/après patch sur ' + targets.length + ' serveur(s) ?\n(à lancer après le reboot du serveur)')) return;
|
if (!confirm('Comparer services+ports avant/après patch sur ' + targets.length + ' serveur(s) ?\n(à lancer après le reboot du serveur)')) return;
|
||||||
btnPost.disabled = true;
|
setBtnState(btnPost, 'running'); setStepState('post', 'running');
|
||||||
let okCount = 0, warnCount = 0, koCount = 0;
|
let okCount = 0, warnCount = 0, koCount = 0;
|
||||||
for (const tr of targets) {
|
for (const tr of targets) {
|
||||||
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
@ -642,7 +726,7 @@
|
|||||||
if (!targets.length) { alert('Aucun serveur avec pre-capture OK'); return; }
|
if (!targets.length) { alert('Aucun serveur avec pre-capture OK'); return; }
|
||||||
if (!confirm('⚠ ATTENTION ⚠\n\nLancer yum update -y (PATCH RÉEL) sur ' + targets.length + ' serveur(s) ?\nSnapshot + pre-capture déjà faits.\nLog en temps réel dans le terminal.')) return;
|
if (!confirm('⚠ ATTENTION ⚠\n\nLancer yum update -y (PATCH RÉEL) sur ' + targets.length + ' serveur(s) ?\nSnapshot + pre-capture déjà faits.\nLog en temps réel dans le terminal.')) return;
|
||||||
if (!confirm('Confirmer une 2e fois : patcher RÉELLEMENT ' + targets.length + ' serveur(s) ?')) return;
|
if (!confirm('Confirmer une 2e fois : patcher RÉELLEMENT ' + targets.length + ' serveur(s) ?')) return;
|
||||||
btnStep3.disabled = true; btnDryrun.disabled = true; btnStep2.disabled = true;
|
setBtnState(btnStep3, 'running'); setStepState('patch', 'running');
|
||||||
let okCount = 0, koCount = 0;
|
let okCount = 0, koCount = 0;
|
||||||
for (const tr of targets) {
|
for (const tr of targets) {
|
||||||
const host = tr.querySelector('td:nth-child(2)').textContent.trim()
|
const host = tr.querySelector('td:nth-child(2)').textContent.trim()
|
||||||
@ -656,6 +740,9 @@
|
|||||||
refreshStepButtons();
|
refreshStepButtons();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Init états visuels au chargement
|
||||||
|
refreshStepButtons();
|
||||||
|
|
||||||
// Click sur une ligne → afficher les détails
|
// Click sur une ligne → afficher les détails
|
||||||
tbody.addEventListener('click', (ev) => {
|
tbody.addEventListener('click', (ev) => {
|
||||||
const tr = ev.target.closest('tr[data-row-id]');
|
const tr = ev.target.closest('tr[data-row-id]');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user