feat(patching/iexec): terminal global HTML colore consolide - toutes les etapes (check, snap, dryrun, pre, patch, reboot, recon, post) appendent dans le meme terminal avec sections horodatees + couleurs ANSI-like + scroll auto + bouton Vider
This commit is contained in:
parent
ff95424e03
commit
0f5296ab40
@ -94,13 +94,26 @@
|
|||||||
{# ─── Terminal style log dry-run / patch ─── #}
|
{# ─── Terminal style log dry-run / patch ─── #}
|
||||||
<div class="card p-2 mb-4" id="term-card" style="display:none;">
|
<div class="card p-2 mb-4" id="term-card" style="display:none;">
|
||||||
<div class="flex justify-between items-center mb-1">
|
<div class="flex justify-between items-center mb-1">
|
||||||
<h3 id="term-title" class="text-xs text-cyber-accent font-bold font-mono">$ terminal</h3>
|
<h3 id="term-title" class="text-xs text-cyber-accent font-bold font-mono">$ patchcenter iexec — log</h3>
|
||||||
<button id="term-close" class="text-xs text-gray-500 hover:text-cyber-accent">✕ Fermer</button>
|
<div class="flex gap-2">
|
||||||
|
<button id="term-clear" class="text-xs text-gray-500 hover:text-cyber-accent">🗑 Vider</button>
|
||||||
|
<button id="term-close" class="text-xs text-gray-500 hover:text-cyber-accent">✕ Masquer</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<pre id="term-pane" class="p-3 text-[11px] whitespace-pre-wrap overflow-auto"
|
<pre id="term-pane" class="p-3 text-[11px] whitespace-pre-wrap overflow-auto"
|
||||||
style="max-height:500px; background:#0a0e14; color:#a6e22e;
|
style="max-height:600px; background:#0a0e14; color:#cdd6f4;
|
||||||
font-family:'Cascadia Code','Consolas','Courier New',monospace;
|
font-family:'Cascadia Code','Consolas','Courier New',monospace;
|
||||||
border:1px solid #1f2937; line-height:1.4;"></pre>
|
border:1px solid #1f2937; line-height:1.4;"></pre>
|
||||||
|
<style>
|
||||||
|
#term-pane .t-section { color:#c678dd; font-weight:bold; }
|
||||||
|
#term-pane .t-ok { color:#a6e22e; }
|
||||||
|
#term-pane .t-warn { color:#e5c07b; }
|
||||||
|
#term-pane .t-ko { color:#ff5555; }
|
||||||
|
#term-pane .t-info { color:#61afef; }
|
||||||
|
#term-pane .t-mute { color:#7f848e; }
|
||||||
|
#term-pane .t-cmd { color:#56b6c2; font-weight:bold; }
|
||||||
|
#term-pane .t-detail { color:#abb2bf; padding-left:2ch; display:block; }
|
||||||
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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">
|
||||||
@ -166,20 +179,25 @@
|
|||||||
async function checkOne(tr){
|
async function checkOne(tr){
|
||||||
const rowId = tr.dataset.rowId;
|
const rowId = tr.dataset.rowId;
|
||||||
const osStr = tr.dataset.os || '';
|
const osStr = tr.dataset.os || '';
|
||||||
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
if (!isLinux(osStr)) {
|
if (!isLinux(osStr)) {
|
||||||
tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-ssh').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-ssh').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-overall').innerHTML = '<span class="text-gray-400" title="Workflow Linux uniquement">⊘ Windows</span>';
|
tr.querySelector('.cell-overall').innerHTML = '<span class="text-gray-400" title="Workflow Linux uniquement">⊘ Windows</span>';
|
||||||
|
termSection('check ' + host);
|
||||||
|
termLine('⊘', 'Windows non supporté (Linux uniquement)');
|
||||||
return {overall: 'unsupported'};
|
return {overall: 'unsupported'};
|
||||||
}
|
}
|
||||||
|
termSection('check ' + host);
|
||||||
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-yellow">…</span>';
|
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-yellow">…</span>';
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/patching/iexec/check/' + rowId, {method:'POST'});
|
const r = await fetch('/patching/iexec/check/' + rowId, {method:'POST'});
|
||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
if (!j.ok) {
|
if (!j.ok) {
|
||||||
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
|
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
|
||||||
|
termLine('✗', 'erreur backend');
|
||||||
return {overall: 'ko'};
|
return {overall: 'ko'};
|
||||||
}
|
}
|
||||||
if (j.overall === 'unsupported') {
|
if (j.overall === 'unsupported') {
|
||||||
@ -188,19 +206,30 @@
|
|||||||
tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported');
|
tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported');
|
||||||
tr.querySelector('.cell-overall').innerHTML = '<span class="text-gray-400" title="' + (j.skipped_reason||'') + '">⊘ N/A</span>';
|
tr.querySelector('.cell-overall').innerHTML = '<span class="text-gray-400" title="' + (j.skipped_reason||'') + '">⊘ N/A</span>';
|
||||||
|
termLine('⊘', j.skipped_reason || 'non supporté');
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
appendTerm(' # target=' + (j.target || '?') + '\n');
|
||||||
const byName = {};
|
const byName = {};
|
||||||
(j.checks || []).forEach(c => byName[c.name] = c);
|
(j.checks || []).forEach(c => {
|
||||||
|
byName[c.name] = c;
|
||||||
|
const sym = c.status === 'ok' ? '✓' : (c.status === 'warn' ? '⚠' : '✗');
|
||||||
|
termLine(sym, c.label + ' : ' + c.message);
|
||||||
|
if (c.details && c.status !== 'ok') {
|
||||||
|
appendTerm(' │ ' + (c.details || '').split('\n').slice(0,8).join('\n │ ') + '\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
tr.querySelector('.cell-dns').innerHTML = byName.dns ? statusBadge(byName.dns.status) : '–';
|
tr.querySelector('.cell-dns').innerHTML = byName.dns ? statusBadge(byName.dns.status) : '–';
|
||||||
tr.querySelector('.cell-ssh').innerHTML = byName.ssh ? statusBadge(byName.ssh.status) : '–';
|
tr.querySelector('.cell-ssh').innerHTML = byName.ssh ? statusBadge(byName.ssh.status) : '–';
|
||||||
tr.querySelector('.cell-disk').innerHTML = byName.disk ? statusBadge(byName.disk.status) + ' <span class="text-[10px] text-gray-400">' + escapeHTML(byName.disk.message) + '</span>' : '–';
|
tr.querySelector('.cell-disk').innerHTML = byName.disk ? statusBadge(byName.disk.status) + ' <span class="text-[10px] text-gray-400">' + escapeHTML(byName.disk.message) + '</span>' : '–';
|
||||||
tr.querySelector('.cell-sat').innerHTML = byName.satellite ? statusBadge(byName.satellite.status) : '–';
|
tr.querySelector('.cell-sat').innerHTML = byName.satellite ? statusBadge(byName.satellite.status) : '–';
|
||||||
tr.querySelector('.cell-overall').innerHTML = statusBadge(j.overall) + ' <span class="text-[10px] text-gray-500">(' + (j.duration_ms||0) + 'ms)</span>';
|
tr.querySelector('.cell-overall').innerHTML = statusBadge(j.overall) + ' <span class="text-[10px] text-gray-500">(' + (j.duration_ms||0) + 'ms)</span>';
|
||||||
|
termLine('→', 'verdict ' + j.overall + ' (' + (j.duration_ms||0) + 'ms)');
|
||||||
tr._checkData = j;
|
tr._checkData = j;
|
||||||
return j;
|
return j;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
|
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
|
||||||
|
termLine('✗', 'exception : ' + e.message);
|
||||||
return {overall: 'ko'};
|
return {overall: 'ko'};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,29 +251,65 @@
|
|||||||
btnStep2.disabled = (okCount === 0);
|
btnStep2.disabled = (okCount === 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Terminal ───
|
// ─── Terminal global (toutes les étapes) ───
|
||||||
const termCard = document.getElementById('term-card');
|
const termCard = document.getElementById('term-card');
|
||||||
const termTitle = document.getElementById('term-title');
|
const termTitle = document.getElementById('term-title');
|
||||||
const termPane = document.getElementById('term-pane');
|
const termPane = document.getElementById('term-pane');
|
||||||
document.getElementById('term-close').addEventListener('click', () => {
|
document.getElementById('term-close').addEventListener('click', () => {
|
||||||
termCard.style.display = 'none';
|
termCard.style.display = 'none';
|
||||||
});
|
});
|
||||||
function openTerm(title){
|
document.getElementById('term-clear').addEventListener('click', () => {
|
||||||
|
termPane.innerHTML = '';
|
||||||
|
});
|
||||||
|
function showTerm(){
|
||||||
|
if (termCard.style.display === 'none') {
|
||||||
termCard.style.display = '';
|
termCard.style.display = '';
|
||||||
termTitle.textContent = '$ ' + title;
|
|
||||||
termPane.textContent = '';
|
|
||||||
termCard.scrollIntoView({behavior:'smooth', block:'nearest'});
|
termCard.scrollIntoView({behavior:'smooth', block:'nearest'});
|
||||||
}
|
}
|
||||||
function appendTerm(s){
|
}
|
||||||
termPane.textContent += s;
|
function appendTermHTML(html){
|
||||||
|
termPane.insertAdjacentHTML('beforeend', html);
|
||||||
termPane.scrollTop = termPane.scrollHeight;
|
termPane.scrollTop = termPane.scrollHeight;
|
||||||
}
|
}
|
||||||
|
function appendTerm(s){
|
||||||
|
// Compat : ancien API qui écrit du texte brut
|
||||||
|
appendTermHTML(escapeHTML(s));
|
||||||
|
}
|
||||||
|
function termSection(label){
|
||||||
|
showTerm();
|
||||||
|
const ts = new Date().toLocaleTimeString('fr-FR', {hour12: false});
|
||||||
|
appendTermHTML('\n<span class="t-section">══════ [' + escapeHTML(label) + '] ' + ts + ' ══════</span>\n');
|
||||||
|
}
|
||||||
|
function termLine(prefix, text){
|
||||||
|
let cls = '';
|
||||||
|
if (prefix === '✓') cls = 't-ok';
|
||||||
|
else if (prefix === '⚠') cls = 't-warn';
|
||||||
|
else if (prefix === '✗') cls = 't-ko';
|
||||||
|
else if (prefix === '⊘') cls = 't-mute';
|
||||||
|
else if (prefix === '⏳') cls = 't-warn';
|
||||||
|
else if (prefix === '→') cls = 't-info';
|
||||||
|
else if (prefix === '#') cls = 't-mute';
|
||||||
|
appendTermHTML(' <span class="' + cls + '">' + escapeHTML(prefix + ' ' + text) + '</span>\n');
|
||||||
|
}
|
||||||
|
function termDetail(text){
|
||||||
|
if (!text) return;
|
||||||
|
const lines = String(text).split('\n').slice(0, 12);
|
||||||
|
appendTermHTML('<span class="t-detail">' + escapeHTML(lines.join('\n')) + '</span>\n');
|
||||||
|
}
|
||||||
|
function classifyYumLine(s){
|
||||||
|
const ll = String(s).toLowerCase();
|
||||||
|
if (ll.startsWith('error') || ll.startsWith('erreur') || ll.includes(' fail') || ll.includes('failed')) return 't-ko';
|
||||||
|
if (ll.startsWith('warning') || ll.startsWith('attention')) return 't-warn';
|
||||||
|
if (ll.includes('complete!') || ll.includes('terminé !') || ll.includes('nothing to do') || ll.includes('rien à faire')) return 't-ok';
|
||||||
|
if (ll.startsWith('==') || ll.startsWith('--') || ll.startsWith('transaction')) return 't-info';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function streamYum(rowId, mode, hostname, extraExcludes){
|
function streamYum(rowId, mode, hostname, extraExcludes){
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const titre = (mode === 'dryrun' ? 'dry-run' : 'PATCH') + ' yum @ ' + hostname
|
const titre = (mode === 'dryrun' ? 'dry-run' : 'PATCH') + ' yum ' + hostname
|
||||||
+ (extraExcludes ? ' [retry +' + extraExcludes + ']' : '');
|
+ (extraExcludes ? ' [retry +' + extraExcludes + ']' : '');
|
||||||
openTerm(titre);
|
termSection(titre);
|
||||||
let url = '/patching/iexec/yum-stream/' + rowId + '?mode=' + mode;
|
let url = '/patching/iexec/yum-stream/' + rowId + '?mode=' + mode;
|
||||||
if (extraExcludes) url += '&extra_excludes=' + encodeURIComponent(extraExcludes);
|
if (extraExcludes) url += '&extra_excludes=' + encodeURIComponent(extraExcludes);
|
||||||
const ev = new EventSource(url);
|
const ev = new EventSource(url);
|
||||||
@ -255,9 +320,11 @@
|
|||||||
if (j.type === 'cmd') {
|
if (j.type === 'cmd') {
|
||||||
appendTerm(' # host : ' + (j.hostname||'') + ' (' + (j.target||'') + ')\n');
|
appendTerm(' # host : ' + (j.hostname||'') + ' (' + (j.target||'') + ')\n');
|
||||||
appendTerm(' # cmd : ' + (j.cmd||'') + '\n');
|
appendTerm(' # cmd : ' + (j.cmd||'') + '\n');
|
||||||
appendTerm('# excludes (' + (j.excludes||[]).length + ')\n\n');
|
appendTerm(' # excludes (' + (j.excludes||[]).length + ')\n');
|
||||||
} else if (j.type === 'line') {
|
} else if (j.type === 'line') {
|
||||||
appendTerm(j.data + '\n');
|
const cls = classifyYumLine(j.data);
|
||||||
|
if (cls) appendTermHTML(' <span class="' + cls + '">' + escapeHTML(j.data) + '</span>\n');
|
||||||
|
else appendTerm(j.data + '\n');
|
||||||
result.lines++;
|
result.lines++;
|
||||||
const ll = j.data.toLowerCase();
|
const ll = j.data.toLowerCase();
|
||||||
if (ll.includes('package') || ll.includes('paquet')
|
if (ll.includes('package') || ll.includes('paquet')
|
||||||
@ -353,6 +420,8 @@
|
|||||||
btnRun.disabled = true;
|
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();
|
||||||
|
termSection('snapshot ' + host);
|
||||||
const cell = tr.querySelector('.cell-snap');
|
const cell = tr.querySelector('.cell-snap');
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">… snapshot</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">… snapshot</span>';
|
||||||
try {
|
try {
|
||||||
@ -362,17 +431,22 @@
|
|||||||
okCount++;
|
okCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-green" title="' + escapeHTML(j.detail||'') + '">✓ ' + escapeHTML(j.snap_name||'OK') + '</span>'
|
cell.innerHTML = '<span class="text-cyber-green" title="' + escapeHTML(j.detail||'') + '">✓ ' + escapeHTML(j.snap_name||'OK') + '</span>'
|
||||||
+ ' <span class="text-[10px] text-gray-400">(' + escapeHTML(j.vcenter||'') + ')</span>';
|
+ ' <span class="text-[10px] text-gray-400">(' + escapeHTML(j.vcenter||'') + ')</span>';
|
||||||
|
termLine('✓', 'snap_name=' + (j.snap_name||'') + ' on vCenter ' + (j.vcenter||''));
|
||||||
|
if (j.branch) appendTerm(' # branch=' + j.branch + ' vm=' + (j.vm_name||'') + '\n');
|
||||||
} else if (j.skipped) {
|
} else if (j.skipped) {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">⚠ ' + escapeHTML(j.detail||'skip') + '</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">⚠ ' + escapeHTML(j.detail||'skip') + '</span>';
|
||||||
|
termLine('⚠', j.detail || 'skipped');
|
||||||
} else {
|
} else {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
||||||
|
termLine('✗', j.detail || 'KO');
|
||||||
}
|
}
|
||||||
tr._snapData = j;
|
tr._snapData = j;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red">✗ erreur réseau</span>';
|
cell.innerHTML = '<span class="text-cyber-red">✗ erreur réseau</span>';
|
||||||
|
termLine('✗', 'exception : ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
btnRun.disabled = false;
|
btnRun.disabled = false;
|
||||||
@ -407,6 +481,8 @@
|
|||||||
btnPre.disabled = true; btnStep3.disabled = true;
|
btnPre.disabled = true; btnStep3.disabled = true;
|
||||||
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();
|
||||||
|
termSection('pre-capture ' + host);
|
||||||
const cell = tr.querySelector('.cell-pre');
|
const cell = tr.querySelector('.cell-pre');
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">… capture</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">… capture</span>';
|
||||||
try {
|
try {
|
||||||
@ -416,13 +492,18 @@
|
|||||||
if (j.ok) {
|
if (j.ok) {
|
||||||
okCount++;
|
okCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-green" title="' + escapeHTML(j.stdout||'') + '">✓ snapshot</span>';
|
cell.innerHTML = '<span class="text-cyber-green" title="' + escapeHTML(j.stdout||'') + '">✓ snapshot</span>';
|
||||||
|
termLine('✓', 'services + ports capturés dans /tmp/secops_*_avant_*.txt');
|
||||||
|
termDetail(j.stdout);
|
||||||
} else {
|
} else {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||j.stderr||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||j.stderr||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
||||||
|
termLine('✗', j.detail || 'KO');
|
||||||
|
if (j.stderr) termDetail(j.stderr);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
||||||
|
termLine('✗', 'exception : ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary.innerHTML += ' · Pre-capt : ✓ ' + okCount + ' / ✗ ' + koCount;
|
summary.innerHTML += ' · Pre-capt : ✓ ' + okCount + ' / ✗ ' + koCount;
|
||||||
@ -438,6 +519,8 @@
|
|||||||
btnReboot.disabled = true;
|
btnReboot.disabled = true;
|
||||||
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();
|
||||||
|
termSection('reboot ' + host);
|
||||||
const cell = tr.querySelector('.cell-recon');
|
const cell = tr.querySelector('.cell-recon');
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">… reboot demandé</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">… reboot demandé</span>';
|
||||||
try {
|
try {
|
||||||
@ -447,13 +530,17 @@
|
|||||||
if (j.ok) {
|
if (j.ok) {
|
||||||
okCount++;
|
okCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">⏳ reboot dans 1min · ' + escapeHTML(j.started_at||'') + '</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">⏳ reboot dans 1min · ' + escapeHTML(j.started_at||'') + '</span>';
|
||||||
|
termLine('⏳', 'shutdown -r +1 envoyé · effectif dans 1 min · ' + (j.started_at||''));
|
||||||
} else {
|
} else {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||j.stderr||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.detail||j.stderr||'') + '">✗ ' + escapeHTML((j.detail||'KO').slice(0,80)) + '</span>';
|
||||||
|
termLine('✗', j.detail || 'KO');
|
||||||
|
if (j.stderr) termDetail(j.stderr);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
||||||
|
termLine('✗', 'exception : ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary.innerHTML += ' · Reboot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
summary.innerHTML += ' · Reboot : ✓ ' + okCount + ' / ✗ ' + koCount;
|
||||||
@ -468,11 +555,14 @@
|
|||||||
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) => {
|
||||||
|
const host = tr.querySelector('td:nth-child(3)').textContent.trim();
|
||||||
|
termSection('wait reconnexion ' + host);
|
||||||
const cell = tr.querySelector('.cell-recon');
|
const cell = tr.querySelector('.cell-recon');
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
const TIMEOUT_MS = 10 * 60 * 1000; // 10 min
|
const TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
const POLL_MS = 10 * 1000;
|
const POLL_MS = 10 * 1000;
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">⏳ poll TCP/22…</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">⏳ poll TCP/22…</span>';
|
||||||
|
let last = '';
|
||||||
while (Date.now() - t0 < TIMEOUT_MS) {
|
while (Date.now() - t0 < TIMEOUT_MS) {
|
||||||
await new Promise(r => setTimeout(r, POLL_MS));
|
await new Promise(r => setTimeout(r, POLL_MS));
|
||||||
try {
|
try {
|
||||||
@ -483,15 +573,21 @@
|
|||||||
tr._reconData = {ok: true, downtime_s: dur, uptime: j.uptime};
|
tr._reconData = {ok: true, downtime_s: dur, uptime: j.uptime};
|
||||||
cell.innerHTML = '<span class="text-cyber-green">✓ revenu en ' + dur + 's</span>'
|
cell.innerHTML = '<span class="text-cyber-green">✓ revenu en ' + dur + 's</span>'
|
||||||
+ '<br><span class="text-[10px] text-gray-400" title="' + escapeHTML(j.uptime||'') + '">' + escapeHTML((j.uptime||'').slice(0,60)) + '</span>';
|
+ '<br><span class="text-[10px] text-gray-400" title="' + escapeHTML(j.uptime||'') + '">' + escapeHTML((j.uptime||'').slice(0,60)) + '</span>';
|
||||||
|
termLine('✓', host + ' revenu en ' + dur + 's · ' + (j.uptime||''));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">⏳ '
|
const status = j.tcp22 ? 'TCP/22 OK · SSH KO' : 'pas joignable';
|
||||||
+ (j.tcp22 ? 'TCP/22 OK · SSH KO' : 'pas joignable')
|
const elapsed = Math.round((Date.now()-t0)/1000);
|
||||||
+ ' · ' + Math.round((Date.now()-t0)/1000) + 's</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">⏳ ' + status + ' · ' + elapsed + 's</span>';
|
||||||
} catch(e) { /* ignore, retry */ }
|
if (status !== last) {
|
||||||
|
termLine('⏳', host + ' : ' + status + ' (t+' + elapsed + 's)');
|
||||||
|
last = status;
|
||||||
|
}
|
||||||
|
} catch(e) { /* retry */ }
|
||||||
}
|
}
|
||||||
tr._reconData = {ok: false};
|
tr._reconData = {ok: false};
|
||||||
cell.innerHTML = '<span class="text-cyber-red">✗ timeout 10 min</span>';
|
cell.innerHTML = '<span class="text-cyber-red">✗ timeout 10 min</span>';
|
||||||
|
termLine('✗', host + ' : timeout 10 min sans reconnexion');
|
||||||
}));
|
}));
|
||||||
refreshStepButtons();
|
refreshStepButtons();
|
||||||
});
|
});
|
||||||
@ -504,6 +600,8 @@
|
|||||||
btnPost.disabled = true;
|
btnPost.disabled = true;
|
||||||
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();
|
||||||
|
termSection('post-compare ' + host);
|
||||||
const cell = tr.querySelector('.cell-post');
|
const cell = tr.querySelector('.cell-post');
|
||||||
cell.innerHTML = '<span class="text-cyber-yellow">… compare</span>';
|
cell.innerHTML = '<span class="text-cyber-yellow">… compare</span>';
|
||||||
try {
|
try {
|
||||||
@ -517,12 +615,21 @@
|
|||||||
const dispPort = (rep.ports_disparus||[]).length;
|
const dispPort = (rep.ports_disparus||[]).length;
|
||||||
const appPort = (rep.ports_apparus||[]).length;
|
const appPort = (rep.ports_apparus||[]).length;
|
||||||
const summ = 'svc -' + dispSvc + ' +' + appSvc + ' / port -' + dispPort + ' +' + appPort;
|
const summ = 'svc -' + dispSvc + ' +' + appSvc + ' / port -' + dispPort + ' +' + appPort;
|
||||||
if (st === 'ok') { okCount++; cell.innerHTML = '<span class="text-cyber-green">✓ ' + escapeHTML(summ) + '</span>'; }
|
if (st === 'ok') { okCount++; cell.innerHTML = '<span class="text-cyber-green">✓ ' + escapeHTML(summ) + '</span>'; termLine('✓', summ); }
|
||||||
else if (st === 'warn') { warnCount++; cell.innerHTML = '<span class="text-cyber-yellow" title="' + escapeHTML(j.stdout||'') + '">⚠ ' + escapeHTML(summ) + '</span>'; }
|
else if (st === 'warn') { warnCount++; cell.innerHTML = '<span class="text-cyber-yellow" title="' + escapeHTML(j.stdout||'') + '">⚠ ' + escapeHTML(summ) + '</span>'; termLine('⚠', summ); }
|
||||||
else { koCount++; cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.stdout||j.detail||'') + '">✗ ' + escapeHTML(summ) + '</span>'; }
|
else { koCount++; cell.innerHTML = '<span class="text-cyber-red" title="' + escapeHTML(j.stdout||j.detail||'') + '">✗ ' + escapeHTML(summ) + '</span>'; termLine('✗', summ); }
|
||||||
|
if (rep.services_disparus && rep.services_disparus.length)
|
||||||
|
appendTermHTML(' <span class="t-ko">services disparus :</span> ' + escapeHTML(rep.services_disparus.join(', ')) + '\n');
|
||||||
|
if (rep.services_apparus && rep.services_apparus.length)
|
||||||
|
appendTermHTML(' <span class="t-warn">services apparus :</span> ' + escapeHTML(rep.services_apparus.join(', ')) + '\n');
|
||||||
|
if (rep.ports_disparus && rep.ports_disparus.length)
|
||||||
|
appendTermHTML(' <span class="t-ko">ports disparus :</span> ' + escapeHTML(rep.ports_disparus.join(', ')) + '\n');
|
||||||
|
if (rep.ports_apparus && rep.ports_apparus.length)
|
||||||
|
appendTermHTML(' <span class="t-warn">ports apparus :</span> ' + escapeHTML(rep.ports_apparus.join(', ')) + '\n');
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
koCount++;
|
koCount++;
|
||||||
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
cell.innerHTML = '<span class="text-cyber-red">✗ erreur</span>';
|
||||||
|
termLine('✗', 'exception : ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
summary.innerHTML += ' · Post-cmp : ✓ ' + okCount + ' · ⚠ ' + warnCount + ' · ✗ ' + koCount;
|
summary.innerHTML += ' · Post-cmp : ✓ ' + okCount + ' · ⚠ ' + warnCount + ' · ✗ ' + koCount;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user