secubox-openwrt/package/secubox/luci-app-cve-triage/htdocs/luci-static/resources/view/cve-triage/dashboard.js
CyberMind-FR 44493ebfe3 feat: Add CVE Triage Agent and Vortex DNS, fix webmail login
New Packages:
- secubox-cve-triage: AI-powered CVE analysis and vulnerability management
  - NVD API integration for CVE data
  - CrowdSec CVE alert correlation
  - LocalAI-powered impact analysis
  - Approval workflow for patch recommendations
  - Multi-source monitoring (opkg, LXC, Docker)

- luci-app-cve-triage: Dashboard with alerts, pending queue, risk score

- secubox-vortex-dns: Meshed multi-dynamic subdomain delegation
  - Master/slave hierarchical DNS delegation
  - Wildcard domain management
  - First Peek auto-registration
  - Gossip-based exposure config sync
  - Submastering for nested hierarchies

Fixes:
- Webmail 401 login: config.docker.inc.php was overriding IMAP host
  to ssl://mail.secubox.in:993 which Docker couldn't reach
- Fixed mailctl webmail configure to use socat proxy (172.17.0.1:10143)

Documentation:
- Added LXC cgroup:mixed fix to FAQ-TROUBLESHOOTING.md
- Updated CLAUDE.md to include FAQ consultation at startup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 12:19:54 +01:00

260 lines
9.2 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require cve-triage.api as api';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
api.callStatus(),
api.callGetPending(),
api.callGetAlerts(),
api.callGetSummary()
]);
},
renderStatusCard: function(status) {
var localaiClass = status.localai_status === 'online' ? 'success' : 'danger';
return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Agent Status'),
E('div', { 'class': 'cve-status-grid' }, [
E('div', { 'class': 'cve-stat' }, [
E('span', { 'class': 'cve-stat-value' }, status.enabled ? 'ON' : 'OFF'),
E('span', { 'class': 'cve-stat-label' }, 'Agent')
]),
E('div', { 'class': 'cve-stat' }, [
E('span', { 'class': 'cve-stat-value cve-' + localaiClass }, status.localai_status.toUpperCase()),
E('span', { 'class': 'cve-stat-label' }, 'LocalAI')
]),
E('div', { 'class': 'cve-stat' }, [
E('span', { 'class': 'cve-stat-value' }, status.pending_count),
E('span', { 'class': 'cve-stat-label' }, 'Pending')
]),
E('div', { 'class': 'cve-stat' }, [
E('span', { 'class': 'cve-stat-value cve-warning' }, status.alert_count),
E('span', { 'class': 'cve-stat-label' }, 'Alerts')
])
]),
E('div', { 'class': 'cve-packages' }, [
E('small', {}, 'Monitored: ' + status.packages.opkg + ' opkg, ' +
status.packages.lxc + ' LXC, ' + status.packages.docker + ' Docker')
]),
status.last_run ? E('div', { 'class': 'cve-lastrun' }, [
E('small', {}, 'Last run: ' + status.last_run)
]) : null
]);
},
renderSummaryCard: function(summaryData) {
var summary = summaryData.summary || {};
var riskScore = summary.risk_score || 0;
var riskClass = riskScore >= 70 ? 'danger' : (riskScore >= 40 ? 'warning' : 'success');
return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Security Summary'),
E('div', { 'class': 'cve-risk-score cve-' + riskClass }, [
E('span', { 'class': 'cve-risk-value' }, riskScore),
E('span', { 'class': 'cve-risk-label' }, '/100 Risk Score')
]),
E('p', { 'class': 'cve-summary-text' }, summary.summary || 'No analysis available. Run a triage cycle.')
]);
},
renderAlertsCard: function(alertsData) {
var alerts = alertsData.alerts || [];
var activeAlerts = alerts.filter(function(a) { return !a.acknowledged; });
if (activeAlerts.length === 0) {
return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Alerts'),
E('p', { 'class': 'cve-no-data' }, 'No active alerts')
]);
}
var self = this;
return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Active Alerts (' + activeAlerts.length + ')'),
E('div', { 'class': 'cve-alerts' },
activeAlerts.slice(0, 5).map(function(alert) {
return E('div', { 'class': 'cve-alert cve-alert-' + api.getSeverityClass(alert.severity) }, [
E('div', { 'class': 'cve-alert-header' }, [
E('span', { 'class': 'cve-severity' }, api.getSeverityIcon(alert.severity)),
api.formatCVE(alert.cve),
E('button', {
'class': 'cbi-button cbi-button-action cve-btn-ack',
'click': function() {
api.callAckAlert(alert.id).then(function() {
window.location.reload();
});
}
}, 'Ack')
]),
E('div', { 'class': 'cve-alert-message' }, alert.message)
]);
})
)
]);
},
renderPendingCard: function(pendingData) {
var pending = pendingData.pending || [];
var self = this;
return E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Pending Recommendations (' + pending.length + ')'),
pending.length > 0 ? E('div', { 'class': 'cve-actions-bar' }, [
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': function() {
if (confirm('Approve all ' + pending.length + ' recommendations?')) {
api.callApproveAll().then(function() {
window.location.reload();
});
}
}
}, 'Approve All'),
E('button', {
'class': 'cbi-button cbi-button-negative',
'click': function() {
if (confirm('Clear all pending recommendations?')) {
api.callClearPending().then(function() {
window.location.reload();
});
}
}
}, 'Clear All')
]) : null,
pending.length === 0 ?
E('p', { 'class': 'cve-no-data' }, 'No pending recommendations') :
E('table', { 'class': 'table cve-table' }, [
E('thead', {}, E('tr', {}, [
E('th', {}, 'Severity'),
E('th', {}, 'CVE'),
E('th', {}, 'Package'),
E('th', {}, 'Action'),
E('th', {}, 'Urgency'),
E('th', {}, 'Actions')
])),
E('tbody', {},
pending.map(function(rec) {
return E('tr', { 'class': 'cve-row-' + api.getSeverityClass(rec.severity) }, [
E('td', {}, E('span', { 'class': 'cve-badge cve-badge-' + api.getSeverityClass(rec.severity) },
rec.severity.toUpperCase())),
E('td', {}, api.formatCVE(rec.cve)),
E('td', {}, rec.affected_package || '-'),
E('td', {}, rec.action),
E('td', {}, rec.urgency),
E('td', {}, [
E('button', {
'class': 'cbi-button cbi-button-positive cve-btn-sm',
'click': function() {
api.callApprove(rec.id).then(function() {
window.location.reload();
});
}
}, '\u2713'),
' ',
E('button', {
'class': 'cbi-button cbi-button-negative cve-btn-sm',
'click': function() {
api.callReject(rec.id, 'Manual rejection').then(function() {
window.location.reload();
});
}
}, '\u2717')
])
]);
})
)
])
]);
},
render: function(data) {
var status = data[0] || {};
var pending = data[1] || {};
var alerts = data[2] || {};
var summary = data[3] || {};
var self = this;
// Inject CSS
var css = E('style', {}, `
.cve-status-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin: 1rem 0; }
.cve-stat { text-align: center; padding: 1rem; background: var(--background-color-low, #f5f5f5); border-radius: 8px; }
.cve-stat-value { display: block; font-size: 1.5rem; font-weight: bold; }
.cve-stat-label { font-size: 0.85rem; color: var(--text-color-medium, #666); }
.cve-success { color: #28a745; }
.cve-warning { color: #ffc107; }
.cve-danger { color: #dc3545; }
.cve-risk-score { text-align: center; padding: 2rem; margin: 1rem 0; border-radius: 12px; }
.cve-risk-score.cve-success { background: rgba(40, 167, 69, 0.1); }
.cve-risk-score.cve-warning { background: rgba(255, 193, 7, 0.1); }
.cve-risk-score.cve-danger { background: rgba(220, 53, 69, 0.1); }
.cve-risk-value { font-size: 3rem; font-weight: bold; }
.cve-risk-label { display: block; font-size: 0.9rem; }
.cve-summary-text { margin-top: 1rem; padding: 1rem; background: var(--background-color-low, #f5f5f5); border-radius: 8px; }
.cve-alert { padding: 0.75rem; margin-bottom: 0.5rem; border-radius: 6px; border-left: 4px solid; }
.cve-alert-danger { border-color: #dc3545; background: rgba(220, 53, 69, 0.05); }
.cve-alert-warning { border-color: #ffc107; background: rgba(255, 193, 7, 0.05); }
.cve-alert-header { display: flex; align-items: center; gap: 0.5rem; }
.cve-alert-message { margin-top: 0.5rem; font-size: 0.9rem; }
.cve-severity { font-size: 1.2rem; }
.cve-link { color: #0d6efd; text-decoration: none; font-family: monospace; }
.cve-link:hover { text-decoration: underline; }
.cve-no-data { color: var(--text-color-medium, #666); font-style: italic; }
.cve-table { width: 100%; }
.cve-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: bold; }
.cve-badge-danger { background: #dc3545; color: white; }
.cve-badge-warning { background: #ffc107; color: black; }
.cve-badge-primary { background: #0d6efd; color: white; }
.cve-badge-secondary { background: #6c757d; color: white; }
.cve-row-danger { background: rgba(220, 53, 69, 0.05); }
.cve-row-warning { background: rgba(255, 193, 7, 0.05); }
.cve-actions-bar { margin-bottom: 1rem; display: flex; gap: 0.5rem; }
.cve-btn-sm { padding: 0.25rem 0.5rem; font-size: 0.85rem; }
.cve-btn-ack { padding: 0.2rem 0.5rem; font-size: 0.75rem; margin-left: auto; }
.cve-packages, .cve-lastrun { margin-top: 0.5rem; color: var(--text-color-medium, #666); }
.cve-header-actions { float: right; }
`);
return E('div', { 'class': 'cbi-map' }, [
css,
E('h2', {}, [
'CVE Triage',
E('span', { 'class': 'cve-header-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
ui.showModal('Running Triage...', [
E('p', { 'class': 'spinning' }, 'Starting CVE triage cycle...')
]);
api.callRun().then(function() {
setTimeout(function() {
ui.hideModal();
window.location.reload();
}, 3000);
});
}
}, 'Run Triage')
])
]),
E('div', { 'class': 'cve-grid', 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;' }, [
this.renderStatusCard(status),
this.renderSummaryCard(summary)
]),
this.renderAlertsCard(alerts),
this.renderPendingCard(pending)
]);
}
});