feat(snapshots): UI simplifiee - dropdown Intervenant unique (defaut user connecte)

Avant: 3 champs (Auteur input + 2 checkboxes 'Mes snapshots' / 'Format PatchCenter') -> trop.

Apres:
- Un seul dropdown 'Intervenant' avec liste dynamique des auteurs detectes dans les
  snapshots PatchCenter charges
- Defaut = user connecte (intervenant_default depuis JWT 'sub')
- Ajoute '(toi)' a cote de l'option qui matche le user connecte
- Si le user connecte n'a pas encore de snapshot, son option apparait quand meme en tete
  avec le tag '(toi, aucun snap)'
- Le filtre 'PatchCenter uniquement' est implicite (toujours actif) -> les snapshots
  SLPM ou manuels n'apparaissent plus du tout
- Les autres users peuvent etre selectionnes pour afficher/supprimer leurs snapshots
  (pas de restriction par compte au-dela de l'authentification)
- updateFilterSummary affiche le filtre actif clairement
This commit is contained in:
Pierre & Lumière 2026-05-07 21:00:28 +02:00
parent c918edb093
commit 46b80474c2

View File

@ -56,10 +56,11 @@
</select>
</div>
<div class="col-span-3">
<label class="text-xs text-gray-500">Auteur (préfixe nom)</label>
<input type="text" id="f-author" value="{{ intervenant_default }}" placeholder="ex: khalid" class="w-full">
<label class="text-xs text-gray-400 mt-1"><input type="checkbox" id="f-only-mine" checked> Mes snapshots uniquement (login = auteur)</label>
<label class="text-xs text-gray-400 mt-1"><input type="checkbox" id="f-only-pc" checked> Snapshots PatchCenter uniquement (exclut SLPM .exe et manuels)</label>
<label class="text-xs text-gray-500">Intervenant</label>
<select id="f-intervenant" class="w-full">
<option value="{{ intervenant_default }}" selected>{{ intervenant_default or '(toi)' }}</option>
</select>
<p class="text-xs text-gray-500 mt-1">Liste alimentée après chargement (auteurs détectés dans les snapshots PatchCenter).</p>
</div>
<div class="col-span-3">
<label class="text-xs text-gray-500">Âge minimum (jours)</label>
@ -111,10 +112,9 @@
<script>
(function() {
const fVc = document.getElementById('f-vcenter');
const fAuthor = document.getElementById('f-author');
const fOnlyMine = document.getElementById('f-only-mine');
const fOnlyPc = document.getElementById('f-only-pc');
const fIntervenant = document.getElementById('f-intervenant');
const fMinAge = document.getElementById('f-min-age');
const intervenantDefault = "{{ intervenant_default|e }}";
const btnRefresh = document.getElementById('btn-refresh');
const btnDelete = document.getElementById('btn-delete');
const status = document.getElementById('status');
@ -137,26 +137,43 @@
}
function applyFilters() {
const author = (fAuthor.value || '').trim().toLowerCase();
const onlyMine = fOnlyMine.checked;
const pcOnly = fOnlyPc.checked;
// Toujours: snapshots PatchCenter uniquement (origin === 'patchcenter')
// + intervenant choisi dans le dropdown
// + age minimum
const intervenant = (fIntervenant.value || '').trim().toLowerCase();
const minAge = parseFloat(fMinAge.value) || 0;
return allSnaps.filter(s => {
// Snapshots PatchCenter uniquement (origin === 'patchcenter')
if (pcOnly && s.origin !== 'patchcenter') return false;
// Mes snapshots uniquement
if (onlyMine) {
if (!author) {
if (!s.author) return false;
} else if (!s.author || s.author.toLowerCase() !== author) {
return false;
}
}
if (s.origin !== 'patchcenter') return false;
if (intervenant && (!s.author || s.author.toLowerCase() !== intervenant)) return false;
if (minAge > 0 && (s.age_days === null || s.age_days < minAge)) return false;
return true;
});
}
function rebuildIntervenantDropdown() {
// Liste des auteurs distincts des snapshots PatchCenter trouvés
const authors = Array.from(new Set(
allSnaps.filter(s => s.origin === 'patchcenter' && s.author).map(s => s.author)
)).sort((a,b) => a.localeCompare(b, 'fr', {sensitivity:'base'}));
// Conserve la sélection courante (ou défaut user connecté)
const current = fIntervenant.value || intervenantDefault;
let opts = '';
// Si user connecté pas dans la liste (= aucun snap encore), on l'ajoute en tête
const haveCurrent = authors.some(a => a.toLowerCase() === current.toLowerCase());
if (intervenantDefault && !authors.some(a => a.toLowerCase() === intervenantDefault.toLowerCase())) {
opts += `<option value="${intervenantDefault}">${intervenantDefault} (toi, aucun snap)</option>`;
}
for (const a of authors) {
const isYou = a.toLowerCase() === intervenantDefault.toLowerCase();
const sel = (a.toLowerCase() === current.toLowerCase()) ? ' selected' : '';
opts += `<option value="${a}"${sel}>${a}${isYou ? ' (toi)' : ''}</option>`;
}
if (!opts) opts = '<option value="">(aucun snap PatchCenter trouvé)</option>';
fIntervenant.innerHTML = opts;
// Re-sélectionne current ou défaut si présent
if (haveCurrent) fIntervenant.value = current;
}
function fmtDateFR(iso) {
// ISO 8601 -> 'jj/mm/aaaa HH:MM' (heure locale du navigateur)
if (!iso) return '';
@ -169,15 +186,12 @@
}
function updateFilterSummary(visibleCount) {
// Affiche le nombre cachés vs total pour debug
const total = allSnaps.length;
const hidden = total - visibleCount;
// Affiche combien sont cachés et la cible
const baseStatus = status.dataset.baseMsg || '';
if (hidden > 0) {
status.innerHTML = baseStatus + ` <span class="text-cyber-yellow">⚠ ${hidden} snapshot(s) caché(s) par les filtres</span> (décoche "Mes snapshots" ou mets âge=0 pour tout voir).`;
} else {
status.innerHTML = baseStatus;
}
const intervenant = (fIntervenant.value || '').trim();
const minAge = parseFloat(fMinAge.value) || 0;
const filterDesc = `intervenant=${intervenant || 'tous'}` + (minAge > 0 ? `, âge≥${minAge}j` : '');
status.innerHTML = baseStatus + ` <span class="text-cyber-accent">→ ${visibleCount} affiché(s) (filtre : ${escapeHTML(filterDesc)})</span>`;
}
function render() {
@ -277,10 +291,12 @@
}
allSnaps = j.snapshots || [];
const errs = (j.errors || []).map(e => `${e.vcenter}: ${e.msg}`).join(' · ');
const baseMsg = `${j.snap_count} snapshot(s) trouvé(s) sur ${j.vcenter_count} vCenter(s).` +
const pcCount = allSnaps.filter(s => s.origin === 'patchcenter').length;
const baseMsg = `${j.snap_count} snapshot(s) trouvé(s) sur ${j.vcenter_count} vCenter(s) (dont ${pcCount} PatchCenter).` +
(errs ? ` <span class="text-cyber-yellow">⚠ ${escapeHTML(errs)}</span>` : '');
status.dataset.baseMsg = baseMsg;
status.innerHTML = baseMsg;
rebuildIntervenantDropdown();
render();
} catch (e) {
status.innerHTML = '<span class="text-cyber-red">Erreur réseau : ' + escapeHTML(String(e)) + '</span>';
@ -289,8 +305,8 @@
}
});
[fAuthor, fMinAge, fOnlyMine, fOnlyPc].forEach(el => el.addEventListener('input', render));
[fOnlyMine, fOnlyPc].forEach(el => el.addEventListener('change', render));
[fIntervenant, fMinAge].forEach(el => el.addEventListener('input', render));
fIntervenant.addEventListener('change', render);
selAll.addEventListener('change', () => {
tbody.querySelectorAll('.row-cb').forEach(cb => {