secubox-openwrt/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/view/ai-insights/dashboard.js
CyberMind-FR 040b69ad1d feat(ai-insights): Add CVE feed panel to dashboard
- Add get_cve_feed RPCD method fetching from NVD API
- Add CVE feed panel showing recent vulnerabilities with CVSS scores
- Cache CVE feed for 30 minutes to reduce API calls
- Link CVE IDs to NVD detail pages
- Color-code severity (critical/high/medium/low)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 09:43:41 +01:00

301 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require ai-insights.api as api';
'require secubox/kiss-theme';
/**
* AI Insights Dashboard - v1.0.0
* Unified view across all SecuBox AI agents
*/
return view.extend({
cveCache: [],
load: function() {
var self = this;
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = L.resource('ai-insights/dashboard.css');
document.head.appendChild(link);
return Promise.all([
api.getOverview().catch(function() { return {}; }),
api.getCVEFeed(10).catch(function() { return { cves: [] }; })
]).then(function(results) {
self.cveCache = (results[1] || {}).cves || [];
return results[0];
});
},
render: function(data) {
var self = this;
var s = data.status || {};
var p = data.posture || {};
var alerts = data.alerts || [];
var agents = s.agents || {};
var view = E('div', { 'class': 'ai-view' }, [
// Header
E('div', { 'class': 'ai-header' }, [
E('div', { 'class': 'ai-title' }, 'AI Security Insights'),
E('div', { 'class': 'ai-status' }, [
E('span', { 'class': 'ai-dot ' + (s.localai === 'online' ? 'online' : 'offline') }),
'LocalAI: ' + (s.localai || 'offline')
])
]),
// Posture Score (hero)
E('div', { 'class': 'ai-posture-card ' + api.getPostureColor(p.score || 0), 'id': 'ai-posture' }, [
E('div', { 'class': 'ai-posture-score' }, [
E('div', { 'class': 'ai-score-value' }, String(p.score || 0)),
E('div', { 'class': 'ai-score-label' }, 'Security Score')
]),
E('div', { 'class': 'ai-posture-info' }, [
E('div', { 'class': 'ai-posture-label' }, api.getPostureLabel(p.score || 0)),
E('div', { 'class': 'ai-posture-factors' }, p.factors || 'Calculating...')
])
]),
// Stats row
E('div', { 'class': 'ai-stats', 'id': 'ai-stats' }, this.renderStats(s)),
// Agents grid
E('div', { 'class': 'ai-agents-grid', 'id': 'ai-agents' }, this.renderAgents(agents)),
// Actions card
E('div', { 'class': 'ai-card' }, [
E('div', { 'class': 'ai-card-header' }, 'Actions'),
E('div', { 'class': 'ai-card-body' }, this.renderActions())
]),
// Alerts card
E('div', { 'class': 'ai-card' }, [
E('div', { 'class': 'ai-card-header' }, [
'Recent Activity',
E('span', { 'class': 'ai-badge' }, String(alerts.length))
]),
E('div', { 'class': 'ai-card-body', 'id': 'ai-alerts' }, this.renderAlerts(alerts))
]),
// CVE Feed card
E('div', { 'class': 'ai-card' }, [
E('div', { 'class': 'ai-card-header' }, [
' CVE Feed (Recent)',
E('span', { 'class': 'ai-badge warning' }, String(this.cveCache.length))
]),
E('div', { 'class': 'ai-card-body', 'id': 'ai-cves' }, this.renderCVEs(this.cveCache))
])
]);
poll.add(L.bind(this.pollData, this), 15);
return KissTheme.wrap([view], 'admin/secubox/ai/insights');
},
renderStats: function(s) {
var agents = s.agents || {};
var online = Object.values(agents).filter(function(a) { return a.status === 'online'; }).length;
var totalAlerts = Object.values(agents).reduce(function(sum, a) { return sum + (a.alerts || 0); }, 0);
var statItems = [
{ label: 'Agents Online', value: online + '/4', type: online === 4 ? 'success' : online > 0 ? 'warning' : 'danger' },
{ label: 'LocalAI', value: s.localai === 'online' ? 'OK' : 'OFF', type: s.localai === 'online' ? 'success' : 'danger' },
{ label: 'Pending', value: totalAlerts, type: totalAlerts > 10 ? 'danger' : totalAlerts > 0 ? 'warning' : 'success' },
{ label: 'Memories', value: s.memories || 0, type: '' }
];
return statItems.map(function(st) {
return E('div', { 'class': 'ai-stat ' + st.type }, [
E('div', { 'class': 'ai-stat-value' }, String(st.value)),
E('div', { 'class': 'ai-stat-label' }, st.label)
]);
});
},
renderAgents: function(agents) {
var agentList = ['threat_analyst', 'dns_guard', 'network_anomaly', 'cve_triage'];
return agentList.map(function(id) {
var agent = agents[id] || {};
var isOnline = agent.status === 'online';
return E('div', { 'class': 'ai-agent-card ' + (isOnline ? 'online' : 'offline') }, [
E('div', { 'class': 'ai-agent-icon' }, api.getAgentIcon(id)),
E('div', { 'class': 'ai-agent-info' }, [
E('div', { 'class': 'ai-agent-name' }, api.getAgentName(id)),
E('div', { 'class': 'ai-agent-status' }, [
E('span', { 'class': 'ai-dot ' + (isOnline ? 'online' : 'offline') }),
isOnline ? 'Running' : 'Stopped'
])
]),
E('div', { 'class': 'ai-agent-alerts' }, [
E('span', { 'class': 'ai-badge ' + (agent.alerts > 0 ? 'warning' : '') }, String(agent.alerts || 0)),
E('span', {}, ' alerts')
])
]);
});
},
renderActions: function() {
var self = this;
return E('div', { 'class': 'ai-actions' }, [
E('button', {
'class': 'ai-btn ai-btn-primary',
'click': function() { self.runAllAgents(); }
}, 'Run All Agents'),
E('button', {
'class': 'ai-btn ai-btn-secondary',
'click': function() { self.getAIAnalysis(); }
}, 'AI Analysis'),
E('button', {
'class': 'ai-btn ai-btn-info',
'click': function() { self.showTimeline(); }
}, 'View Timeline'),
E('a', {
'class': 'ai-btn ai-btn-outline',
'href': L.url('admin/secubox/ai/localrecall')
}, 'LocalRecall')
]);
},
renderAlerts: function(alerts) {
if (!alerts || !alerts.length) {
return E('div', { 'class': 'ai-empty' }, 'No recent activity from AI agents');
}
return E('div', { 'class': 'ai-alerts-list' }, alerts.slice(0, 15).map(function(alert) {
var data = alert.data || {};
var source = alert.source || 'unknown';
var type = alert.type || 'alert';
return E('div', { 'class': 'ai-alert-item ' + type }, [
E('div', { 'class': 'ai-alert-icon' }, api.getAgentIcon(source)),
E('div', { 'class': 'ai-alert-content' }, [
E('div', { 'class': 'ai-alert-source' }, api.getAgentName(source)),
E('div', { 'class': 'ai-alert-message' }, data.message || data.type || type)
]),
E('div', { 'class': 'ai-alert-time' }, api.formatRelativeTime(data.timestamp))
]);
}));
},
renderCVEs: function(cves) {
if (!cves || !cves.length) {
return E('div', { 'class': 'ai-empty' }, 'Loading CVE feed...');
}
return E('div', { 'class': 'ai-cve-list' }, cves.slice(0, 10).map(function(cve) {
var score = cve.score || 0;
var severity = score >= 9.0 ? 'critical' : score >= 7.0 ? 'high' : score >= 4.0 ? 'medium' : 'low';
var severityClass = score >= 9.0 ? 'danger' : score >= 7.0 ? 'warning' : score >= 4.0 ? 'caution' : 'success';
return E('div', { 'class': 'ai-cve-item' }, [
E('div', { 'class': 'ai-cve-score ' + severityClass }, [
E('span', { 'class': 'score-value' }, score.toFixed(1)),
E('span', { 'class': 'score-label' }, severity.toUpperCase())
]),
E('div', { 'class': 'ai-cve-content' }, [
E('div', { 'class': 'ai-cve-id' }, [
E('a', { 'href': 'https://nvd.nist.gov/vuln/detail/' + cve.id, 'target': '_blank' }, cve.id)
]),
E('div', { 'class': 'ai-cve-desc' }, (cve.description || '').substring(0, 120) + '...')
]),
E('div', { 'class': 'ai-cve-date' }, cve.published || '')
]);
}));
},
runAllAgents: function() {
ui.showModal('Running Agents', [
E('p', { 'class': 'spinning' }, 'Starting all AI agents...')
]);
api.runAll().then(function(result) {
ui.hideModal();
var started = Object.values(result).filter(function(s) { return s === 'started'; }).length;
ui.addNotification(null, E('p', {}, 'Started ' + started + ' agents'), 'success');
}).catch(function() {
ui.hideModal();
ui.addNotification(null, E('p', {}, 'Failed to start agents'), 'error');
});
},
getAIAnalysis: function() {
ui.showModal('AI Analysis', [
E('p', { 'class': 'spinning' }, 'Generating security analysis (may take up to 60s)...')
]);
api.analyze().then(function(result) {
ui.hideModal();
if (result.analysis) {
ui.showModal('Security Analysis', [
E('div', { 'style': 'white-space: pre-wrap; max-height: 400px; overflow-y: auto;' }, result.analysis),
E('div', { 'class': 'right' }, E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Close'))
]);
} else {
ui.addNotification(null, E('p', {}, 'Error: ' + (result.error || 'Analysis failed')), 'error');
}
}).catch(function() {
ui.hideModal();
ui.addNotification(null, E('p', {}, 'Analysis failed'), 'error');
});
},
showTimeline: function() {
api.getTimeline(24).then(function(result) {
var timeline = result.timeline || [];
ui.showModal('Security Timeline (24h)', [
E('div', { 'style': 'max-height: 400px; overflow-y: auto;' },
timeline.length ? E('div', { 'class': 'ai-timeline' }, timeline.map(function(e) {
return E('div', { 'class': 'ai-timeline-item' }, [
E('div', { 'class': 'ai-timeline-time' }, e.time),
E('div', { 'class': 'ai-timeline-source' }, e.source),
E('div', { 'class': 'ai-timeline-msg' }, e.message)
]);
})) : E('p', {}, 'No events in the last 24 hours')
),
E('div', { 'class': 'right' }, E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Close'))
]);
});
},
pollData: function() {
var self = this;
return api.getOverview().then(function(data) {
var s = data.status || {};
var p = data.posture || {};
var alerts = data.alerts || [];
var agents = s.agents || {};
var el = document.getElementById('ai-stats');
if (el) dom.content(el, self.renderStats(s));
el = document.getElementById('ai-agents');
if (el) dom.content(el, self.renderAgents(agents));
el = document.getElementById('ai-alerts');
if (el) dom.content(el, self.renderAlerts(alerts));
// Update posture card
el = document.getElementById('ai-posture');
if (el) {
el.className = 'ai-posture-card ' + api.getPostureColor(p.score || 0);
var scoreEl = el.querySelector('.ai-score-value');
var labelEl = el.querySelector('.ai-posture-label');
var factorsEl = el.querySelector('.ai-posture-factors');
if (scoreEl) scoreEl.textContent = String(p.score || 0);
if (labelEl) labelEl.textContent = api.getPostureLabel(p.score || 0);
if (factorsEl) factorsEl.textContent = p.factors || '';
}
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});