feat(patching/import): filtre intervenant en dropdown, retire filtre asset texte, ajoute tri asc/desc/none au clic sur entete Asset

This commit is contained in:
Pierre & Lumière 2026-05-04 13:17:23 +02:00
parent 488b5a980b
commit 8b6057aef2

View File

@ -108,8 +108,9 @@
<div id="sheet-table-wrap" class="overflow-x-auto" style="display:none;"> <div id="sheet-table-wrap" class="overflow-x-auto" style="display:none;">
{# Filtres client-side #} {# Filtres client-side #}
<div class="flex gap-2 items-center mb-2 flex-wrap"> <div class="flex gap-2 items-center mb-2 flex-wrap">
<input type="text" id="filter-asset" placeholder="Filtre asset…" class="text-xs px-2 py-1" style="width:160px"> <select id="filter-intervenant" class="text-xs px-2 py-1">
<input type="text" id="filter-intervenant" placeholder="Filtre intervenant…" class="text-xs px-2 py-1" style="width:160px"> <option value="">— Tous intervenants —</option>
</select>
<select id="filter-env" class="text-xs px-2 py-1"> <select id="filter-env" class="text-xs px-2 py-1">
<option value="">— Tous environnements —</option> <option value="">— Tous environnements —</option>
</select> </select>
@ -133,7 +134,9 @@
<thead class="text-cyber-accent border-b border-cyber-border"> <thead class="text-cyber-accent border-b border-cyber-border">
<tr> <tr>
<th class="text-left p-1 w-6"><input type="checkbox" id="select-all-head"></th> <th class="text-left p-1 w-6"><input type="checkbox" id="select-all-head"></th>
<th class="text-left p-1">Asset</th> <th class="text-left p-1 cursor-pointer select-none hover:text-cyber-accent" id="th-asset" title="Cliquer pour trier">
Asset <span id="th-asset-arrow" class="text-[10px] opacity-50"></span>
</th>
<th class="text-left p-1">Env</th> <th class="text-left p-1">Env</th>
<th class="text-left p-1">Domaine</th> <th class="text-left p-1">Domaine</th>
<th class="text-left p-1">OS</th> <th class="text-left p-1">OS</th>
@ -172,13 +175,15 @@
const selCount = document.getElementById('selection-count'); const selCount = document.getElementById('selection-count');
const btnPre = document.getElementById('btn-prepatch'); const btnPre = document.getElementById('btn-prepatch');
const btnPatch = document.getElementById('btn-patch'); const btnPatch = document.getElementById('btn-patch');
const fAsset = document.getElementById('filter-asset');
const fInter = document.getElementById('filter-intervenant'); const fInter = document.getElementById('filter-intervenant');
const fEnv = document.getElementById('filter-env'); const fEnv = document.getElementById('filter-env');
const fReset = document.getElementById('filter-reset'); const fReset = document.getElementById('filter-reset');
const fCount = document.getElementById('filter-count'); const fCount = document.getElementById('filter-count');
const thAsset = document.getElementById('th-asset');
const thAssetArrow = document.getElementById('th-asset-arrow');
let currentRows = []; let currentRows = [];
let sortAsset = 0; // 0 = ordre fichier, 1 = asc, -1 = desc
function escapeHTML(s){ function escapeHTML(s){
if (s === null || s === undefined) return ''; if (s === null || s === undefined) return '';
@ -199,17 +204,13 @@
} }
function applyFilters(){ function applyFilters(){
const fa = (fAsset.value || '').toLowerCase().trim(); const fi = (fInter.value || '').trim();
const fi = (fInter.value || '').toLowerCase().trim();
const fe = (fEnv.value || '').trim(); const fe = (fEnv.value || '').trim();
let visibleCount = 0; let visibleCount = 0;
tbody.querySelectorAll('tr').forEach(tr => { tbody.querySelectorAll('tr').forEach(tr => {
const a = (tr.dataset.asset || '').toLowerCase(); const i = tr.dataset.intervenant || '';
const i = (tr.dataset.intervenant || '').toLowerCase();
const e = tr.dataset.env || ''; const e = tr.dataset.env || '';
const ok = (!fa || a.includes(fa)) const ok = (!fi || i === fi) && (!fe || e === fe);
&& (!fi || i.includes(fi))
&& (!fe || e === fe);
if (ok) { tr.classList.remove('row-hidden'); tr.style.display=''; visibleCount++; } if (ok) { tr.classList.remove('row-hidden'); tr.style.display=''; visibleCount++; }
else { tr.classList.add('row-hidden'); tr.style.display='none'; } else { tr.classList.add('row-hidden'); tr.style.display='none'; }
}); });
@ -217,11 +218,16 @@
refreshSelection(); refreshSelection();
} }
function rebuildEnvOptions(rows){ function rebuildSelectOptions(sel, values, placeholder){
const envs = Array.from(new Set(rows.map(r => r.environnement).filter(x => x))).sort(); const cur = sel.value;
const cur = fEnv.value; const opts = Array.from(new Set(values.filter(x => x))).sort((a,b) => a.localeCompare(b, 'fr', {sensitivity:'base'}));
fEnv.innerHTML = '<option value="">— Tous environnements —</option>' sel.innerHTML = '<option value="">' + placeholder + '</option>'
+ envs.map(e => '<option value="' + escapeHTML(e) + '"' + (cur === e ? ' selected' : '') + '>' + escapeHTML(e) + '</option>').join(''); + opts.map(v => '<option value="' + escapeHTML(v) + '"' + (cur === v ? ' selected' : '') + '>' + escapeHTML(v) + '</option>').join('');
}
function updateSortArrow(){
thAssetArrow.textContent = sortAsset === 1 ? '▲' : (sortAsset === -1 ? '▼' : '↕');
thAssetArrow.classList.toggle('opacity-50', sortAsset === 0);
} }
async function loadSheet(name){ async function loadSheet(name){
@ -237,8 +243,25 @@
empty.style.display='none'; wrap.style.display=''; empty.style.display='none'; wrap.style.display='';
summary.textContent = j.count + ' lignes'; summary.textContent = j.count + ' lignes';
currentRows = j.rows; currentRows = j.rows;
rebuildEnvOptions(currentRows); rebuildSelectOptions(fInter, currentRows.map(r => r.intervenant), '— Tous intervenants —');
tbody.innerHTML = j.rows.map(r => { rebuildSelectOptions(fEnv, currentRows.map(r => r.environnement), '— Tous environnements —');
sortAsset = 0;
updateSortArrow();
renderTable();
}
function renderTable(){
let rows = currentRows.slice();
if (sortAsset !== 0) {
rows.sort((a, b) => {
const av = (a.asset_name || '').toLowerCase();
const bv = (b.asset_name || '').toLowerCase();
if (av < bv) return -sortAsset;
if (av > bv) return sortAsset;
return 0;
});
}
tbody.innerHTML = rows.map(r => {
const linkSrv = r.server_id const linkSrv = r.server_id
? '<a href="/qualys/search?field=hostname&q=' + encodeURIComponent(r.resolved_hostname || r.asset_name) + '" class="text-cyber-blue hover:underline">' + escapeHTML(r.resolved_hostname || r.asset_name) + '</a>' ? '<a href="/qualys/search?field=hostname&q=' + encodeURIComponent(r.resolved_hostname || r.asset_name) + '" class="text-cyber-blue hover:underline">' + escapeHTML(r.resolved_hostname || r.asset_name) + '</a>'
: '<span class="text-cyber-yellow" title="Pas matché en base PatchCenter">' + escapeHTML(r.asset_name || '') + ' ⚠</span>'; : '<span class="text-cyber-yellow" title="Pas matché en base PatchCenter">' + escapeHTML(r.asset_name || '') + ' ⚠</span>';
@ -280,11 +303,17 @@
selAll.addEventListener('change', () => toggleAll(selAll.checked)); selAll.addEventListener('change', () => toggleAll(selAll.checked));
selAllHead.addEventListener('change', () => toggleAll(selAllHead.checked)); selAllHead.addEventListener('change', () => toggleAll(selAllHead.checked));
[fAsset, fInter, fEnv].forEach(el => el.addEventListener('input', applyFilters)); [fInter, fEnv].forEach(el => el.addEventListener('change', applyFilters));
fEnv.addEventListener('change', applyFilters);
fReset.addEventListener('click', () => { fReset.addEventListener('click', () => {
fAsset.value = ''; fInter.value = ''; fEnv.value = ''; fInter.value = ''; fEnv.value = '';
applyFilters(); sortAsset = 0;
updateSortArrow();
renderTable();
});
thAsset.addEventListener('click', () => {
sortAsset = sortAsset === 1 ? -1 : (sortAsset === -1 ? 0 : 1);
updateSortArrow();
renderTable();
}); });
btnPre.addEventListener('click', () => { btnPre.addEventListener('click', () => {