feat(dashboard): sélecteur de plage temporelle sur /campaigns

Avant : toutes les vues de campagnes étaient fixes à 7 jours.
Après : sélecteur 1j / 7j (défaut) / 14j / 30j / 90j en haut à droite.

- Ajout du paramètre ?days= (1–90, défaut 7) à :
    GET /api/campaigns
    GET /api/campaigns/graph
    GET /api/campaigns/scatter
    GET /api/campaigns/{cid}
- Le sélecteur recharge simultanément les 3 vues (cartes, scatter, graphe)
  et le panneau de détail avec la même fenêtre temporelle
- Le compteur de campagnes indique la plage active : (4 campagnes — 30j)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-10 13:24:08 +02:00
parent 9548b1782d
commit 79dbb23d6f
2 changed files with 54 additions and 24 deletions

View File

@ -40,6 +40,14 @@
Campagnes de bots
</h1>
<div class="ml-auto flex items-center gap-3">
<!-- ── Sélecteur de plage temporelle ── -->
<div class="flex items-center gap-1 bg-gray-800/60 rounded-lg p-1" id="days-selector">
<button onclick="setDays(1)" data-days="1" class="days-btn px-2 py-1 rounded text-xs text-gray-400 hover:text-white hover:bg-gray-700 transition-colors">1j</button>
<button onclick="setDays(7)" data-days="7" class="days-btn px-2 py-1 rounded text-xs text-white bg-purple-600">7j</button>
<button onclick="setDays(14)" data-days="14" class="days-btn px-2 py-1 rounded text-xs text-gray-400 hover:text-white hover:bg-gray-700 transition-colors">14j</button>
<button onclick="setDays(30)" data-days="30" class="days-btn px-2 py-1 rounded text-xs text-gray-400 hover:text-white hover:bg-gray-700 transition-colors">30j</button>
<button onclick="setDays(90)" data-days="90" class="days-btn px-2 py-1 rounded text-xs text-gray-400 hover:text-white hover:bg-gray-700 transition-colors">90j</button>
</div>
<div class="text-center px-3">
<div class="text-2xl font-bold text-purple-400" id="kpi-total"></div>
<div class="text-[10px] text-gray-500 uppercase tracking-wider">Campagnes</div>
@ -238,13 +246,25 @@ function fmtCountry(cc) {
* ════════════════════════════════════════════════════════════════════════════ */
let _campaigns = [], _scatterData = [], _graphData = {nodes:[],edges:[]};
let _scatterChart = null, _radarChart = null, _timelineChart = null;
let _currentDays = 7;
function setDays(d) {
_currentDays = d;
document.querySelectorAll('.days-btn').forEach(b => {
const active = parseInt(b.dataset.days) === d;
b.className = `days-btn px-2 py-1 rounded text-xs transition-colors ${active ? 'text-white bg-purple-600' : 'text-gray-400 hover:text-white hover:bg-gray-700'}`;
});
closeDetail();
loadAll();
}
async function loadAll() {
document.getElementById('camp-count').textContent = '(chargement…)';
try {
const [campResp, scatterResp, graphResp] = await Promise.all([
fetch('/api/campaigns').then(r=>r.json()),
fetch('/api/campaigns/scatter').then(r=>r.json()),
fetch('/api/campaigns/graph').then(r=>r.json()),
fetch(`/api/campaigns?days=${_currentDays}`).then(r=>r.json()),
fetch(`/api/campaigns/scatter?days=${_currentDays}`).then(r=>r.json()),
fetch(`/api/campaigns/graph?days=${_currentDays}`).then(r=>r.json()),
]);
_campaigns = campResp.campaigns || [];
_scatterData = scatterResp.data || [];
@ -256,13 +276,15 @@ async function loadAll() {
document.getElementById('kpi-total').textContent = _campaigns.length;
document.getElementById('kpi-ips').textContent = totalIPs.toLocaleString();
document.getElementById('kpi-detections').textContent = totalDet.toLocaleString();
document.getElementById('camp-count').textContent = `(${_campaigns.length} actives)`;
const label = _currentDays === 1 ? '24h' : `${_currentDays}j`;
document.getElementById('camp-count').textContent = `(${_campaigns.length} campagne${_campaigns.length!==1?'s':''}${label})`;
renderCampGrid();
renderScatter();
renderGraph();
} catch(e) {
console.error('Campaign load error:', e);
document.getElementById('camp-count').textContent = '(erreur)';
}
}
@ -511,7 +533,7 @@ async function selectCampaign(cid) {
document.getElementById('detail-link').href = `/cluster/${cid}`;
try {
const resp = await fetch(`/api/campaigns/${cid}`);
const resp = await fetch(`/api/campaigns/${cid}?days=${_currentDays}`);
const data = await resp.json();
const p = data.profile || {};
const members = data.members || [];