fix(multi): Exposure fixes, MagicMirror2 port, Tor Shield health card
Exposure Manager: - Fix RPCD subshell issues in status and ssl_list methods - Fix JS views to handle both array and object API responses MagicMirror2: - Change default port from 8082 to 8085 (avoid CyberFeed conflict) - Update mm2ctl, RPCD, settings.js, dashboard.js, config Tor Shield: - Add restart method to RPCD and API - Add health status minicard (Service, Bootstrap, DNS, Kill Switch) Portal: - Add 'active-ports' section for detected services - Separate portal apps (Services) from detected ports (Active Ports) Service Detection: - Prioritize port-based identification over process name Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7566014096
commit
a1bad31807
@ -14,8 +14,10 @@ return view.extend({
|
|||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var status = data[0] || {};
|
var status = data[0] || {};
|
||||||
var conflicts = data[1] || [];
|
var conflictsResult = data[1] || {};
|
||||||
|
|
||||||
|
// Handle both direct array and wrapped object responses
|
||||||
|
var conflicts = Array.isArray(conflictsResult) ? conflictsResult : (conflictsResult.conflicts || []);
|
||||||
var services = status.services || {};
|
var services = status.services || {};
|
||||||
var tor = status.tor || {};
|
var tor = status.tor || {};
|
||||||
var ssl = status.ssl || {};
|
var ssl = status.ssl || {};
|
||||||
|
|||||||
@ -13,8 +13,10 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var services = data[0] || [];
|
var scanResult = data[0] || {};
|
||||||
var config = data[1] || [];
|
var configResult = data[1] || {};
|
||||||
|
var services = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
|
||||||
|
var config = Array.isArray(configResult) ? configResult : (configResult.known_services || []);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Inject CSS
|
// Inject CSS
|
||||||
|
|||||||
@ -13,8 +13,10 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var sslBackends = data[0] || [];
|
var sslResult = data[0] || {};
|
||||||
var allServices = data[1] || [];
|
var scanResult = data[1] || {};
|
||||||
|
var sslBackends = Array.isArray(sslResult) ? sslResult : (sslResult.backends || []);
|
||||||
|
var allServices = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Inject CSS
|
// Inject CSS
|
||||||
|
|||||||
@ -13,8 +13,10 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var torServices = data[0] || [];
|
var torResult = data[0] || {};
|
||||||
var allServices = data[1] || [];
|
var scanResult = data[1] || {};
|
||||||
|
var torServices = Array.isArray(torResult) ? torResult : (torResult.services || []);
|
||||||
|
var allServices = Array.isArray(scanResult) ? scanResult : (scanResult.services || []);
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Inject CSS
|
// Inject CSS
|
||||||
|
|||||||
@ -137,23 +137,32 @@ case "$1" in
|
|||||||
json_close_array
|
json_close_array
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
# HAProxy SSL backends
|
# HAProxy SSL backends - use temp file to avoid subshell
|
||||||
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
||||||
ssl_count=0
|
ssl_count=0
|
||||||
[ -f "$HAPROXY_CONFIG" ] && ssl_count=$(grep -c "^backend.*_backend$" "$HAPROXY_CONFIG" 2>/dev/null || echo 0)
|
[ -f "$HAPROXY_CONFIG" ] && ssl_count=$(grep -c "^backend.*_backend$" "$HAPROXY_CONFIG" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
json_add_object "ssl"
|
TMP_SSL="/tmp/exposure_ssl_$$"
|
||||||
json_add_int "count" "$ssl_count"
|
|
||||||
json_add_array "backends"
|
|
||||||
if [ -f "$HAPROXY_CONFIG" ]; then
|
if [ -f "$HAPROXY_CONFIG" ]; then
|
||||||
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
||||||
backend=$(echo "$line" | awk '{print $2}' | sed 's/_backend$//')
|
backend=$(echo "$line" | awk '{print $2}' | sed 's/_backend$//')
|
||||||
domain=$(grep "acl host_${backend} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
domain=$(grep "acl host_${backend} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
||||||
|
echo "$backend ${domain:-N/A}"
|
||||||
|
done > "$TMP_SSL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_add_object "ssl"
|
||||||
|
json_add_int "count" "$ssl_count"
|
||||||
|
json_add_array "backends"
|
||||||
|
if [ -f "$TMP_SSL" ]; then
|
||||||
|
while read backend domain; do
|
||||||
|
[ -z "$backend" ] && continue
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
json_add_string "service" "$backend"
|
json_add_string "service" "$backend"
|
||||||
json_add_string "domain" "${domain:-N/A}"
|
json_add_string "domain" "$domain"
|
||||||
json_close_object
|
json_close_object
|
||||||
done
|
done < "$TMP_SSL"
|
||||||
|
rm -f "$TMP_SSL"
|
||||||
fi
|
fi
|
||||||
json_close_array
|
json_close_array
|
||||||
json_close_object
|
json_close_object
|
||||||
@ -195,23 +204,32 @@ case "$1" in
|
|||||||
|
|
||||||
ssl_list)
|
ssl_list)
|
||||||
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
HAPROXY_CONFIG="/srv/lxc/haproxy/rootfs/etc/haproxy/haproxy.cfg"
|
||||||
|
TMP_SSLLIST="/tmp/exposure_ssllist_$$"
|
||||||
|
|
||||||
json_init
|
# Extract backend info to temp file to avoid subshell issues
|
||||||
json_add_array "backends"
|
|
||||||
|
|
||||||
if [ -f "$HAPROXY_CONFIG" ]; then
|
if [ -f "$HAPROXY_CONFIG" ]; then
|
||||||
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
grep -E "^backend .+_backend$" "$HAPROXY_CONFIG" 2>/dev/null | while read line; do
|
||||||
backend=$(echo "$line" | awk '{print $2}')
|
backend=$(echo "$line" | awk '{print $2}')
|
||||||
service=$(echo "$backend" | sed 's/_backend$//')
|
service=$(echo "$backend" | sed 's/_backend$//')
|
||||||
domain=$(grep "acl host_${service} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
domain=$(grep "acl host_${service} " "$HAPROXY_CONFIG" 2>/dev/null | awk '{print $NF}')
|
||||||
server=$(grep -A5 "backend $backend" "$HAPROXY_CONFIG" 2>/dev/null | grep "server " | awk '{print $3}')
|
server=$(grep -A5 "backend $backend" "$HAPROXY_CONFIG" 2>/dev/null | grep "server " | awk '{print $3}')
|
||||||
|
echo "$service|${domain:-N/A}|${server:-N/A}"
|
||||||
|
done > "$TMP_SSLLIST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_init
|
||||||
|
json_add_array "backends"
|
||||||
|
|
||||||
|
if [ -f "$TMP_SSLLIST" ]; then
|
||||||
|
while IFS='|' read service domain server; do
|
||||||
|
[ -z "$service" ] && continue
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
json_add_string "service" "$service"
|
json_add_string "service" "$service"
|
||||||
json_add_string "domain" "${domain:-N/A}"
|
json_add_string "domain" "$domain"
|
||||||
json_add_string "backend" "${server:-N/A}"
|
json_add_string "backend" "$server"
|
||||||
json_close_object
|
json_close_object
|
||||||
done
|
done < "$TMP_SSLLIST"
|
||||||
|
rm -f "$TMP_SSLLIST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
json_close_array
|
json_close_array
|
||||||
|
|||||||
@ -126,7 +126,7 @@ return view.extend({
|
|||||||
]),
|
]),
|
||||||
E('div', { 'class': 'mm2-card' }, [
|
E('div', { 'class': 'mm2-card' }, [
|
||||||
E('div', { 'class': 'mm2-stat' }, [
|
E('div', { 'class': 'mm2-stat' }, [
|
||||||
E('div', { 'class': 'mm2-stat-value' }, ':' + (config.port || 8082)),
|
E('div', { 'class': 'mm2-stat-value' }, ':' + (config.port || 8085)),
|
||||||
E('div', { 'class': 'mm2-stat-label' }, _('Web Port'))
|
E('div', { 'class': 'mm2-stat-label' }, _('Web Port'))
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|||||||
@ -67,7 +67,7 @@ return view.extend({
|
|||||||
|
|
||||||
o = s.option(form.Value, 'port', _('Web Port'));
|
o = s.option(form.Value, 'port', _('Web Port'));
|
||||||
o.datatype = 'port';
|
o.datatype = 'port';
|
||||||
o.default = '8082';
|
o.default = '8085';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.option(form.Value, 'address', _('Listen Address'));
|
o = s.option(form.Value, 'address', _('Listen Address'));
|
||||||
|
|||||||
@ -26,7 +26,7 @@ get_status() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
|
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
|
||||||
local port=$(uci -q get magicmirror2.main.port || echo "8082")
|
local port=$(uci -q get magicmirror2.main.port || echo "8085")
|
||||||
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
||||||
|
|
||||||
[ "$running" = "1" ] && web_url="http://${router_ip}:${port}"
|
[ "$running" = "1" ] && web_url="http://${router_ip}:${port}"
|
||||||
@ -54,7 +54,7 @@ EOF
|
|||||||
# Get main configuration
|
# Get main configuration
|
||||||
get_config() {
|
get_config() {
|
||||||
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
|
local enabled=$(uci -q get magicmirror2.main.enabled || echo "0")
|
||||||
local port=$(uci -q get magicmirror2.main.port || echo "8082")
|
local port=$(uci -q get magicmirror2.main.port || echo "8085")
|
||||||
local address=$(uci -q get magicmirror2.main.address || echo "0.0.0.0")
|
local address=$(uci -q get magicmirror2.main.address || echo "0.0.0.0")
|
||||||
local data_path=$(uci -q get magicmirror2.main.data_path || echo "/srv/magicmirror2")
|
local data_path=$(uci -q get magicmirror2.main.data_path || echo "/srv/magicmirror2")
|
||||||
local memory_limit=$(uci -q get magicmirror2.main.memory_limit || echo "512M")
|
local memory_limit=$(uci -q get magicmirror2.main.memory_limit || echo "512M")
|
||||||
@ -327,7 +327,7 @@ set_config() {
|
|||||||
# Get web URL for iframe
|
# Get web URL for iframe
|
||||||
get_web_url() {
|
get_web_url() {
|
||||||
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1")
|
||||||
local port=$(uci -q get magicmirror2.main.port || echo "8082")
|
local port=$(uci -q get magicmirror2.main.port || echo "8085")
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
|
|||||||
@ -440,6 +440,13 @@ return baseclass.extend({
|
|||||||
icon: '\ud83d\udce6',
|
icon: '\ud83d\udce6',
|
||||||
path: 'admin/secubox/services',
|
path: 'admin/secubox/services',
|
||||||
order: 8
|
order: 8
|
||||||
|
},
|
||||||
|
'active-ports': {
|
||||||
|
id: 'active-ports',
|
||||||
|
name: 'Active Ports',
|
||||||
|
icon: '\ud83d\udd0c',
|
||||||
|
path: 'admin/secubox/services',
|
||||||
|
order: 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -156,7 +156,8 @@ return view.extend({
|
|||||||
this.renderNetworkSection(),
|
this.renderNetworkSection(),
|
||||||
this.renderMonitoringSection(),
|
this.renderMonitoringSection(),
|
||||||
this.renderSystemSection(),
|
this.renderSystemSection(),
|
||||||
this.renderServicesSection()
|
this.renderServicesAppsSection(),
|
||||||
|
this.renderActivePortsSection()
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -171,7 +172,7 @@ return view.extend({
|
|||||||
var sections = portal.getSections();
|
var sections = portal.getSections();
|
||||||
// Sections that link to other pages vs tabs within portal
|
// Sections that link to other pages vs tabs within portal
|
||||||
var linkSections = ['portal', 'hub', 'admin'];
|
var linkSections = ['portal', 'hub', 'admin'];
|
||||||
var tabSections = ['security', 'network', 'monitoring', 'system', 'services'];
|
var tabSections = ['security', 'network', 'monitoring', 'system', 'services', 'active-ports'];
|
||||||
|
|
||||||
return E('div', { 'class': 'sb-portal-header' }, [
|
return E('div', { 'class': 'sb-portal-header' }, [
|
||||||
// Brand
|
// Brand
|
||||||
@ -443,7 +444,13 @@ return view.extend({
|
|||||||
'System administration and configuration tools', apps);
|
'System administration and configuration tools', apps);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderServicesSection: function() {
|
renderServicesAppsSection: function() {
|
||||||
|
var apps = portal.getInstalledAppsBySection('services', this.installedApps);
|
||||||
|
return this.renderAppSection('services', 'Services',
|
||||||
|
'Application services running on your network', apps);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderActivePortsSection: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var services = this.detectedServices || [];
|
var services = this.detectedServices || [];
|
||||||
|
|
||||||
@ -514,9 +521,9 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (serviceCards.length === 0) {
|
if (serviceCards.length === 0) {
|
||||||
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [
|
return E('div', { 'class': 'sb-portal-section', 'data-section': 'active-ports' }, [
|
||||||
E('div', { 'class': 'sb-section-header' }, [
|
E('div', { 'class': 'sb-section-header' }, [
|
||||||
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'),
|
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Ports'),
|
||||||
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sb-section-empty' }, [
|
E('div', { 'class': 'sb-section-empty' }, [
|
||||||
@ -527,9 +534,9 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return E('div', { 'class': 'sb-portal-section', 'data-section': 'services' }, [
|
return E('div', { 'class': 'sb-portal-section', 'data-section': 'active-ports' }, [
|
||||||
E('div', { 'class': 'sb-section-header' }, [
|
E('div', { 'class': 'sb-section-header' }, [
|
||||||
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Services'),
|
E('h2', { 'class': 'sb-section-title' }, '🔌 Active Ports'),
|
||||||
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
E('p', { 'class': 'sb-section-subtitle' }, 'Detected services listening on network ports')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sb-app-grid' }, serviceCards)
|
E('div', { 'class': 'sb-app-grid' }, serviceCards)
|
||||||
|
|||||||
@ -27,6 +27,12 @@ var callDisable = rpc.declare({
|
|||||||
expect: { success: false }
|
expect: { success: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callRestart = rpc.declare({
|
||||||
|
object: 'luci.tor-shield',
|
||||||
|
method: 'restart',
|
||||||
|
expect: { success: false }
|
||||||
|
});
|
||||||
|
|
||||||
var callCircuits = rpc.declare({
|
var callCircuits = rpc.declare({
|
||||||
object: 'luci.tor-shield',
|
object: 'luci.tor-shield',
|
||||||
method: 'circuits',
|
method: 'circuits',
|
||||||
@ -161,6 +167,7 @@ return baseclass.extend({
|
|||||||
getStatus: callStatus,
|
getStatus: callStatus,
|
||||||
enable: callEnable,
|
enable: callEnable,
|
||||||
disable: callDisable,
|
disable: callDisable,
|
||||||
|
restart: callRestart,
|
||||||
getCircuits: callCircuits,
|
getCircuits: callCircuits,
|
||||||
newIdentity: callNewIdentity,
|
newIdentity: callNewIdentity,
|
||||||
checkLeaks: callCheckLeaks,
|
checkLeaks: callCheckLeaks,
|
||||||
|
|||||||
@ -87,6 +87,27 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Handle restart
|
||||||
|
handleRestart: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
ui.showModal(_('Restart Tor Shield'), [
|
||||||
|
E('p', { 'class': 'spinning' }, _('Restarting Tor Shield service...'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
api.restart().then(function(result) {
|
||||||
|
ui.hideModal();
|
||||||
|
if (result.success) {
|
||||||
|
ui.addNotification(null, E('p', _('Tor Shield is restarting. Please wait for bootstrap to complete.')), 'info');
|
||||||
|
} else {
|
||||||
|
ui.addNotification(null, E('p', result.error || _('Failed to restart')), 'error');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null, E('p', _('Error: %s').format(err.message || err)), 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Handle leak test
|
// Handle leak test
|
||||||
handleLeakTest: function() {
|
handleLeakTest: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -373,6 +394,54 @@ return view.extend({
|
|||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
// Health Status Minicard
|
||||||
|
E('div', { 'class': 'tor-health-card', 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-bottom: 20px;' }, [
|
||||||
|
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
|
||||||
|
E('div', {
|
||||||
|
'class': 'tor-health-indicator',
|
||||||
|
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (isProtected ? '#10b981' : isConnecting ? '#f59e0b' : '#6b7280') + '; box-shadow: 0 0 8px ' + (isProtected ? '#10b981' : isConnecting ? '#f59e0b' : 'transparent') + ';'
|
||||||
|
}),
|
||||||
|
E('div', {}, [
|
||||||
|
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Service')),
|
||||||
|
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
|
||||||
|
status.running ? _('Running') : _('Stopped'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
|
||||||
|
E('div', {
|
||||||
|
'class': 'tor-health-indicator',
|
||||||
|
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.bootstrap >= 100 ? '#10b981' : status.bootstrap > 0 ? '#f59e0b' : '#6b7280') + '; box-shadow: 0 0 8px ' + (status.bootstrap >= 100 ? '#10b981' : status.bootstrap > 0 ? '#f59e0b' : 'transparent') + ';'
|
||||||
|
}),
|
||||||
|
E('div', {}, [
|
||||||
|
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Bootstrap')),
|
||||||
|
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
|
||||||
|
status.bootstrap >= 100 ? _('Complete') : status.bootstrap + '%')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
|
||||||
|
E('div', {
|
||||||
|
'class': 'tor-health-indicator',
|
||||||
|
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.dns_over_tor ? '#10b981' : '#f59e0b') + '; box-shadow: 0 0 8px ' + (status.dns_over_tor ? '#10b981' : '#f59e0b') + ';'
|
||||||
|
}),
|
||||||
|
E('div', {}, [
|
||||||
|
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('DNS')),
|
||||||
|
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
|
||||||
|
status.dns_over_tor ? _('Protected') : _('Exposed'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'tor-health-item', 'style': 'display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--tor-bg-card, #1a1a24); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);' }, [
|
||||||
|
E('div', {
|
||||||
|
'class': 'tor-health-indicator',
|
||||||
|
'style': 'width: 12px; height: 12px; border-radius: 50%; background: ' + (status.kill_switch ? '#10b981' : '#6b7280') + '; box-shadow: 0 0 8px ' + (status.kill_switch ? '#10b981' : 'transparent') + ';'
|
||||||
|
}),
|
||||||
|
E('div', {}, [
|
||||||
|
E('div', { 'style': 'font-size: 14px; font-weight: 600; color: var(--tor-text, #fff);' }, _('Kill Switch')),
|
||||||
|
E('div', { 'style': 'font-size: 12px; color: var(--tor-text-muted, #a0a0b0);' },
|
||||||
|
status.kill_switch ? _('Active') : _('Disabled'))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
// Actions Card
|
// Actions Card
|
||||||
E('div', { 'class': 'tor-card' }, [
|
E('div', { 'class': 'tor-card' }, [
|
||||||
E('div', { 'class': 'tor-card-header' }, [
|
E('div', { 'class': 'tor-card-header' }, [
|
||||||
@ -393,6 +462,11 @@ return view.extend({
|
|||||||
'click': L.bind(this.handleLeakTest, this),
|
'click': L.bind(this.handleLeakTest, this),
|
||||||
'disabled': !isActive
|
'disabled': !isActive
|
||||||
}, ['\uD83D\uDD0D ', _('Leak Test')]),
|
}, ['\uD83D\uDD0D ', _('Leak Test')]),
|
||||||
|
E('button', {
|
||||||
|
'class': 'tor-btn tor-btn-warning',
|
||||||
|
'click': L.bind(this.handleRestart, this),
|
||||||
|
'disabled': !status.enabled
|
||||||
|
}, ['\u21BB ', _('Restart')]),
|
||||||
E('a', {
|
E('a', {
|
||||||
'class': 'tor-btn',
|
'class': 'tor-btn',
|
||||||
'href': L.url('admin', 'services', 'tor-shield', 'circuits')
|
'href': L.url('admin', 'services', 'tor-shield', 'circuits')
|
||||||
|
|||||||
@ -707,9 +707,21 @@ save_settings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Main dispatcher
|
# Main dispatcher
|
||||||
|
# Restart Tor Shield service
|
||||||
|
do_restart() {
|
||||||
|
json_init
|
||||||
|
|
||||||
|
/etc/init.d/tor-shield restart >/dev/null 2>&1 &
|
||||||
|
|
||||||
|
json_add_boolean "success" 1
|
||||||
|
json_add_string "message" "Tor Shield restarting"
|
||||||
|
|
||||||
|
json_dump
|
||||||
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
list)
|
||||||
echo '{"status":{},"enable":{"preset":"str"},"disable":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"}}'
|
echo '{"status":{},"enable":{"preset":"str"},"disable":{},"restart":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"}}'
|
||||||
;;
|
;;
|
||||||
call)
|
call)
|
||||||
case "$2" in
|
case "$2" in
|
||||||
@ -722,6 +734,9 @@ case "$1" in
|
|||||||
disable)
|
disable)
|
||||||
do_disable
|
do_disable
|
||||||
;;
|
;;
|
||||||
|
restart)
|
||||||
|
do_restart
|
||||||
|
;;
|
||||||
circuits)
|
circuits)
|
||||||
get_circuits
|
get_circuits
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -60,7 +60,7 @@ define Package/secubox-app-magicmirror2/postinst
|
|||||||
echo " mm2ctl install"
|
echo " mm2ctl install"
|
||||||
echo " /etc/init.d/magicmirror2 start"
|
echo " /etc/init.d/magicmirror2 start"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Web interface: http://<router-ip>:8082"
|
echo "Web interface: http://<router-ip>:8085"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To manage modules:"
|
echo "To manage modules:"
|
||||||
echo " mm2ctl module list"
|
echo " mm2ctl module list"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
config magicmirror2 'main'
|
config magicmirror2 'main'
|
||||||
option enabled '0'
|
option enabled '0'
|
||||||
option port '8082'
|
option port '8085'
|
||||||
option address '0.0.0.0'
|
option address '0.0.0.0'
|
||||||
option data_path '/srv/magicmirror2'
|
option data_path '/srv/magicmirror2'
|
||||||
option memory_limit '512M'
|
option memory_limit '512M'
|
||||||
|
|||||||
@ -51,7 +51,7 @@ Examples:
|
|||||||
mm2ctl module list
|
mm2ctl module list
|
||||||
mm2ctl config
|
mm2ctl config
|
||||||
|
|
||||||
Web Interface: http://<router-ip>:8082
|
Web Interface: http://<router-ip>:8085
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }
|
|||||||
|
|
||||||
# Load configuration with defaults
|
# Load configuration with defaults
|
||||||
load_config() {
|
load_config() {
|
||||||
port="$(uci_get main.port || echo 8082)"
|
port="$(uci_get main.port || echo 8085)"
|
||||||
address="$(uci_get main.address || echo 0.0.0.0)"
|
address="$(uci_get main.address || echo 0.0.0.0)"
|
||||||
data_path="$(uci_get main.data_path || echo /srv/magicmirror2)"
|
data_path="$(uci_get main.data_path || echo /srv/magicmirror2)"
|
||||||
memory_limit="$(uci_get main.memory_limit || echo 512M)"
|
memory_limit="$(uci_get main.memory_limit || echo 512M)"
|
||||||
@ -255,7 +255,7 @@ lxc_create_docker_rootfs() {
|
|||||||
cat >> "$rootfs/opt/start-mm2.sh" << 'START'
|
cat >> "$rootfs/opt/start-mm2.sh" << 'START'
|
||||||
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
|
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
|
||||||
export NODE_ENV=production
|
export NODE_ENV=production
|
||||||
export MM_PORT="${MM2_PORT:-8082}"
|
export MM_PORT="${MM2_PORT:-8085}"
|
||||||
export MM_ADDRESS="${MM2_ADDRESS:-0.0.0.0}"
|
export MM_ADDRESS="${MM2_ADDRESS:-0.0.0.0}"
|
||||||
|
|
||||||
MM_DIR="/opt/magic_mirror"
|
MM_DIR="/opt/magic_mirror"
|
||||||
|
|||||||
@ -1200,40 +1200,19 @@ case "$1" in
|
|||||||
addr=$(echo "$local" | sed 's/:[^:]*$//')
|
addr=$(echo "$local" | sed 's/:[^:]*$//')
|
||||||
name=""; icon=""; category="other"; path=""
|
name=""; icon=""; category="other"; path=""
|
||||||
|
|
||||||
# First: identify by process name (most accurate)
|
# First: identify by well-known port (most reliable for multi-service ports)
|
||||||
case "$proc" in
|
|
||||||
sshd|dropbear) name="SSH"; icon="lock"; category="system" ;;
|
|
||||||
dnsmasq|named|unbound) name="DNS"; icon="globe"; category="system" ;;
|
|
||||||
haproxy) name="HAProxy"; icon="arrow"; category="proxy" ;;
|
|
||||||
nginx|uhttpd) name="Web Server"; icon="settings"; category="system" ;;
|
|
||||||
gitea) name="Gitea"; icon="git"; path=":$port"; category="app" ;;
|
|
||||||
hexo|node) [ "$port" = "4000" ] && { name="HexoJS"; icon="blog"; path=":$port"; category="app"; } ;;
|
|
||||||
crowdsec|lapi) name="CrowdSec"; icon="security"; category="security" ;;
|
|
||||||
netifyd) name="Netifyd"; icon="chart"; path=":$port"; category="monitoring" ;;
|
|
||||||
streamlit|python*)
|
|
||||||
[ "$port" = "8501" ] && { name="Streamlit"; icon="app"; path=":$port"; category="app"; }
|
|
||||||
;;
|
|
||||||
slimserver|squeezeboxserver) name="Lyrion"; icon="music"; path=":$port"; category="media" ;;
|
|
||||||
tor) name="Tor"; icon="onion"; category="privacy" ;;
|
|
||||||
cyberfeed*) name="CyberFeed"; icon="feed"; path=":$port"; category="app" ;;
|
|
||||||
metabolizer*) name="Metabolizer"; icon="blog"; path=":$port"; category="app" ;;
|
|
||||||
magicmirror*|electron) name="MagicMirror"; icon="app"; path=":$port"; category="app" ;;
|
|
||||||
picobrew*) name="PicoBrew"; icon="app"; path=":$port"; category="app" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Fallback: identify by port number if process didn't match
|
|
||||||
if [ -z "$name" ]; then
|
|
||||||
case "$port" in
|
case "$port" in
|
||||||
22) name="SSH"; icon="lock"; category="system" ;;
|
22) name="SSH"; icon="lock"; category="system" ;;
|
||||||
53) name="DNS"; icon="globe"; category="system" ;;
|
53) name="DNS"; icon="globe"; category="system" ;;
|
||||||
80) name="HTTP"; icon="arrow"; path="/"; category="proxy" ;;
|
80) name="HTTP"; icon="arrow"; path="/"; category="proxy" ;;
|
||||||
443) name="HTTPS"; icon="shield"; path="/"; category="proxy" ;;
|
443) name="HTTPS"; icon="shield"; path="/"; category="proxy" ;;
|
||||||
|
2222) name="Gitea SSH"; icon="git"; category="app" ;;
|
||||||
3000) name="Gitea"; icon="git"; path=":3000"; category="app" ;;
|
3000) name="Gitea"; icon="git"; path=":3000"; category="app" ;;
|
||||||
|
3483) name="Squeezebox"; icon="music"; category="media" ;;
|
||||||
4000) name="HexoJS"; icon="blog"; path=":4000"; category="app" ;;
|
4000) name="HexoJS"; icon="blog"; path=":4000"; category="app" ;;
|
||||||
6060) name="CrowdSec LAPI"; icon="security"; category="security" ;;
|
6060) name="CrowdSec LAPI"; icon="security"; category="security" ;;
|
||||||
8080) name="Web App"; icon="app"; path=":8080"; category="app" ;;
|
|
||||||
8081) name="LuCI"; icon="settings"; path=":8081"; category="system" ;;
|
8081) name="LuCI"; icon="settings"; path=":8081"; category="system" ;;
|
||||||
8082) name="CyberFeed"; icon="feed"; path=":8082"; category="app" ;;
|
8085) name="MagicMirror2"; icon="app"; path=":8085"; category="app" ;;
|
||||||
8086) name="Netifyd"; icon="chart"; path=":8086"; category="monitoring" ;;
|
8086) name="Netifyd"; icon="chart"; path=":8086"; category="monitoring" ;;
|
||||||
8404) name="HAProxy Stats"; icon="stats"; path=":8404/stats"; category="monitoring" ;;
|
8404) name="HAProxy Stats"; icon="stats"; path=":8404/stats"; category="monitoring" ;;
|
||||||
8444) name="LuCI HTTPS"; icon="admin"; path=":8444"; category="system" ;;
|
8444) name="LuCI HTTPS"; icon="admin"; path=":8444"; category="system" ;;
|
||||||
@ -1241,8 +1220,27 @@ case "$1" in
|
|||||||
9000) name="Lyrion"; icon="music"; path=":9000"; category="media" ;;
|
9000) name="Lyrion"; icon="music"; path=":9000"; category="media" ;;
|
||||||
9050) name="Tor SOCKS"; icon="onion"; category="privacy" ;;
|
9050) name="Tor SOCKS"; icon="onion"; category="privacy" ;;
|
||||||
9090) name="Lyrion CLI"; icon="music"; category="media" ;;
|
9090) name="Lyrion CLI"; icon="music"; category="media" ;;
|
||||||
2222) name="Gitea SSH"; icon="git"; category="app" ;;
|
esac
|
||||||
3483) name="Squeezebox"; icon="music"; category="media" ;;
|
|
||||||
|
# Fallback: identify by process name if port didn't match
|
||||||
|
if [ -z "$name" ]; then
|
||||||
|
case "$proc" in
|
||||||
|
sshd|dropbear) name="SSH"; icon="lock"; category="system" ;;
|
||||||
|
dnsmasq|named|unbound) name="DNS"; icon="globe"; category="system" ;;
|
||||||
|
haproxy) name="HAProxy"; icon="arrow"; category="proxy" ;;
|
||||||
|
nginx|uhttpd) name="Web Server"; icon="settings"; category="system" ;;
|
||||||
|
gitea) name="Gitea"; icon="git"; path=":$port"; category="app" ;;
|
||||||
|
hexo|node) name="HexoJS"; icon="blog"; path=":$port"; category="app" ;;
|
||||||
|
crowdsec|lapi) name="CrowdSec"; icon="security"; category="security" ;;
|
||||||
|
netifyd) name="Netifyd"; icon="chart"; path=":$port"; category="monitoring" ;;
|
||||||
|
slimserver|squeezeboxserver) name="Lyrion"; icon="music"; path=":$port"; category="media" ;;
|
||||||
|
tor) name="Tor"; icon="onion"; category="privacy" ;;
|
||||||
|
cyberfeed*) name="CyberFeed"; icon="feed"; path=":$port"; category="app" ;;
|
||||||
|
metabolizer*) name="Metabolizer"; icon="blog"; path=":$port"; category="app" ;;
|
||||||
|
magicmirror*|electron) name="MagicMirror"; icon="app"; path=":$port"; category="app" ;;
|
||||||
|
picobrew*) name="PicoBrew"; icon="app"; path=":$port"; category="app" ;;
|
||||||
|
streamlit) name="Streamlit"; icon="app"; path=":$port"; category="app" ;;
|
||||||
|
python*) name="Python App"; icon="app"; path=":$port"; category="app" ;;
|
||||||
*) name="$proc"; icon=""; category="other"; path=":$port" ;;
|
*) name="$proc"; icon=""; category="other"; path=":$port" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user