secubox-openwrt/package/secubox/luci-app-routes-status/htdocs/luci-static/resources/view/routes-status/overview.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

289 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require ui';
'require rpc';
'require secubox/kiss-theme';
var callStatus = rpc.declare({
object: 'luci.routes-status',
method: 'status',
params: ['offset', 'limit'],
expect: { }
});
var callSyncRoutes = rpc.declare({
object: 'luci.routes-status',
method: 'sync_routes',
expect: { }
});
var callAddRoute = rpc.declare({
object: 'luci.routes-status',
method: 'add_route',
params: ['domain', 'port'],
expect: { }
});
return view.extend({
allVhosts: [],
currentOffset: 0,
pageSize: 50,
totalVhosts: 0,
statusData: null,
load: function() {
return callStatus(0, 50);
},
// Emoji-based status pill
pill: function(emoji, label, ok) {
return E('span', {
'class': 'vhost-pill' + (ok ? ' ok' : ' warn'),
'title': label
}, emoji);
},
handleSync: function() {
ui.showModal(_('Syncing...'), [
E('p', { 'class': 'spinning' }, _('Syncing routes from HAProxy...'))
]);
callSyncRoutes().then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.addNotification(null, E('p', {}, '✅ ' + _('Routes synchronized')), 'success');
location.reload();
} else {
ui.addNotification(null, E('p', {}, '❌ ' + (res.error || 'Sync failed')), 'error');
}
});
},
handleAddRoute: function(domain, port) {
var self = this;
ui.showModal(_('Add Route'), [
E('div', { 'style': 'margin-bottom: 16px;' }, [
E('p', {}, _('Add mitmproxy route for: ') + domain),
E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin: 12px 0;' }, [
E('label', { 'style': 'font-weight: 500; color: var(--kiss-muted); min-width: 80px;' }, _('Port')),
E('div', { 'style': 'flex: 1;' }, [
E('input', { 'type': 'number', 'id': 'route-port', 'value': port || '443', 'style': 'width:80px;' })
])
])
]),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': function() {
var p = parseInt(document.getElementById('route-port').value, 10);
if (p > 0) {
ui.hideModal();
self.doAddRoute(domain, p);
}
},
'style': 'margin-left:8px;'
}, _('Add'))
])
]);
},
doAddRoute: function(domain, port) {
ui.showModal(_('Adding...'), [
E('p', { 'class': 'spinning' }, _('Adding route...'))
]);
callAddRoute(domain, port).then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.addNotification(null, E('p', {}, '✅ ' + _('Route added')), 'success');
location.reload();
} else {
ui.addNotification(null, E('p', {}, '❌ ' + (res.error || 'Failed')), 'error');
}
});
},
handleLoadMore: function() {
var self = this;
var btn = document.getElementById('load-more-btn');
var spinner = document.getElementById('load-spinner');
if (btn) btn.style.display = 'none';
if (spinner) spinner.style.display = 'block';
this.currentOffset += this.pageSize;
callStatus(this.currentOffset, this.pageSize).then(function(data) {
if (spinner) spinner.style.display = 'none';
var newVhosts = data.vhosts || [];
if (newVhosts.length > 0) {
self.allVhosts = self.allVhosts.concat(newVhosts);
var tbody = document.getElementById('vhosts-tbody');
if (tbody) {
newVhosts.forEach(function(v) {
tbody.appendChild(self.renderRow(v));
});
}
var counter = document.getElementById('vhosts-count');
if (counter) {
counter.textContent = self.allVhosts.length + '/' + self.totalVhosts;
}
}
if (self.allVhosts.length < self.totalVhosts && btn) {
btn.style.display = 'inline-block';
}
});
},
renderRow: function(v) {
var self = this;
var needsRoute = !v.has_route_out || !v.has_route_in;
return E('tr', { 'class': 'vhost-row' }, [
// Domain
E('td', {}, [
E('a', {
'href': 'https://' + v.domain,
'target': '_blank',
'class': 'vhost-domain'
}, v.domain)
]),
// Status indicators (emoji-based)
E('td', { 'class': 'vhost-status' }, [
// Routes
this.pill(v.has_route_out && v.has_route_in ? '🔗' : '⚠️',
'Routes: ' + (v.has_route_out ? 'OUT✓' : 'OUT✗') + ' ' + (v.has_route_in ? 'IN✓' : 'IN✗'),
v.has_route_out && v.has_route_in),
// SSL
this.pill(v.ssl_status === 'valid' ? '🔒' : v.ssl_status === 'expiring' ? '⏰' : '🔓',
'SSL: ' + v.ssl_status,
v.ssl_status === 'valid'),
// WAF
this.pill(v.waf_bypass ? '🚫' : '🛡️',
v.waf_bypass ? 'WAF Bypass' : 'WAF Active',
!v.waf_bypass),
// Active
this.pill(v.active ? '✅' : '⏸️',
v.active ? 'Active' : 'Inactive',
v.active)
]),
// Action
E('td', {}, [
needsRoute ? E('button', {
'class': 'btn btn-sm',
'click': function() { self.handleAddRoute(v.domain, v.backend_port || 443); }
}, '') : null
])
]);
},
render: function(data) {
var self = this;
var vhosts = data.vhosts || [];
this.allVhosts = vhosts;
this.totalVhosts = data.total || vhosts.length;
this.currentOffset = data.offset || 0;
this.statusData = data;
// Quick stats from first page
var stats = {
active: vhosts.filter(function(v) { return v.active; }).length,
missing: vhosts.filter(function(v) { return !v.has_route_out || !v.has_route_in; }).length,
bypass: vhosts.filter(function(v) { return v.waf_bypass; }).length,
ssl: vhosts.filter(function(v) { return v.ssl_status === 'valid'; }).length
};
var content = E('div', { 'class': 'vhosts-checker' }, [
// Inline styles for dark theme compatibility
E('style', {}, [
'.vhosts-checker { font-family: system-ui, sans-serif; }',
'.vhosts-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 12px; }',
'.vhosts-title { font-size: 1.4em; font-weight: 600; display: flex; align-items: center; gap: 8px; }',
'.vhosts-stats { display: flex; gap: 16px; font-size: 0.9em; opacity: 0.8; }',
'.vhosts-stat { display: flex; align-items: center; gap: 4px; }',
'.services { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }',
'.service-badge { padding: 6px 12px; border-radius: 6px; display: flex; align-items: center; gap: 6px; font-size: 0.85em; background: rgba(255,255,255,0.1); }',
'.service-badge.ok { background: rgba(76, 175, 80, 0.2); }',
'.service-badge.err { background: rgba(244, 67, 54, 0.2); }',
'.vhosts-table { width: 100%; border-collapse: collapse; }',
'.vhosts-table th { text-align: left; padding: 8px; opacity: 0.7; font-weight: 500; border-bottom: 1px solid rgba(255,255,255,0.1); }',
'.vhost-row td { padding: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); }',
'.vhost-domain { color: #64b5f6; text-decoration: none; }',
'.vhost-domain:hover { text-decoration: underline; }',
'.vhost-status { display: flex; gap: 6px; }',
'.vhost-pill { font-size: 1.1em; cursor: help; opacity: 0.9; }',
'.vhost-pill.warn { opacity: 0.6; }',
'.load-more { text-align: center; padding: 16px; }',
'.btn-sm { padding: 4px 8px; font-size: 0.9em; }'
].join('\n')),
// Header
E('div', { 'class': 'vhosts-header' }, [
E('div', { 'class': 'vhosts-title' }, [
'🔀 ', _('VHosts Checker')
]),
E('div', { 'class': 'vhosts-stats' }, [
E('span', { 'class': 'vhosts-stat' }, ['📊 ', this.totalVhosts, ' ', _('total')]),
E('span', { 'class': 'vhosts-stat' }, ['✅ ', stats.active, '+', ' ', _('active')]),
stats.missing > 0 ? E('span', { 'class': 'vhosts-stat' }, ['⚠️ ', stats.missing, ' ', _('missing routes')]) : null,
stats.bypass > 0 ? E('span', { 'class': 'vhosts-stat' }, ['🚫 ', stats.bypass, ' ', _('WAF bypass')]) : null
])
]),
// Service status badges
E('div', { 'class': 'services' }, [
E('span', { 'class': 'service-badge' + (data.haproxy_running ? ' ok' : ' err') }, [
data.haproxy_running ? '✅' : '❌', ' HAProxy'
]),
E('span', { 'class': 'service-badge' + (data.mitmproxy_running ? ' ok' : ' err') }, [
data.mitmproxy_running ? '' : '', ' mitmproxy'
]),
E('span', { 'class': 'service-badge' }, ['🖥 ', data.host_ip || '192.168.255.1']),
E('button', {
'class': 'kiss-btn kiss-btn-cyan',
'click': function() { self.handleSync(); },
'style': 'margin-left: auto;'
}, '🔄 ' + _('Sync'))
]),
// Table
vhosts.length > 0 ?
E('table', { 'class': 'vhosts-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Domain')),
E('th', {}, _('Status')),
E('th', { 'style': 'width: 50px;' }, '')
])
]),
E('tbody', { 'id': 'vhosts-tbody' }, vhosts.map(function(v) { return self.renderRow(v); }))
]) :
E('p', { 'style': 'text-align: center; opacity: 0.6; padding: 20px;' }, _('No vhosts found.')),
// Load more
this.totalVhosts > vhosts.length ? E('div', { 'class': 'load-more' }, [
E('button', {
'id': 'load-more-btn',
'class': 'kiss-btn',
'click': function() { self.handleLoadMore(); }
}, [
'📥 ', _('Load More'),
' (',
E('span', { 'id': 'vhosts-count' }, vhosts.length + '/' + this.totalVhosts),
')'
]),
E('p', { 'id': 'load-spinner', 'class': 'spinning', 'style': 'display: none;' }, _('Loading...'))
]) : null
]);
return KissTheme.wrap([content], 'admin/status/vhosts-checker');
}
});