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>
246 lines
7.9 KiB
JavaScript
246 lines
7.9 KiB
JavaScript
'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');
|
|
}
|
|
});
|