'use strict'; 'require view'; 'require dom'; 'require poll'; 'require ui'; 'require cve-triage.api as api'; 'require secubox/kiss-theme'; return view.extend({ handleSaveApply: null, handleSave: null, handleReset: null, load: function() { return Promise.all([ api.callStatus(), api.callGetPending(), api.callGetAlerts(), api.callGetSummary() ]); }, renderStats: function(status, pending, alerts) { var c = KissTheme.colors; var localaiColor = status.localai_status === 'online' ? c.green : c.red; return [ KissTheme.stat(status.enabled ? 'ON' : 'OFF', 'Agent', status.enabled ? c.green : c.red), KissTheme.stat(status.localai_status.toUpperCase(), 'LocalAI', localaiColor), KissTheme.stat(status.pending_count, 'Pending', c.orange), KissTheme.stat(status.alert_count, 'Alerts', status.alert_count > 0 ? c.red : c.muted) ]; }, renderSummaryCard: function(summaryData) { var summary = summaryData.summary || {}; var riskScore = summary.risk_score || 0; var riskColor = riskScore >= 70 ? 'red' : (riskScore >= 40 ? 'orange' : 'green'); var riskCssColor = riskScore >= 70 ? 'var(--kiss-red)' : (riskScore >= 40 ? 'var(--kiss-orange)' : 'var(--kiss-green)'); return KissTheme.card('Security Summary', E('div', { 'style': 'text-align: center;' }, [ E('div', { 'style': 'font-size: 48px; font-weight: 700; color: ' + riskCssColor + ';' }, riskScore), E('div', { 'style': 'font-size: 14px; color: var(--kiss-muted); margin-bottom: 16px;' }, '/100 Risk Score'), E('div', { 'style': 'padding: 12px; background: var(--kiss-bg); border-radius: 8px; text-align: left;' }, 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 KissTheme.card('Alerts', E('p', { 'style': 'color: var(--kiss-muted); text-align: center; padding: 20px;' }, 'No active alerts') ); } var alertItems = activeAlerts.slice(0, 5).map(function(alert) { var severityColor = { 'critical': 'red', 'high': 'orange', 'medium': 'orange', 'low': 'blue' }[alert.severity] || 'muted'; var borderColor = { 'critical': 'var(--kiss-red)', 'high': 'var(--kiss-orange)', 'medium': 'var(--kiss-orange)', 'low': 'var(--kiss-blue)' }[alert.severity] || 'var(--kiss-line)'; return E('div', { 'style': 'padding: 12px; background: var(--kiss-bg); border-radius: 6px; border-left: 3px solid ' + borderColor + '; margin-bottom: 8px;' }, [ E('div', { 'style': 'display: flex; align-items: center; gap: 8px; margin-bottom: 6px;' }, [ KissTheme.badge(alert.severity.toUpperCase(), severityColor), api.formatCVE(alert.cve), E('button', { 'class': 'kiss-btn', 'style': 'margin-left: auto; padding: 4px 10px; font-size: 11px;', 'click': function() { api.callAckAlert(alert.id).then(function() { window.location.reload(); }); } }, 'Ack') ]), E('div', { 'style': 'color: var(--kiss-muted); font-size: 13px;' }, alert.message) ]); }); return KissTheme.card( E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [ E('span', {}, 'Active Alerts'), KissTheme.badge(activeAlerts.length + ' alerts', 'red') ]), E('div', {}, alertItems) ); }, renderPendingCard: function(pendingData) { var pending = pendingData.pending || []; if (pending.length === 0) { return KissTheme.card('Pending Recommendations', E('p', { 'style': 'color: var(--kiss-muted); text-align: center; padding: 20px;' }, 'No pending recommendations') ); } var actionButtons = E('div', { 'style': 'display: flex; gap: 12px; margin-bottom: 16px;' }, [ E('button', { 'class': 'kiss-btn kiss-btn-green', 'click': function() { if (confirm('Approve all ' + pending.length + ' recommendations?')) { api.callApproveAll().then(function() { window.location.reload(); }); } } }, 'Approve All'), E('button', { 'class': 'kiss-btn kiss-btn-red', 'click': function() { if (confirm('Clear all pending recommendations?')) { api.callClearPending().then(function() { window.location.reload(); }); } } }, 'Clear All') ]); var rows = pending.map(function(rec) { var severityColor = { 'critical': 'red', 'high': 'orange', 'medium': 'orange', 'low': 'blue' }[rec.severity] || 'muted'; return E('tr', {}, [ E('td', {}, KissTheme.badge(rec.severity.toUpperCase(), severityColor)), E('td', {}, api.formatCVE(rec.cve)), E('td', { 'style': 'font-family: monospace;' }, rec.affected_package || '-'), E('td', {}, rec.action), E('td', {}, rec.urgency), E('td', { 'style': 'width: 100px;' }, [ E('div', { 'style': 'display: flex; gap: 6px;' }, [ E('button', { 'class': 'kiss-btn kiss-btn-green', 'style': 'padding: 4px 10px; font-size: 11px;', 'click': function() { api.callApprove(rec.id).then(function() { window.location.reload(); }); } }, '\u2713'), E('button', { 'class': 'kiss-btn kiss-btn-red', 'style': 'padding: 4px 10px; font-size: 11px;', 'click': function() { api.callReject(rec.id, 'Manual rejection').then(function() { window.location.reload(); }); } }, '\u2717') ]) ]) ]); }); return KissTheme.card( E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [ E('span', {}, 'Pending Recommendations'), KissTheme.badge(pending.length + ' pending', 'orange') ]), E('div', {}, [ actionButtons, E('table', { 'class': 'kiss-table' }, [ E('thead', {}, [ E('tr', {}, [ E('th', { 'style': 'width: 80px;' }, 'Severity'), E('th', {}, 'CVE'), E('th', {}, 'Package'), E('th', {}, 'Action'), E('th', {}, 'Urgency'), E('th', {}, 'Actions') ]) ]), E('tbody', {}, rows) ]) ]) ); }, render: function(data) { var self = this; var status = data[0] || {}; var pending = data[1] || {}; var alerts = data[2] || {}; var summary = data[3] || {}; var content = [ // Header 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;' }, 'CVE Triage'), status.enabled ? KissTheme.badge('Active', 'green') : KissTheme.badge('Inactive', 'red'), E('button', { 'class': 'kiss-btn kiss-btn-blue', 'style': 'margin-left: auto;', '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('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'AI-powered CVE vulnerability triage and remediation recommendations') ]), // Stats E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin: 20px 0;' }, this.renderStats(status, pending, alerts)), // Package counts E('div', { 'style': 'color: var(--kiss-muted); font-size: 12px; margin-bottom: 20px;' }, 'Monitored: ' + status.packages.opkg + ' opkg, ' + status.packages.lxc + ' LXC, ' + status.packages.docker + ' Docker' + (status.last_run ? ' | Last run: ' + status.last_run : '')), // Two column layout E('div', { 'class': 'kiss-grid kiss-grid-2', 'style': 'margin-bottom: 20px;' }, [ this.renderSummaryCard(summary), this.renderAlertsCard(alerts) ]), // Pending recommendations (full width) this.renderPendingCard(pending) ]; return KissTheme.wrap(content, 'admin/secubox/security/cve-triage'); } });