secubox-openwrt/package/secubox/luci-app-webradio/htdocs/luci-static/resources/view/webradio/security.js
CyberMind-FR 1bbd345cee refactor(luci): Mass KissTheme UI rework across all LuCI apps
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>
2026-03-12 11:09:34 +01:00

336 lines
13 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require ui';
'require uci';
'require form';
'require secubox/kiss-theme';
var callSecurityStatus = rpc.declare({
object: 'luci.webradio',
method: 'security_status',
expect: {}
});
var callInstallCrowdsec = rpc.declare({
object: 'luci.webradio',
method: 'install_crowdsec',
expect: {}
});
var callGenerateCert = rpc.declare({
object: 'luci.webradio',
method: 'generate_ssl_cert',
params: ['hostname'],
expect: {}
});
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
callSecurityStatus(),
uci.load('icecast')
]);
},
renderStats: function(status) {
var c = KissTheme.colors;
return [
KissTheme.stat(status.ssl_enabled ? 'On' : 'Off', 'SSL/TLS', status.ssl_enabled ? c.green : c.muted),
KissTheme.stat(status.crowdsec_installed ? 'Active' : 'Off', 'CrowdSec', status.crowdsec_installed ? c.blue : c.muted),
KissTheme.stat(status.crowdsec_decisions || '0', 'Bans', c.red)
];
},
render: function(data) {
var self = this;
var status = data[0] || {};
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;' }, 'Security & Hardening'),
KissTheme.badge('ICECAST', 'cyan')
]),
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' },
'Configure SSL/TLS, rate limiting, and CrowdSec integration')
]),
// Stats
E('div', { 'class': 'kiss-grid kiss-grid-3', 'style': 'margin: 20px 0;' },
this.renderStats(status)),
// SSL/TLS Section
KissTheme.card('SSL/TLS Encryption',
E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' },
'Enable HTTPS for secure streaming. Listeners can connect via https://hostname:8443/live'),
E('table', { 'class': 'kiss-table', 'style': 'margin: 8px 0;' }, [
E('tbody', {}, [
E('tr', {}, [
E('td', { 'style': 'width: 180px; font-weight: 600;' }, 'SSL Status'),
E('td', {}, KissTheme.badge(status.ssl_enabled ? 'Enabled' : 'Disabled', status.ssl_enabled ? 'green' : 'red'))
]),
E('tr', {}, [
E('td', { 'style': 'font-weight: 600;' }, 'Certificate'),
E('td', { 'style': status.ssl_cert_exists ? 'color: var(--kiss-green);' : 'color: var(--kiss-orange);' },
status.ssl_cert_exists ? 'Found: ' + status.ssl_cert_path : 'Not found')
]),
status.ssl_cert_expiry ? E('tr', {}, [
E('td', { 'style': 'font-weight: 600;' }, 'Expires'),
E('td', {}, status.ssl_cert_expiry)
]) : ''
])
]),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;' }, [
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'display: flex; align-items: center; gap: 10px;' }, [
E('input', {
'type': 'checkbox',
'id': 'ssl-enabled',
'checked': uci.get('icecast', 'ssl', 'enabled') === '1'
}),
E('span', {}, 'Enable HTTPS streaming on port 8443')
])
]),
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'SSL Port'),
E('input', {
'type': 'number',
'id': 'ssl-port',
'value': uci.get('icecast', 'ssl', 'port') || '8443',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
])
]),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px;' }, [
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'Certificate Path'),
E('input', {
'type': 'text',
'id': 'ssl-cert',
'value': uci.get('icecast', 'ssl', 'certificate') || '/etc/ssl/certs/icecast.pem',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
]),
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'Private Key Path'),
E('input', {
'type': 'text',
'id': 'ssl-key',
'value': uci.get('icecast', 'ssl', 'key') || '/etc/ssl/private/icecast.key',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
])
]),
E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'click': ui.createHandlerFn(this, 'handleSaveSSL')
}, 'Save SSL Settings'),
E('button', {
'class': 'kiss-btn',
'click': ui.createHandlerFn(this, 'handleGenerateCert')
}, 'Generate Self-Signed Certificate')
])
])
),
// Rate Limiting Section
KissTheme.card('Rate Limiting',
E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' },
'Configure connection limits to prevent abuse'),
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px;' }, [
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'Client Timeout (sec)'),
E('input', {
'type': 'number',
'id': 'client-timeout',
'value': uci.get('icecast', 'ratelimit', 'client_timeout') || '30',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
]),
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'Burst Size (bytes)'),
E('input', {
'type': 'number',
'id': 'burst-size',
'value': uci.get('icecast', 'ratelimit', 'burst_size') || '65535',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
]),
E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' }, [
E('label', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, 'Queue Size (bytes)'),
E('input', {
'type': 'number',
'id': 'queue-size',
'value': uci.get('icecast', 'ratelimit', 'queue_size') || '524288',
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); color: var(--kiss-text); padding: 10px 12px; border-radius: 6px;'
})
])
]),
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'style': 'align-self: flex-start;',
'click': ui.createHandlerFn(this, 'handleSaveRateLimit')
}, 'Save Rate Limits')
])
),
// CrowdSec Section
KissTheme.card('CrowdSec Integration',
E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('p', { 'style': 'color: var(--kiss-muted); margin: 0;' },
'Automatic abuse detection and IP blocking with CrowdSec'),
E('table', { 'class': 'kiss-table', 'style': 'margin: 8px 0;' }, [
E('tbody', {}, [
E('tr', {}, [
E('td', { 'style': 'width: 180px; font-weight: 600;' }, 'CrowdSec'),
E('td', {}, KissTheme.badge(status.crowdsec_installed ? 'Installed' : 'Not Installed', status.crowdsec_installed ? 'green' : 'red'))
]),
E('tr', {}, [
E('td', { 'style': 'font-weight: 600;' }, 'Icecast Parsers'),
E('td', {}, KissTheme.badge(status.crowdsec_parsers ? 'Installed' : 'Not Installed', status.crowdsec_parsers ? 'green' : 'red'))
]),
E('tr', {}, [
E('td', { 'style': 'font-weight: 600;' }, 'Icecast Scenarios'),
E('td', {}, KissTheme.badge(status.crowdsec_scenarios ? 'Installed' : 'Not Installed', status.crowdsec_scenarios ? 'green' : 'red'))
]),
status.crowdsec_decisions ? E('tr', {}, [
E('td', { 'style': 'font-weight: 600;' }, 'Active Bans'),
E('td', {}, String(status.crowdsec_decisions))
]) : ''
])
]),
E('div', {}, [
E('p', { 'style': 'margin: 0 0 8px 0;' }, 'CrowdSec protection includes:'),
E('ul', { 'style': 'color: var(--kiss-muted); margin: 0; padding-left: 20px;' }, [
E('li', {}, 'Connection flood detection (20+ connections in 30s)'),
E('li', {}, 'Bandwidth abuse / stream ripping detection'),
E('li', {}, 'Automatic IP blocking via firewall bouncer')
])
]),
status.crowdsec_installed ? E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': ui.createHandlerFn(this, 'handleInstallCrowdsec')
}, status.crowdsec_parsers ? 'Reinstall CrowdSec Rules' : 'Install CrowdSec Rules')
: E('p', { 'style': 'color: var(--kiss-orange); margin: 0;' },
'Install CrowdSec package first: opkg install crowdsec crowdsec-firewall-bouncer')
])
),
// Security Tips
KissTheme.card('Security Tips',
E('ul', { 'style': 'color: var(--kiss-muted); margin: 0; padding-left: 20px;' }, [
E('li', {}, 'Change default passwords immediately (admin, source, relay)'),
E('li', {}, 'Use SSL/TLS for all public-facing streams'),
E('li', {}, 'Enable CrowdSec to automatically block abusive IPs'),
E('li', {}, 'Set reasonable listener limits to prevent resource exhaustion'),
E('li', {}, 'Monitor logs regularly: /var/log/icecast/'),
E('li', {}, 'Consider using firewall rules to restrict source connections to localhost')
])
)
];
return KissTheme.wrap(content, 'admin/services/webradio/security');
},
handleSaveSSL: function() {
var enabled = document.getElementById('ssl-enabled').checked;
var port = document.getElementById('ssl-port').value;
var cert = document.getElementById('ssl-cert').value;
var key = document.getElementById('ssl-key').value;
uci.set('icecast', 'ssl', 'ssl');
uci.set('icecast', 'ssl', 'enabled', enabled ? '1' : '0');
uci.set('icecast', 'ssl', 'port', port);
uci.set('icecast', 'ssl', 'certificate', cert);
uci.set('icecast', 'ssl', 'key', key);
return uci.save().then(function() {
return uci.apply();
}).then(function() {
ui.addNotification(null, E('p', 'SSL settings saved. Restart Icecast to apply.'));
});
},
handleSaveRateLimit: function() {
var clientTimeout = document.getElementById('client-timeout').value;
var burstSize = document.getElementById('burst-size').value;
var queueSize = document.getElementById('queue-size').value;
uci.set('icecast', 'ratelimit', 'ratelimit');
uci.set('icecast', 'ratelimit', 'client_timeout', clientTimeout);
uci.set('icecast', 'ratelimit', 'burst_size', burstSize);
uci.set('icecast', 'ratelimit', 'queue_size', queueSize);
return uci.save().then(function() {
return uci.apply();
}).then(function() {
ui.addNotification(null, E('p', 'Rate limit settings saved. Restart Icecast to apply.'));
});
},
handleGenerateCert: function() {
var hostname = uci.get('icecast', 'server', 'hostname') || 'localhost';
ui.showModal('Generate Certificate', [
E('p', {}, 'Generate a self-signed SSL certificate for: ' + hostname),
E('p', { 'style': 'color: var(--kiss-orange);' },
'Note: Self-signed certificates will show browser warnings. For production, use Let\'s Encrypt or a proper CA.'),
E('div', { 'style': 'display: flex; gap: 12px; margin-top: 16px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': L.bind(function() {
ui.hideModal();
ui.showModal('Generating', [
E('p', { 'class': 'spinning' }, 'Generating certificate...')
]);
callGenerateCert(hostname).then(function(res) {
ui.hideModal();
if (res.result === 'ok') {
ui.addNotification(null, E('p', 'Certificate generated successfully'));
window.location.reload();
} else {
ui.addNotification(null, E('p', 'Failed: ' + (res.error || 'unknown')), 'error');
}
});
}, this)
}, 'Generate'),
E('button', {
'class': 'kiss-btn',
'click': function() { ui.hideModal(); }
}, 'Cancel')
])
]);
},
handleInstallCrowdsec: function() {
ui.showModal('Installing CrowdSec Rules', [
E('p', { 'class': 'spinning' }, 'Installing Icecast parsers and scenarios...')
]);
return callInstallCrowdsec().then(function(res) {
ui.hideModal();
if (res.result === 'ok') {
ui.addNotification(null, E('p', 'CrowdSec rules installed successfully'));
window.location.reload();
} else {
ui.addNotification(null, E('p', 'Failed: ' + (res.error || 'unknown')), 'error');
}
});
}
});