Compare commits

...

2 Commits

Author SHA1 Message Date
CyberMind
9b8144073f
Merge pull request #580 from CyberMind-FR/fix/575-eye-graph-remove-hide-all-ip-nodes-count
Some checks are pending
License Headers / check (push) Waiting to run
Eye graph: hide IP nodes + clients list top-5 (#575)
2026-06-14 14:42:57 +02:00
2f04b0fa84 fix(toolbox): eye graph hides IP nodes + clients list capped to 5 (closes #575)
- social.js: drop IP-only tracker nodes (v4/v6) from the eye force-graph —
  no more domain+IP double bubbles; remaining nodes labelled country flag +
  domain name.
- www/toolbox index.html #clients: show top 5 only (+N more note).
secubox-toolbox 2.6.28.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:42:07 +02:00
3 changed files with 24 additions and 2 deletions

View File

@ -1,3 +1,14 @@
secubox-toolbox (2.6.28-1~bookworm1) bookworm; urgency=medium
* Eye graph: hide all IP nodes + cap clients list (#575).
- social.js: drop every IP-only tracker node (IPv4/IPv6) from the
eye force-graph — no more domain+IP double bubbles for one tracker.
Remaining tracker nodes are labelled country-flag + domain name.
- www/toolbox index.html #clients tab: show the top 5 clients only
("+ N autres" note when more).
-- Gerald KERMA <devel@cybermind.fr> Sun, 14 Jun 2026 12:30:00 +0200
secubox-toolbox (2.6.27-1~bookworm1) bookworm; urgency=medium secubox-toolbox (2.6.27-1~bookworm1) bookworm; urgency=medium
* webext popup: protection stats + live filter toggles (#574); webext * webext popup: protection stats + live filter toggles (#574); webext

View File

@ -243,8 +243,9 @@ async function loadClients() {
const rows = (d && d.clients) ? d.clients : (Array.isArray(d) ? d : null); const rows = (d && d.clients) ? d.clients : (Array.isArray(d) ? d : null);
if (!rows) { el.innerHTML = `<div class="empty">${(d && d.__error) || 'no data'}</div>`; return; } if (!rows) { el.innerHTML = `<div class="empty">${(d && d.__error) || 'no data'}</div>`; return; }
if (!rows.length) { el.innerHTML = '<div class="empty">no clients</div>'; return; } if (!rows.length) { el.innerHTML = '<div class="empty">no clients</div>'; return; }
const shown = rows.slice(0, 5); // #575 — cap the list to top 5
let html = '<table><thead><tr><th>MAC (hash)</th><th>IP</th><th>state</th><th>niveau</th><th>score</th><th>last</th><th>Actions</th></tr></thead><tbody>'; let html = '<table><thead><tr><th>MAC (hash)</th><th>IP</th><th>state</th><th>niveau</th><th>score</th><th>last</th><th>Actions</th></tr></thead><tbody>';
for (const c of rows) { for (const c of shown) {
const ago = c.last_seen ? Math.round((Date.now()/1000 - c.last_seen) / 60) + 'm' : '—'; const ago = c.last_seen ? Math.round((Date.now()/1000 - c.last_seen) / 60) + 'm' : '—';
html += `<tr> html += `<tr>
<td><code>${c.mac_hash}</code></td> <td><code>${c.mac_hash}</code></td>
@ -265,6 +266,9 @@ async function loadClients() {
</tr>`; </tr>`;
} }
html += '</tbody></table>'; html += '</tbody></table>';
if (rows.length > shown.length) {
html += `<div class="empty">+ ${rows.length - shown.length} autres clients (top 5 affichés)</div>`;
}
el.innerHTML = html; el.innerHTML = html;
} }

View File

@ -155,6 +155,11 @@
// the densest tracker clusters become the visible "hot spots". // the densest tracker clusters become the visible "hot spots".
const EYE_ID = 'eye:device'; const EYE_ID = 'eye:device';
// #575 — IPs are noise in this view (and produce a 2nd bubble next to
// the domain for the same metric). Hide every IP-only tracker node ;
// keep domains, labelled by country flag + name (never the IP).
const isIp = (s) => /^\d{1,3}(\.\d{1,3}){3}$/.test(s) || (s || '').includes(':');
// Build d3 dataset: sites are union of all node.sites + tracker nodes themselves. // Build d3 dataset: sites are union of all node.sites + tracker nodes themselves.
const siteSet = new Set(); const siteSet = new Set();
for (const n of graph.nodes) for (const s of (n.sites || [])) siteSet.add(s); for (const n of graph.nodes) for (const s of (n.sites || [])) siteSet.add(s);
@ -169,10 +174,11 @@
nodes.push({ id: 'site:' + s, label: s, kind: 'site' }); nodes.push({ id: 'site:' + s, label: s, kind: 'site' });
} }
for (const n of graph.nodes) { for (const n of graph.nodes) {
if (isIp(n.domain)) continue; // #575 — hide IP-only nodes
idx.set('tracker:' + n.domain, nodes.length); idx.set('tracker:' + n.domain, nodes.length);
nodes.push({ nodes.push({
id: 'tracker:' + n.domain, id: 'tracker:' + n.domain,
label: n.domain, label: (n.country_flag ? n.country_flag + ' ' : '') + n.domain,
kind: 'tracker', kind: 'tracker',
hits: n.hits, hits: n.hits,
sites: n.sites, sites: n.sites,
@ -194,6 +200,7 @@
links.push({ source: EYE_ID, target: 'site:' + s, reuse: 1, spoke: true }); links.push({ source: EYE_ID, target: 'site:' + s, reuse: 1, spoke: true });
} }
for (const n of graph.nodes) { for (const n of graph.nodes) {
if (isIp(n.domain)) continue; // #575 — no links to hidden IP nodes
const trackerKey = 'tracker:' + n.domain; const trackerKey = 'tracker:' + n.domain;
for (const s of (n.sites || [])) { for (const s of (n.sites || [])) {
links.push({ links.push({