secubox-openwrt/package/secubox/luci-app-secubox-p2p/htdocs/luci-static/resources/view/secubox-p2p/threat-hub.js
CyberMind-FR 1bbd345cee refactor(luci): Mass KissTheme UI rework across all LuCI apps
Convert 90+ LuCI view files from legacy cbi-button-* classes to
KissTheme kiss-btn-* classes for consistent dark theme styling.

Pattern conversions applied:
- cbi-button-positive → kiss-btn-green
- cbi-button-negative/remove → kiss-btn-red
- cbi-button-apply → kiss-btn-cyan
- cbi-button-action → kiss-btn-blue
- cbi-button (plain) → kiss-btn

Also replaced hardcoded colors (#080, #c00, #888, etc.) with
CSS variables (--kiss-green, --kiss-red, --kiss-muted, etc.)
for proper dark theme compatibility.

Apps updated include: ai-gateway, auth-guardian, bandwidth-manager,
cloner, config-advisor, crowdsec-dashboard, dns-provider, exposure,
glances, haproxy, hexojs, iot-guard, jellyfin, ksm-manager,
mac-guardian, magicmirror2, master-link, meshname-dns, metablogizer,
metabolizer, mqtt-bridge, netdata-dashboard, picobrew, routes-status,
secubox-admin, secubox-mirror, secubox-p2p, secubox-security-threats,
service-registry, simplex, streamlit, system-hub, tor-shield,
traffic-shaper, vhost-manager, vortex-dns, vortex-firewall,
webradio, wireguard-dashboard, zigbee2mqtt, zkp, and more.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 11:09:34 +01:00

407 lines
13 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require dom';
'require poll';
'require request';
'require secubox/kiss-theme';
var API_BASE = window.location.protocol + '//' + window.location.hostname + ':7331/api/threat-intel';
function fetchJSON(endpoint) {
return request.get(API_BASE + '/' + endpoint, { timeout: 10000 })
.then(function(res) {
try { return res.json(); }
catch(e) { return null; }
})
.catch(function() { return null; });
}
function postJSON(endpoint) {
return request.post(API_BASE + '/' + endpoint, null, { timeout: 15000 })
.then(function(res) {
try { return res.json(); }
catch(e) { return null; }
})
.catch(function() { return null; });
}
function timeAgo(ts) {
if (!ts || ts === 0) return 'Never';
var now = Math.floor(Date.now() / 1000);
var diff = now - ts;
if (diff < 60) return diff + 's ago';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
}
function severityBadge(severity) {
var colors = {
critical: 'red',
high: 'orange',
medium: 'purple',
low: 'blue'
};
var colorType = colors[severity] || 'muted';
return KissTheme.badge(severity || 'unknown', colorType);
}
function trustBadge(trust) {
var colors = {
direct: 'green',
transitive: 'orange',
unknown: 'muted',
self: 'blue'
};
var icons = {
direct: '\u2714',
transitive: '\u2194',
unknown: '?',
self: '\u2605'
};
var colorType = colors[trust] || 'muted';
return KissTheme.badge((icons[trust] || '') + ' ' + (trust || 'unknown'), colorType);
}
return view.extend({
status: null,
iocs: [],
peers: [],
load: function() {
return Promise.all([
fetchJSON('status'),
fetchJSON('iocs'),
fetchJSON('peers')
]);
},
render: function(data) {
this.status = data[0] || {};
this.iocs = data[1] || [];
this.peers = data[2] || [];
var self = this;
poll.add(function() {
return Promise.all([
fetchJSON('status'),
fetchJSON('iocs'),
fetchJSON('peers')
]).then(function(fresh) {
self.status = fresh[0] || self.status;
self.iocs = fresh[1] || self.iocs;
self.peers = fresh[2] || self.peers;
self.updateCards();
self.updatePeerTable();
self.updateIOCTable();
});
}, 30);
var content = [
this.renderHeader(),
this.renderSummaryCards(),
this.renderActions(),
this.renderPeerTable(),
this.renderIOCTable()
];
return KissTheme.wrap(content, 'admin/secubox/p2p/threat-hub');
},
renderHeader: function() {
var enabled = this.status.enabled !== false;
return E('div', { 'style': 'margin-bottom: 24px;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, 'Threat Intelligence Hub'),
KissTheme.badge(enabled ? 'Active' : 'Disabled', enabled ? 'green' : 'red'),
this.status.auto_apply ? KissTheme.badge('Auto-Apply ON', 'blue') : null
]),
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' },
'Decentralized IOC sharing across mesh nodes via CrowdSec + mitmproxy')
]);
},
renderStats: function() {
var s = this.status;
var c = KissTheme.colors;
return [
KissTheme.stat(s.local_iocs || 0, 'Local IOCs Shared', c.blue),
KissTheme.stat(s.received_iocs || 0, 'Received from Mesh', c.orange),
KissTheme.stat(s.applied_iocs || 0, 'Applied to Firewall', c.green),
KissTheme.stat(s.peer_contributors || 0, 'Peer Contributors', c.purple)
];
},
renderSummaryCards: function() {
var s = this.status;
var c = KissTheme.colors;
var cards = [
{ id: 'card-local', label: 'Local IOCs Shared', value: s.local_iocs || 0, color: c.blue },
{ id: 'card-received', label: 'Received from Mesh', value: s.received_iocs || 0, color: c.orange },
{ id: 'card-applied', label: 'Applied to Firewall', value: s.applied_iocs || 0, color: c.green },
{ id: 'card-peers', label: 'Peer Contributors', value: s.peer_contributors || 0, color: c.purple },
{ id: 'card-chain', label: 'Chain Blocks', value: s.chain_threat_blocks || 0, color: c.cyan }
];
return E('div', {
'id': 'summary-cards',
'class': 'kiss-grid kiss-grid-4',
'style': 'margin-bottom: 24px;'
}, cards.map(function(card) {
return E('div', {
'id': card.id,
'style': 'background: var(--kiss-bg2); border: 1px solid var(--kiss-line); border-radius: 12px; ' +
'padding: 16px; text-align: center; border-left: 4px solid ' + card.color + ';'
}, [
E('div', {
'class': 'card-value',
'style': 'font-size: 32px; font-weight: bold; color: ' + card.color + ';'
}, String(card.value)),
E('div', { 'style': 'font-size: 12px; color: var(--kiss-muted); margin-top: 4px;' }, card.label)
]);
}));
},
renderActions: function() {
var self = this;
return KissTheme.card('Actions',
E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'click': function() {
this.disabled = true;
this.textContent = 'Publishing...';
var btn = this;
postJSON('publish').then(function(res) {
btn.disabled = false;
btn.textContent = 'Publish Now';
if (res && res.success)
ui.addNotification(null, E('p', 'Published ' + (res.published || 0) + ' IOCs to chain'), 'info');
else
ui.addNotification(null, E('p', 'Publish failed'), 'error');
});
}
}, 'Publish Now'),
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': function() {
this.disabled = true;
this.textContent = 'Applying...';
var btn = this;
postJSON('apply').then(function(res) {
btn.disabled = false;
btn.textContent = 'Apply Pending';
if (res && res.success)
ui.addNotification(null, E('p', 'Applied ' + (res.applied || 0) + ' IOCs, skipped ' + (res.skipped || 0)), 'info');
else
ui.addNotification(null, E('p', 'Apply failed'), 'error');
});
}
}, 'Apply Pending')
])
);
},
renderPeerTable: function() {
var peers = this.peers || [];
var rows = peers.map(function(p) {
return E('tr', {}, [
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 13px;' },
(p.node || '').substring(0, 12) + '...'),
E('td', { 'style': 'padding: 10px 12px;' },
trustBadge(p.trust)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
String(p.ioc_count || 0)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
String(p.applied_count || 0)),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
timeAgo(p.last_seen))
]);
});
var tableContent;
if (rows.length === 0) {
tableContent = E('p', { 'style': 'color: var(--kiss-muted); text-align: center; padding: 24px;' },
'No peer contributions yet. IOCs from mesh nodes will appear here after sync.');
} else {
tableContent = E('table', { 'id': 'peer-table', 'class': 'kiss-table' }, [
E('thead', {}, E('tr', {}, [
E('th', { 'style': 'padding: 10px 12px;' }, 'Node'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Trust'),
E('th', { 'style': 'padding: 10px 12px; text-align: center;' }, 'IOCs'),
E('th', { 'style': 'padding: 10px 12px; text-align: center;' }, 'Applied'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Last Seen')
])),
E('tbody', {}, rows)
]);
}
return KissTheme.card(
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', {}, 'Peer Contributions'),
KissTheme.badge(peers.length + ' peers', 'purple')
]),
tableContent
);
},
renderIOCTable: function() {
var iocs = this.iocs || [];
var c = KissTheme.colors;
var rows = iocs.slice(0, 50).map(function(ioc) {
return E('tr', {}, [
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 13px;' },
ioc.ip || '-'),
E('td', { 'style': 'padding: 10px 12px;' },
severityBadge(ioc.severity)),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
ioc.source || '-'),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
ioc.scenario || '-'),
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 11px; color: var(--kiss-muted);' },
(ioc.node || '').substring(0, 12)),
E('td', { 'style': 'padding: 10px 12px;' },
trustBadge(ioc.trust)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
ioc.applied ?
E('span', { 'style': 'color: ' + c.green + '; font-weight: bold;' }, '\u2714') :
E('span', { 'style': 'color: var(--kiss-muted);' }, '\u2013')),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
timeAgo(ioc.ts))
]);
});
var tableContent;
if (rows.length === 0) {
tableContent = E('p', { 'style': 'color: var(--kiss-muted); text-align: center; padding: 24px;' },
'No IOCs received from mesh yet.');
} else {
tableContent = E('div', {}, [
E('p', { 'style': 'color: var(--kiss-muted); font-size: 12px; margin: 0 0 12px 0;' },
'Showing up to 50 most recent. Total: ' + (this.iocs || []).length),
E('div', { 'style': 'overflow-x: auto;' }, [
E('table', { 'id': 'ioc-table', 'class': 'kiss-table' }, [
E('thead', {}, E('tr', {}, [
E('th', { 'style': 'padding: 10px 12px;' }, 'IP'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Severity'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Source'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Scenario'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Origin'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Trust'),
E('th', { 'style': 'padding: 10px 12px; text-align: center;' }, 'Applied'),
E('th', { 'style': 'padding: 10px 12px;' }, 'Age')
])),
E('tbody', {}, rows)
])
])
]);
}
return KissTheme.card(
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', {}, 'Received IOCs'),
KissTheme.badge(iocs.length + ' total', 'orange')
]),
tableContent
);
},
updateCards: function() {
var s = this.status || {};
var mapping = {
'card-local': s.local_iocs || 0,
'card-received': s.received_iocs || 0,
'card-applied': s.applied_iocs || 0,
'card-peers': s.peer_contributors || 0,
'card-chain': s.chain_threat_blocks || 0
};
Object.keys(mapping).forEach(function(id) {
var card = document.getElementById(id);
if (card) {
var valEl = card.querySelector('.card-value');
if (valEl) valEl.textContent = String(mapping[id]);
}
});
},
updatePeerTable: function() {
var table = document.getElementById('peer-table');
if (!table) return;
var tbody = table.querySelector('tbody');
if (!tbody) return;
var peers = this.peers || [];
dom.content(tbody, peers.length === 0 ?
E('tr', {}, [
E('td', { 'colspan': '5', 'style': 'padding: 24px; text-align: center; color: var(--kiss-muted);' },
'No peer contributions yet.')
]) :
peers.map(function(p) {
return E('tr', {}, [
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 13px;' },
(p.node || '').substring(0, 12) + '...'),
E('td', { 'style': 'padding: 10px 12px;' },
trustBadge(p.trust)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
String(p.ioc_count || 0)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
String(p.applied_count || 0)),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
timeAgo(p.last_seen))
]);
})
);
},
updateIOCTable: function() {
var table = document.getElementById('ioc-table');
if (!table) return;
var tbody = table.querySelector('tbody');
if (!tbody) return;
var iocs = (this.iocs || []).slice(0, 50);
var c = KissTheme.colors;
var countEl = table.parentNode.parentNode.querySelector('p');
if (countEl) countEl.textContent = 'Showing up to 50 most recent. Total: ' + (this.iocs || []).length;
dom.content(tbody, iocs.length === 0 ?
E('tr', {}, [
E('td', { 'colspan': '8', 'style': 'padding: 24px; text-align: center; color: var(--kiss-muted);' },
'No IOCs received from mesh yet.')
]) :
iocs.map(function(ioc) {
return E('tr', {}, [
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 13px;' },
ioc.ip || '-'),
E('td', { 'style': 'padding: 10px 12px;' },
severityBadge(ioc.severity)),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
ioc.source || '-'),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
ioc.scenario || '-'),
E('td', { 'style': 'padding: 10px 12px; font-family: monospace; font-size: 11px; color: var(--kiss-muted);' },
(ioc.node || '').substring(0, 12)),
E('td', { 'style': 'padding: 10px 12px;' },
trustBadge(ioc.trust)),
E('td', { 'style': 'padding: 10px 12px; text-align: center;' },
ioc.applied ?
E('span', { 'style': 'color: ' + c.green + '; font-weight: bold;' }, '\u2714') :
E('span', { 'style': 'color: var(--kiss-muted);' }, '\u2013')),
E('td', { 'style': 'padding: 10px 12px; color: var(--kiss-muted); font-size: 12px;' },
timeAgo(ioc.ts))
]);
})
);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});