fix(luci): Performance and UX improvements for exposure and portal

- Optimize exposure RPCD: O(n) single-pass awk parsing for vhost_list
  and ssl_list (fixes XHR timeout on 200+ vhosts)
- Fix portal tree URLs: Use get_menu_path() to read actual LuCI menu
  paths from JSON instead of hardcoded paths
- Add Downloads category to portal tree (torrent, droplet patterns)
- Add new apps to System category (config-vault, reporter, smtp-relay,
  rtty, dpi-dual, metacatalog)
- Enhance KISS theme menu: Add Downloads, Monitoring categories
- Fix Lyrion URL: Use HTTPS vhost instead of dynamic port URL

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-16 07:35:18 +01:00
parent b08e71fa7f
commit d0cd42e2a1
4 changed files with 290 additions and 138 deletions

View File

@ -270,44 +270,52 @@ case "$1" in
;;
ssl_list)
TMP_SSLLIST="/tmp/exposure_ssllist_$$"
> "$TMP_SSLLIST"
# Read from HAProxy UCI config (vhosts with their backends)
for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
domain=$(uci -q get "haproxy.${vhost}.domain")
backend=$(uci -q get "haproxy.${vhost}.backend")
enabled=$(uci -q get "haproxy.${vhost}.enabled")
[ "$enabled" != "1" ] && continue
[ -z "$domain" ] && continue
# Get server address from backend config
server=""
if [ -n "$backend" ]; then
server=$(uci -q get "haproxy.${backend}.server" 2>/dev/null | head -1 | awk '{print $2}')
fi
echo "${backend:-$vhost}|${domain}|${server:-N/A}" >> "$TMP_SSLLIST"
done
json_init
json_add_array "backends"
if [ -s "$TMP_SSLLIST" ]; then
while IFS='|' read service domain server; do
[ -z "$service" ] && continue
json_add_object ""
json_add_string "service" "$service"
json_add_string "domain" "$domain"
json_add_string "backend" "$server"
json_close_object
done < "$TMP_SSLLIST"
fi
rm -f "$TMP_SSLLIST"
json_close_array
json_dump
# OPTIMIZED: Single-pass awk parsing
uci show haproxy 2>/dev/null | awk '
BEGIN {
printf "{\"backends\":["
first = 1
}
/=vhost$/ {
if (vh_domain != "" && vh_enabled == "1") {
if (first == 0) printf ","
first = 0
printf "{\"service\":\"%s\",\"domain\":\"%s\",\"backend\":\"%s\"}",
(vh_backend != "" ? vh_backend : vh_id), vh_domain, "N/A"
}
gsub(/^haproxy\./, "", $0)
gsub(/=vhost$/, "", $0)
vh_id = $0
vh_domain = ""
vh_backend = ""
vh_enabled = "0"
in_vhost = 1
}
/=backend$/ || /=server$/ { in_vhost = 0 }
/\.domain=/ && in_vhost {
gsub(/.*\.domain=/, "", $0)
gsub(/'\''/, "", $0)
vh_domain = $0
}
/\.backend=/ && in_vhost {
gsub(/.*\.backend=/, "", $0)
gsub(/'\''/, "", $0)
vh_backend = $0
}
/\.enabled=/ && in_vhost {
gsub(/.*\.enabled=/, "", $0)
gsub(/'\''/, "", $0)
vh_enabled = $0
}
END {
if (vh_domain != "" && vh_enabled == "1") {
if (first == 0) printf ","
printf "{\"service\":\"%s\",\"domain\":\"%s\",\"backend\":\"%s\"}",
(vh_backend != "" ? vh_backend : vh_id), vh_domain, "N/A"
}
printf "]}"
}
'
;;
get_config)
@ -479,80 +487,171 @@ case "$1" in
;;
vhost_list)
json_init
# OPTIMIZED: Single-pass awk parsing instead of O(n²) uci calls
uci show haproxy 2>/dev/null | awk '
BEGIN {
printf "{\"haproxy\":["
first_vh = 1
}
# HAProxy vhosts (domain -> backend with resolved port)
json_add_array "haproxy"
for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
domain=$(uci -q get "haproxy.${vhost}.domain")
backend=$(uci -q get "haproxy.${vhost}.backend")
enabled=$(uci -q get "haproxy.${vhost}.enabled")
ssl=$(uci -q get "haproxy.${vhost}.ssl")
acme=$(uci -q get "haproxy.${vhost}.acme")
# Collect server backend->port mappings
/\.backend=/ && /=server$/ == 0 {
# This is a vhost or backend .backend= line, skip for server collection
}
/=server$/ {
gsub(/^haproxy\./, "", $0)
gsub(/=server$/, "", $0)
current_srv = $0
}
/^haproxy\.[^.]+\.backend=/ && prev_type == "server" {
gsub(/^haproxy\.[^.]+\.backend=/, "", $0)
gsub(/'\''/, "", $0)
srv_backends[current_srv] = $0
}
/^haproxy\.[^.]+\.port=/ && prev_type == "server" {
gsub(/^haproxy\.[^.]+\.port=/, "", $0)
gsub(/'\''/, "", $0)
srv_ports[current_srv] = $0
}
[ -z "$domain" ] && continue
# Track section type
/=vhost$/ { prev_type = "vhost" }
/=server$/ { prev_type = "server" }
/=backend$/ { prev_type = "backend" }
# Check for original_backend (when mitmproxy is intercepting)
original_backend=$(uci -q get "haproxy.${vhost}.original_backend")
resolve_backend="${original_backend:-$backend}"
# Resolve backend port from the target backend
backend_port=""
if [ -n "$resolve_backend" ]; then
# Try inline server option: 'name IP:PORT check'
server_line=$(uci -q get "haproxy.${resolve_backend}.server" 2>/dev/null)
if [ -n "$server_line" ]; then
backend_port=$(echo "$server_line" | awk '{print $2}' | grep -o ':[0-9]*' | tr -d ':')
fi
# Try server sections referencing this backend
if [ -z "$backend_port" ]; then
for srv in $(uci show haproxy 2>/dev/null | grep "=server$" | cut -d'.' -f2 | cut -d'=' -f1); do
srv_backend=$(uci -q get "haproxy.${srv}.backend")
if [ "$srv_backend" = "$resolve_backend" ]; then
backend_port=$(uci -q get "haproxy.${srv}.port")
# Process vhosts
/=vhost$/ {
# Output previous vhost
if (vh_id != "" && vh_domain != "") {
if (first_vh == 0) printf ","
first_vh = 0
# Resolve backend port
resolve_be = (vh_orig_be != "") ? vh_orig_be : vh_backend
port = 0
for (s in srv_backends) {
if (srv_backends[s] == resolve_be && srv_ports[s] != "") {
port = srv_ports[s]
break
fi
done
fi
fi
}
}
printf "{\"id\":\"%s\",\"domain\":\"%s\",\"backend\":\"%s\",\"backend_port\":%d,\"ssl\":%s,\"acme\":%s,\"enabled\":%s}",
vh_id, vh_domain, resolve_be, port,
(vh_ssl == "1" ? "true" : "false"),
(vh_acme == "1" ? "true" : "false"),
(vh_enabled == "1" ? "true" : "false")
}
# Start new vhost
gsub(/^haproxy\./, "", $0)
gsub(/=vhost$/, "", $0)
vh_id = $0
vh_domain = ""
vh_backend = ""
vh_orig_be = ""
vh_ssl = "0"
vh_acme = "0"
vh_enabled = "0"
}
/\.domain=/ && prev_type == "vhost" {
gsub(/.*\.domain=/, "", $0)
gsub(/'\''/, "", $0)
vh_domain = $0
}
/\.backend=/ && prev_type == "vhost" {
gsub(/.*\.backend=/, "", $0)
gsub(/'\''/, "", $0)
vh_backend = $0
}
/\.original_backend=/ && prev_type == "vhost" {
gsub(/.*\.original_backend=/, "", $0)
gsub(/'\''/, "", $0)
vh_orig_be = $0
}
/\.ssl=/ && prev_type == "vhost" {
gsub(/.*\.ssl=/, "", $0)
gsub(/'\''/, "", $0)
vh_ssl = $0
}
/\.acme=/ && prev_type == "vhost" {
gsub(/.*\.acme=/, "", $0)
gsub(/'\''/, "", $0)
vh_acme = $0
}
/\.enabled=/ && prev_type == "vhost" {
gsub(/.*\.enabled=/, "", $0)
gsub(/'\''/, "", $0)
vh_enabled = $0
}
json_add_object ""
json_add_string "id" "$vhost"
json_add_string "domain" "$domain"
json_add_string "backend" "${resolve_backend:-${backend:-}}"
json_add_int "backend_port" "${backend_port:-0}"
json_add_boolean "ssl" "${ssl:-0}"
json_add_boolean "acme" "${acme:-0}"
json_add_boolean "enabled" "${enabled:-0}"
json_close_object
done
json_close_array
END {
# Output last vhost
if (vh_id != "" && vh_domain != "") {
if (first_vh == 0) printf ","
resolve_be = (vh_orig_be != "") ? vh_orig_be : vh_backend
port = 0
for (s in srv_backends) {
if (srv_backends[s] == resolve_be && srv_ports[s] != "") {
port = srv_ports[s]
break
}
}
printf "{\"id\":\"%s\",\"domain\":\"%s\",\"backend\":\"%s\",\"backend_port\":%d,\"ssl\":%s,\"acme\":%s,\"enabled\":%s}",
vh_id, vh_domain, resolve_be, port,
(vh_ssl == "1" ? "true" : "false"),
(vh_acme == "1" ? "true" : "false"),
(vh_enabled == "1" ? "true" : "false")
}
printf "],"
}
'
# uhttpd vhosts (non-main instances)
json_add_array "uhttpd"
for section in $(uci show uhttpd 2>/dev/null | grep "=uhttpd$" | cut -d'.' -f2 | cut -d'=' -f1); do
[ "$section" = "main" ] && continue
[ "$section" = "acme" ] && continue
listen=$(uci -q get "uhttpd.${section}.listen_http")
home=$(uci -q get "uhttpd.${section}.home")
[ -z "$listen" ] && continue
port=$(echo "$listen" | grep -o '[0-9]*$')
# Derive friendly name from section id
fname=$(echo "$section" | sed 's/^metablog_site_//' | sed 's/_/ /g')
json_add_object ""
json_add_string "id" "$section"
json_add_int "port" "${port:-0}"
json_add_string "name" "$fname"
json_add_string "home" "${home:-}"
json_close_object
done
json_close_array
json_dump
# uhttpd vhosts - also optimized with awk
uci show uhttpd 2>/dev/null | awk '
BEGIN {
printf "\"uhttpd\":["
first = 1
}
/=uhttpd$/ {
if (section != "" && section != "main" && section != "acme" && listen != "") {
if (first == 0) printf ","
first = 0
# Extract port from listen
match(listen, /[0-9]+$/)
port = substr(listen, RSTART, RLENGTH)
gsub(/^metablog_site_/, "", section)
gsub(/_/, " ", section)
printf "{\"id\":\"%s\",\"port\":%s,\"name\":\"%s\",\"home\":\"%s\"}",
orig_section, (port != "" ? port : "0"), section, home
}
gsub(/^uhttpd\./, "", $0)
gsub(/=uhttpd$/, "", $0)
orig_section = $0
section = $0
listen = ""
home = ""
}
/\.listen_http=/ {
gsub(/.*\.listen_http=/, "", $0)
gsub(/'\''/, "", $0)
listen = $0
}
/\.home=/ {
gsub(/.*\.home=/, "", $0)
gsub(/'\''/, "", $0)
home = $0
}
END {
if (section != "" && section != "main" && section != "acme" && listen != "") {
if (first == 0) printf ","
match(listen, /[0-9]+$/)
port = substr(listen, RSTART, RLENGTH)
gsub(/^metablog_site_/, "", section)
gsub(/_/, " ", section)
printf "{\"id\":\"%s\",\"port\":%s,\"name\":\"%s\",\"home\":\"%s\"}",
orig_section, (port != "" ? port : "0"), section, home
}
printf "]}"
}
'
;;
emancipate)

View File

@ -145,13 +145,13 @@ return view.extend({
E('h3', {}, _('Web Interface')),
E('div', { 'style': 'margin-bottom:12px' }, [
E('a', {
'href': 'http://' + window.location.hostname + ':' + (s.port || 9000),
'href': 'https://lyrion.gk2.secubox.in/',
'target': '_blank',
'class': 'cbi-button cbi-button-action',
'style': 'margin-right:8px'
}, _('Open Lyrion Web UI')),
E('span', { 'style': 'color:#888' },
'http://' + window.location.hostname + ':' + (s.port || 9000))
'https://lyrion.gk2.secubox.in/')
])
]),

View File

@ -4,6 +4,20 @@
. /usr/share/libubox/jshn.sh
# Get actual menu path for a luci-app package
get_menu_path() {
local pkg="$1"
local menu_file="/usr/share/luci/menu.d/${pkg}.json"
if [ -f "$menu_file" ]; then
local path=$(grep -o '"admin/[^"]*"' "$menu_file" | head -1 | tr -d '"')
if [ -n "$path" ]; then
echo "$path"
return
fi
fi
echo "admin/services/$(echo "$pkg" | sed 's/luci-app-//')"
}
# Discover LuCI menu entries from menu.d JSON files
discover_luci_menus() {
local menus=""
@ -90,11 +104,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-crowdsec*|luci-app-mitmproxy*|luci-app-guardian*|luci-app-dnsguard*|luci-app-threat*|luci-app-wazuh*|luci-app-vortex*)
luci-app-crowdsec*|luci-app-mitmproxy*|luci-app-guardian*|luci-app-dnsguard*|luci-app-threat*|luci-app-wazuh*|luci-app-vortex*|luci-app-firewall*|luci-app-device-intel*|luci-app-security*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -109,11 +123,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-jellyfin*|luci-app-lyrion*|luci-app-streamlit*|luci-app-peertube*|luci-app-magicmirror*)
luci-app-jellyfin*|luci-app-lyrion*|luci-app-streamlit*|luci-app-peertube*|luci-app-magicmirror*|luci-app-media-flow*|luci-app-icecast*|luci-app-webradio*|luci-app-mmpm*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -128,11 +142,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-haproxy*|luci-app-wireguard*|luci-app-tor*|luci-app-cdn*|luci-app-exposure*|luci-app-dns-provider*)
luci-app-haproxy*|luci-app-wireguard*|luci-app-tor*|luci-app-cdn*|luci-app-exposure*|luci-app-dns-provider*|luci-app-vhost*|luci-app-bandwidth*|luci-app-traffic*|luci-app-network-modes*|luci-app-ksmbd*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -151,7 +165,7 @@ build_tree() {
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -166,11 +180,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-domoticz*|luci-app-zigbee*|luci-app-iot*|luci-app-mqtt*)
luci-app-domoticz*|luci-app-zigbee*|luci-app-iot*|luci-app-mqtt*|luci-app-picobrew*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -185,11 +199,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-localai*|luci-app-ollama*|luci-app-simplex*|luci-app-gotosocial*|luci-app-ai-*|luci-app-voip*|luci-app-jabber*|luci-app-jitsi*|luci-app-mail*|luci-app-nextcloud*|luci-app-webradio*)
luci-app-localai*|luci-app-ollama*|luci-app-simplex*|luci-app-gotosocial*|luci-app-ai-*|luci-app-voip*|luci-app-jabber*|luci-app-jitsi*|luci-app-mailserver*|luci-app-nextcloud*|luci-app-matrix*|luci-app-cyberfeed*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -204,11 +218,11 @@ build_tree() {
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-cloner*|luci-app-backup*|luci-app-system*|luci-app-config-advisor*|luci-app-service-registry*)
luci-app-cloner*|luci-app-backup*|luci-app-system*|luci-app-config-advisor*|luci-app-service-registry*|luci-app-watchdog*|luci-app-glances*|luci-app-netdata*|luci-app-package-manager*|luci-app-master-link*|luci-app-uhttpd*|luci-app-config-vault*|luci-app-reporter*|luci-app-smtp-relay*|luci-app-rtty*|luci-app-dpi-dual*|luci-app-metacatalog*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/services/$(echo "$app" | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
@ -217,24 +231,36 @@ build_tree() {
json_close_array
json_close_object
# Other SecuBox Apps (catch-all)
# Downloads Apps
json_add_object ""
json_add_string "cat" "Other SecuBox Apps"
json_add_string "cat" "Downloads"
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-torrent*|luci-app-droplet*|luci-app-aria*|luci-app-transmission*|luci-app-nzb*|luci-app-sabnzbd*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;
esac
done
json_close_array
json_close_object
# SecuBox Core Apps
json_add_object ""
json_add_string "cat" "SecuBox Core"
json_add_array "items"
for app in $apps; do
case "$app" in
luci-app-crowdsec*|luci-app-mitmproxy*|luci-app-guardian*|luci-app-dnsguard*|luci-app-threat*|luci-app-wazuh*|luci-app-vortex*) continue ;;
luci-app-jellyfin*|luci-app-lyrion*|luci-app-streamlit*|luci-app-peertube*|luci-app-magicmirror*) continue ;;
luci-app-haproxy*|luci-app-wireguard*|luci-app-tor*|luci-app-cdn*|luci-app-exposure*|luci-app-dns-provider*) continue ;;
luci-app-gitea*|luci-app-hexo*|luci-app-metablog*|luci-app-metabol*) continue ;;
luci-app-domoticz*|luci-app-zigbee*|luci-app-iot*|luci-app-mqtt*) continue ;;
luci-app-localai*|luci-app-ollama*|luci-app-simplex*|luci-app-gotosocial*|luci-app-ai-*|luci-app-voip*|luci-app-jabber*|luci-app-jitsi*|luci-app-mail*|luci-app-nextcloud*|luci-app-webradio*) continue ;;
luci-app-cloner*|luci-app-backup*|luci-app-system*|luci-app-config-advisor*|luci-app-service-registry*) continue ;;
luci-app-secubox*|luci-app-*secubox*)
local name=$(echo "$app" | sed 's/luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)$i=toupper(substr($i,1,1))tolower(substr($i,2))}1')
json_add_object ""
json_add_string "name" "$name"
json_add_string "path" "admin/secubox/$(echo "$app" | sed 's/luci-app-secubox-//' | sed 's/luci-app-//')"
json_add_string "path" "$(get_menu_path "$app")"
json_add_string "package" "$app"
json_close_object
;;

View File

@ -96,7 +96,7 @@ var KissThemeClass = baseclass.extend({
]},
{ icon: '📶', name: 'Traffic Shaper', path: 'admin/secubox/network/traffic-shaper' },
{ icon: '📡', name: 'Bandwidth', path: 'admin/secubox/network/bandwidth-manager' },
{ icon: '🌐', name: 'Network Modes', path: 'admin/secubox/network/network-modes' },
{ icon: '🌐', name: 'Network Modes', path: 'admin/secubox/network/modes' },
{ icon: '🔌', name: 'Interfaces', path: 'admin/network/network' },
{ icon: '🔧', name: 'Net Diagnostics', path: 'admin/services/network-diagnostics' }
]},
@ -119,6 +119,7 @@ var KissThemeClass = baseclass.extend({
// ═══════════════════════════════════════════════════════════════
{ cat: 'Communication', icon: '💬', collapsed: true, items: [
{ icon: '✉️', name: 'Mail Server', path: 'admin/services/mailserver' },
{ icon: '📧', name: 'SMTP Relay', path: 'admin/secubox/system/smtp-relay' },
{ icon: '💬', name: 'Jabber/XMPP', path: 'admin/services/jabber' },
{ icon: '🔐', name: 'Matrix', path: 'admin/services/matrix' },
{ icon: '🔒', name: 'SimpleX', path: 'admin/services/simplex' },
@ -145,7 +146,10 @@ var KissThemeClass = baseclass.extend({
{ name: 'Settings', path: 'admin/services/metablogizer/settings' }
]},
{ icon: '🎯', name: 'Streamlit', path: 'admin/services/streamlit' },
{ icon: '🔧', name: 'Streamlit Forge', path: 'admin/services/streamlit-forge' },
{ icon: '📰', name: 'CyberFeed', path: 'admin/services/cyberfeed' },
{ icon: '📚', name: 'Meta Catalog', path: 'admin/secubox/metacatalog' },
{ icon: '🎭', name: 'Avatar Tap', path: 'admin/services/avatar-tap' },
{ icon: '🏠', name: 'Domoticz', path: 'admin/services/domoticz' },
{ icon: '🍺', name: 'PicoBrew', path: 'admin/services/picobrew' }
]},
@ -168,13 +172,12 @@ var KissThemeClass = baseclass.extend({
// P2P & MESH - Distributed networking
// ═══════════════════════════════════════════════════════════════
{ cat: 'P2P & Mesh', icon: '🔗', collapsed: true, items: [
{ icon: '🔗', name: 'Master Link', path: 'admin/secubox/master-link' },
{ icon: '🔗', name: 'Master Link', path: 'admin/services/secubox-mesh' },
{ icon: '🌐', name: 'P2P Network', path: 'admin/services/secubox-p2p' },
{ icon: '🔗', name: 'Mesh Network', path: 'admin/services/secubox-mesh' },
{ icon: '📡', name: 'Exposure', path: 'admin/services/exposure' },
{ icon: '📡', name: 'Exposure', path: 'admin/secubox/network/exposure' },
{ icon: '📋', name: 'Service Registry', path: 'admin/services/service-registry' },
{ icon: '☁️', name: 'SaaS Relay', path: 'admin/services/saas-relay' },
{ icon: '🌳', name: 'Netifyd', path: 'admin/services/secubox-netifyd' }
{ icon: '🌳', name: 'Netifyd', path: 'admin/secubox/netifyd' }
]},
// ═══════════════════════════════════════════════════════════════
@ -187,7 +190,31 @@ var KissThemeClass = baseclass.extend({
{ icon: '📁', name: 'File Sharing', path: 'admin/services/ksmbd' },
{ icon: '🌳', name: 'LuCI Menu', path: 'admin/secubox/luci-tree' },
{ icon: '🔧', name: 'Software', path: 'admin/system/opkg' },
{ icon: '🖥️', name: 'uhttpd', path: 'admin/services/uhttpd' }
{ icon: '🖥️', name: 'uhttpd', path: 'admin/services/uhttpd' },
{ icon: '🔐', name: 'Config Vault', path: 'admin/secubox/system/config-vault' },
{ icon: '📋', name: 'Reporter', path: 'admin/secubox/system/reporter' },
{ icon: '🐕', name: 'Watchdog', path: 'admin/secubox/system/watchdog' },
{ icon: '🖥️', name: 'Remote RTTY', path: 'admin/secubox/system/system-hub/rtty-remote' }
]},
// ═══════════════════════════════════════════════════════════════
// DOWNLOADS - Torrent and Usenet clients
// ═══════════════════════════════════════════════════════════════
{ cat: 'Downloads', icon: '📥', collapsed: true, items: [
{ icon: '🧲', name: 'Torrent', path: 'admin/services/torrent' },
{ icon: '💧', name: 'Droplet', path: 'admin/services/droplet' },
{ icon: '🌊', name: 'WebTorrent', path: 'admin/services/webtorrent' }
]},
// ═══════════════════════════════════════════════════════════════
// MONITORING - System and network monitoring
// ═══════════════════════════════════════════════════════════════
{ cat: 'Monitoring', icon: '📈', collapsed: true, items: [
{ icon: '👁️', name: 'Glances', path: 'admin/secubox/monitoring/glances' },
{ icon: '📊', name: 'Netdata', path: 'admin/secubox/monitoring/netdata' },
{ icon: '🔍', name: 'Device Intel', path: 'admin/secubox/device-intel' },
{ icon: '📡', name: 'Threat Analyst', path: 'admin/services/threat-analyst' },
{ icon: '🔬', name: 'DPI Dual', path: 'admin/secubox/dpi-dual' }
]}
],