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>
289 lines
9.9 KiB
JavaScript
289 lines
9.9 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require rpc';
|
|
'require ui';
|
|
'require secubox/kiss-theme';
|
|
|
|
// Use cached status for fast loading
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'status_cached'
|
|
});
|
|
|
|
var callAlerts = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'alerts'
|
|
});
|
|
|
|
var callBans = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'bans'
|
|
});
|
|
|
|
var callStart = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'start'
|
|
});
|
|
|
|
var callStop = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'stop'
|
|
});
|
|
|
|
var callRestart = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'restart'
|
|
});
|
|
|
|
var callUnban = rpc.declare({
|
|
object: 'luci.mitmproxy',
|
|
method: 'unban',
|
|
params: ['ip']
|
|
});
|
|
|
|
function fmt(n) {
|
|
n = parseInt(n) || 0;
|
|
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
|
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
|
|
return String(n);
|
|
}
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
// Only load cached status for fast initial render
|
|
return callStatus().catch(function(e) {
|
|
console.error('status:', e);
|
|
return {};
|
|
});
|
|
},
|
|
|
|
renderNav: function(active) {
|
|
var tabs = [
|
|
{ id: 'status', label: 'Dashboard', path: 'admin/secubox/security/mitmproxy/status' },
|
|
{ id: 'waf-filters', label: 'WAF Filters', path: 'admin/secubox/security/mitmproxy/waf-filters' },
|
|
{ id: 'settings', label: 'Settings', path: 'admin/secubox/security/mitmproxy/settings' }
|
|
];
|
|
var c = KissTheme.colors;
|
|
return E('div', { 'style': 'display: flex; gap: 8px; margin-bottom: 20px; border-bottom: 1px solid var(--kiss-line); padding-bottom: 12px;' }, tabs.map(function(t) {
|
|
var isActive = active === t.id;
|
|
return E('a', {
|
|
'href': L.url(t.path),
|
|
'style': 'padding: 8px 16px; text-decoration: none; border-radius: 6px; font-size: 13px; ' +
|
|
(isActive ? 'background: rgba(0,200,83,0.1); color: var(--kiss-green); border: 1px solid rgba(0,200,83,0.3);' :
|
|
'color: var(--kiss-muted); border: 1px solid transparent;')
|
|
}, t.label);
|
|
}));
|
|
},
|
|
|
|
renderStats: function(s) {
|
|
var c = KissTheme.colors;
|
|
var isRunning = s.running === true;
|
|
var stats = [
|
|
{ label: 'WAF Status', value: isRunning ? 'ACTIVE' : 'DOWN', color: isRunning ? c.green : c.red },
|
|
{ label: 'Threats Today', value: fmt(s.threats_today || 0), color: (s.threats_today || 0) > 0 ? c.orange : c.muted },
|
|
{ label: 'Auto-Bans', value: fmt(s.autobans_total || 0), color: (s.autobans_total || 0) > 0 ? c.red : c.muted },
|
|
{ label: 'Pending', value: fmt(s.autobans_pending || 0), color: (s.autobans_pending || 0) > 0 ? c.yellow : c.muted }
|
|
];
|
|
return stats.map(function(st) {
|
|
return KissTheme.stat(st.value, st.label, st.color);
|
|
});
|
|
},
|
|
|
|
renderHealth: function(s) {
|
|
var c = KissTheme.colors;
|
|
var checks = [
|
|
{ label: 'WAF Engine', ok: s.running === true },
|
|
{ label: 'Auto-Ban', ok: s.autoban_enabled === true, value: s.autoban_sensitivity || 'moderate' },
|
|
{ label: 'Ban Duration', ok: true, value: s.autoban_duration || '4h' },
|
|
{ label: 'Mode', ok: true, value: s.mode || 'upstream' }
|
|
];
|
|
return E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' }, checks.map(function(ch) {
|
|
var valueText = ch.value ? ch.value : (ch.ok ? 'OK' : 'Disabled');
|
|
return E('div', { 'style': 'display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.03);' }, [
|
|
E('div', { 'style': 'width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; ' +
|
|
(ch.ok ? 'background: rgba(0,200,83,0.15); color: var(--kiss-green);' : 'background: rgba(255,23,68,0.15); color: var(--kiss-red);') },
|
|
ch.ok ? '\u2713' : '\u2717'),
|
|
E('div', { 'style': 'flex: 1;' }, [
|
|
E('div', { 'style': 'font-size: 13px; color: var(--kiss-text);' }, ch.label),
|
|
E('div', { 'style': 'font-size: 11px; color: var(--kiss-muted);' }, valueText)
|
|
])
|
|
]);
|
|
}));
|
|
},
|
|
|
|
renderControls: function(isRunning) {
|
|
var self = this;
|
|
return E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px;' }, [
|
|
E('button', {
|
|
'class': 'kiss-btn kiss-btn-green',
|
|
'disabled': isRunning,
|
|
'click': function() {
|
|
ui.showModal('Starting WAF...', [E('p', { 'class': 'spinning' }, 'Please wait...')]);
|
|
callStart().then(function() { ui.hideModal(); location.reload(); });
|
|
}
|
|
}, ['\u25B6 ', 'Start']),
|
|
E('button', {
|
|
'class': 'kiss-btn kiss-btn-red',
|
|
'disabled': !isRunning,
|
|
'click': function() {
|
|
ui.showModal('Stopping WAF...', [E('p', { 'class': 'spinning' }, 'Please wait...')]);
|
|
callStop().then(function() { ui.hideModal(); location.reload(); });
|
|
}
|
|
}, ['\u25A0 ', 'Stop']),
|
|
E('button', {
|
|
'class': 'kiss-btn',
|
|
'click': function() {
|
|
ui.showModal('Restarting WAF...', [E('p', { 'class': 'spinning' }, 'Please wait...')]);
|
|
callRestart().then(function() { ui.hideModal(); location.reload(); });
|
|
}
|
|
}, ['\u21BB ', 'Restart'])
|
|
]);
|
|
},
|
|
|
|
renderAlerts: function(alerts) {
|
|
alerts = Array.isArray(alerts) ? alerts : [];
|
|
if (!alerts.length) {
|
|
return E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, 'No recent threats');
|
|
}
|
|
return E('table', { 'class': 'kiss-table' }, [
|
|
E('thead', {}, E('tr', {}, [
|
|
E('th', {}, 'Time'),
|
|
E('th', {}, 'Source'),
|
|
E('th', {}, 'Type'),
|
|
E('th', {}, 'Severity')
|
|
])),
|
|
E('tbody', {}, alerts.slice(0, 10).map(function(a) {
|
|
var ts = a.timestamp || '';
|
|
var time = ts.split('T')[1] ? ts.split('T')[1].split('.')[0] : '-';
|
|
var sevColor = a.severity === 'high' ? 'var(--kiss-red)' :
|
|
a.severity === 'medium' ? 'var(--kiss-orange)' : 'var(--kiss-muted)';
|
|
return E('tr', {}, [
|
|
E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, time),
|
|
E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, a.source_ip || '-')),
|
|
E('td', {}, E('span', { 'style': 'font-size: 12px;' }, a.type || '-')),
|
|
E('td', {}, E('span', { 'style': 'color: ' + sevColor + ';' }, a.severity || '-'))
|
|
]);
|
|
}))
|
|
]);
|
|
},
|
|
|
|
renderBans: function(bans) {
|
|
var self = this;
|
|
bans = Array.isArray(bans) ? bans : [];
|
|
if (!bans.length) {
|
|
return E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, 'No active bans');
|
|
}
|
|
return E('table', { 'class': 'kiss-table' }, [
|
|
E('thead', {}, E('tr', {}, [
|
|
E('th', {}, 'IP'),
|
|
E('th', {}, 'Reason'),
|
|
E('th', {}, 'Duration'),
|
|
E('th', { 'style': 'width: 80px;' }, 'Action')
|
|
])),
|
|
E('tbody', {}, bans.slice(0, 10).map(function(b) {
|
|
var d = (b.decisions && b.decisions[0]) || {};
|
|
var ip = d.value || (b.source && b.source.ip) || '-';
|
|
var reason = d.scenario || '-';
|
|
if (reason.length > 30) reason = reason.substring(0, 27) + '...';
|
|
return E('tr', {}, [
|
|
E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, ip)),
|
|
E('td', { 'style': 'font-size: 12px;' }, reason),
|
|
E('td', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, d.duration || '-'),
|
|
E('td', {}, E('button', {
|
|
'class': 'kiss-btn',
|
|
'style': 'padding: 4px 8px; font-size: 11px;',
|
|
'click': function() {
|
|
if (!confirm('Unban ' + ip + '?')) return;
|
|
ui.showModal('Unbanning...', [E('p', { 'class': 'spinning' }, 'Removing ban...')]);
|
|
callUnban(ip).then(function() { ui.hideModal(); location.reload(); });
|
|
}
|
|
}, 'Unban'))
|
|
]);
|
|
}))
|
|
]);
|
|
},
|
|
|
|
render: function(status) {
|
|
var self = this;
|
|
status = status || {};
|
|
var isRunning = status.running === true;
|
|
|
|
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;' }, 'WAF Dashboard'),
|
|
KissTheme.badge(isRunning ? 'ACTIVE' : 'STOPPED', isRunning ? 'green' : 'red')
|
|
]),
|
|
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' }, 'Web Application Firewall - Real-time threat detection')
|
|
]),
|
|
|
|
// Navigation tabs
|
|
this.renderNav('status'),
|
|
|
|
// Stats grid
|
|
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin: 20px 0;' }, this.renderStats(status)),
|
|
|
|
// Two column layout
|
|
E('div', { 'class': 'kiss-grid kiss-grid-2' }, [
|
|
// Health card
|
|
KissTheme.card('System Health', E('div', {}, [
|
|
this.renderHealth(status),
|
|
this.renderControls(isRunning)
|
|
])),
|
|
// Alerts card
|
|
KissTheme.card('Recent Threats', E('div', { 'id': 'waf-alerts' }, [
|
|
E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, [
|
|
E('span', { 'class': 'spinning' }),
|
|
' Loading...'
|
|
])
|
|
]))
|
|
]),
|
|
|
|
// Bans card
|
|
KissTheme.card('Active Bans', E('div', { 'id': 'waf-bans' }, [
|
|
E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-muted);' }, [
|
|
E('span', { 'class': 'spinning' }),
|
|
' Loading...'
|
|
])
|
|
]))
|
|
];
|
|
|
|
// Async load alerts
|
|
callAlerts().then(function(data) {
|
|
var el = document.getElementById('waf-alerts');
|
|
if (el) {
|
|
el.innerHTML = '';
|
|
el.appendChild(self.renderAlerts(data && data.alerts));
|
|
}
|
|
}).catch(function() {
|
|
var el = document.getElementById('waf-alerts');
|
|
if (el) {
|
|
el.innerHTML = '';
|
|
el.appendChild(E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-red);' }, 'Failed to load threats'));
|
|
}
|
|
});
|
|
|
|
// Async load bans
|
|
callBans().then(function(data) {
|
|
var el = document.getElementById('waf-bans');
|
|
if (el) {
|
|
el.innerHTML = '';
|
|
el.appendChild(self.renderBans(data && data.bans));
|
|
}
|
|
}).catch(function() {
|
|
var el = document.getElementById('waf-bans');
|
|
if (el) {
|
|
el.innerHTML = '';
|
|
el.appendChild(E('div', { 'style': 'text-align: center; padding: 24px; color: var(--kiss-red);' }, 'Failed to load bans'));
|
|
}
|
|
});
|
|
|
|
return KissTheme.wrap(content, 'admin/secubox/security/mitmproxy/status');
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|