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>
260 lines
9.2 KiB
JavaScript
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)
|
|
]);
|
|
}
|
|
});
|