patchcenter/app/templates/patching_iexec.html

182 lines
8.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% block title %}Pré-patching — iexec{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-4">
<div>
<h2 class="text-xl font-bold text-cyber-accent">Pré-patching — workflow iexec</h2>
<p class="text-xs text-gray-500 mt-1">
{{ rows|length }} serveur(s) éligible(s).
<span class="text-cyber-yellow">Note : seuls les Linux sont concernés (Windows non géré).</span>
</p>
</div>
<a href="javascript:history.back()" class="btn-sm bg-cyber-border text-cyber-accent px-4 py-2">← Retour</a>
</div>
{# ─── Stepper ─── #}
<div class="flex items-center mb-4 gap-2 text-xs">
<span class="px-3 py-1 rounded bg-cyber-yellow/20 text-cyber-yellow font-bold">1. Vérifications</span>
<span class="text-gray-500"></span>
<span class="px-3 py-1 rounded bg-cyber-border text-gray-500">2. Snapshot</span>
<span class="text-gray-500"></span>
<span class="px-3 py-1 rounded bg-cyber-border text-gray-500">3. Patch yum</span>
</div>
<div class="card p-3 mb-4">
<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>
<div class="flex gap-2">
<button id="btn-run-all" class="btn-primary px-3 py-1 text-xs">Lancer les checks</button>
</div>
</div>
<p class="text-xs text-gray-500 mb-3">
Pour chaque serveur Linux : résolution DNS · connexion SSH ·
joignabilité Satellite (LAN <code>vpdsiasat2</code> / DMZ <code>vpdsiasat1</code>) +
<code>subscription-manager identity</code> + <code>yum repolist enabled</code>.
Toutes les commandes utilisent <code>sudo -n</code>.
</p>
<table class="w-full text-xs">
<thead class="text-cyber-accent border-b border-cyber-border">
<tr>
<th class="text-left p-1">Asset</th>
<th class="text-left p-1">Hostname BDD</th>
<th class="text-left p-1">Env</th>
<th class="text-left p-1">Domaine</th>
<th class="text-left p-1">OS</th>
<th class="text-left p-1">Excludes</th>
<th class="text-left p-1">DNS</th>
<th class="text-left p-1">SSH</th>
<th class="text-left p-1">Satellite</th>
<th class="text-left p-1">Verdict</th>
</tr>
</thead>
<tbody id="check-tbody">
{% for r in rows %}
<tr class="border-b border-cyber-border/30" data-row-id="{{ r.id }}" data-os="{{ r.os or '' }}">
<td class="p-1 font-mono">{{ r.asset_name }}</td>
<td class="p-1 font-mono">{{ r.hostname or '' }}</td>
<td class="p-1">{{ r.environnement or '' }}</td>
<td class="p-1">{{ r.domaine if r.domaine is defined else '' }}</td>
<td class="p-1">{{ r.os or '' }}</td>
<td class="p-1 text-cyber-yellow">{{ r.effective_excludes or '(aucun)' }}</td>
<td class="p-1 cell-dns text-gray-500">·</td>
<td class="p-1 cell-ssh text-gray-500">·</td>
<td class="p-1 cell-sat text-gray-500">·</td>
<td class="p-1 cell-overall text-gray-500">en attente</td>
</tr>
{% else %}
<tr><td colspan="10" class="p-2 text-gray-500">Aucune ligne éligible.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{# ─── Détails par serveur (panneau pliable) ─── #}
<div class="card p-3 mb-4" id="details-card" style="display:none;">
<h3 class="text-sm font-bold text-cyber-accent mb-2">Détails du dernier check</h3>
<pre id="details-pane" class="bg-cyber-bg p-2 text-[11px] whitespace-pre-wrap overflow-x-auto" style="max-height:400px;"></pre>
</div>
<div class="flex justify-between items-center mt-4">
<span id="run-summary" class="text-xs text-gray-400"></span>
<button id="btn-step2" class="btn-sm bg-cyber-green/20 text-cyber-green px-4 py-2 text-xs" disabled>
→ Step 2 (snapshot) — à brancher
</button>
</div>
<script>
(function(){
const btnRun = document.getElementById('btn-run-all');
const btnStep2 = document.getElementById('btn-step2');
const tbody = document.getElementById('check-tbody');
const summary = document.getElementById('run-summary');
const detailsCard = document.getElementById('details-card');
const detailsPane = document.getElementById('details-pane');
function statusBadge(st){
if (st === 'ok') return '<span class="text-cyber-green">✓ OK</span>';
if (st === 'warn')return '<span class="text-cyber-yellow">⚠ WARN</span>';
if (st === 'ko') return '<span class="text-cyber-red">✗ KO</span>';
if (st === 'unsupported') return '<span class="text-gray-400">⊘ N/A</span>';
return '<span class="text-gray-500">' + st + '</span>';
}
function isLinux(osStr){
const s = (osStr || '').toLowerCase();
return s && !s.includes('windows') && s !== 'win';
}
async function checkOne(tr){
const rowId = tr.dataset.rowId;
const osStr = tr.dataset.os || '';
if (!isLinux(osStr)) {
tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported');
tr.querySelector('.cell-ssh').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>';
return {overall: 'unsupported'};
}
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-yellow">…</span>';
try {
const r = await fetch('/patching/iexec/check/' + rowId, {method:'POST'});
const j = await r.json();
if (!j.ok) {
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
return {overall: 'ko'};
}
if (j.overall === 'unsupported') {
tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported');
tr.querySelector('.cell-ssh').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>';
return j;
}
const byName = {};
(j.checks || []).forEach(c => byName[c.name] = c);
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-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._checkData = j;
return j;
} catch(e) {
tr.querySelector('.cell-overall').innerHTML = '<span class="text-cyber-red">err</span>';
return {overall: 'ko'};
}
}
btnRun.addEventListener('click', async () => {
btnRun.disabled = true; btnStep2.disabled = true;
summary.textContent = 'Lancement…';
const trs = Array.from(tbody.querySelectorAll('tr[data-row-id]'));
let okCount = 0, warnCount = 0, koCount = 0, naCount = 0;
for (const tr of trs) {
const r = await checkOne(tr);
if (r.overall === 'ok') okCount++;
else if (r.overall === 'warn') warnCount++;
else if (r.overall === 'unsupported') naCount++;
else koCount++;
}
summary.innerHTML = '✓ ' + okCount + ' OK · ⚠ ' + warnCount + ' warn · ✗ ' + koCount + ' KO · ⊘ ' + naCount + ' N/A';
btnRun.disabled = false;
btnStep2.disabled = (okCount === 0);
});
// Click sur une ligne → afficher les détails
tbody.addEventListener('click', (ev) => {
const tr = ev.target.closest('tr[data-row-id]');
if (!tr || !tr._checkData) return;
detailsCard.style.display = '';
const j = tr._checkData;
let txt = `Hostname: ${j.hostname || ''}\nTarget: ${j.target || ''}\nVerdict: ${j.overall}\n\n`;
(j.checks || []).forEach(c => {
txt += `[${c.status.toUpperCase()}] ${c.label} : ${c.message}\n`;
if (c.details) txt += c.details + '\n';
txt += '---\n';
});
detailsPane.textContent = txt;
});
})();
</script>
{% endblock %}