diff --git a/package/secubox/luci-app-vortex-dns/htdocs/luci-static/resources/view/vortex-dns/dashboard.js b/package/secubox/luci-app-vortex-dns/htdocs/luci-static/resources/view/vortex-dns/dashboard.js index 5f78d7ea..e067dc0c 100644 --- a/package/secubox/luci-app-vortex-dns/htdocs/luci-static/resources/view/vortex-dns/dashboard.js +++ b/package/secubox/luci-app-vortex-dns/htdocs/luci-static/resources/view/vortex-dns/dashboard.js @@ -50,12 +50,19 @@ var callMeshSync = rpc.declare({ expect: {} }); +var callGetPublished = rpc.declare({ + object: 'luci.vortex-dns', + method: 'get_published', + expect: { services: [] } +}); + return view.extend({ load: function() { return Promise.all([ callStatus(), callGetSlaves(), - callGetPeers() + callGetPeers(), + callGetPublished() ]); }, @@ -63,6 +70,7 @@ return view.extend({ var status = data[0] || {}; var slaves = data[1] || []; var peers = data[2] || []; + var published = data[3] || []; var view = E('div', { 'class': 'cbi-map' }, [ E('h2', {}, 'Vortex DNS'), @@ -103,6 +111,9 @@ return view.extend({ // Mesh Peers Section this.renderPeersSection(status.mesh, peers), + // Published Services Section + this.renderServicesSection(published, status.master), + // Actions Section this.renderActionsSection(status) ]); @@ -225,6 +236,58 @@ return view.extend({ ]); }, + renderServicesSection: function(services, master) { + var wildcard = master ? master.wildcard_domain : null; + var nodePrefix = wildcard ? wildcard.split('.')[0] : null; + + // Deduplicate services by name + var seen = {}; + var uniqueServices = []; + (services || []).forEach(function(s) { + if (!seen[s.name]) { + seen[s.name] = true; + uniqueServices.push(s); + } + }); + + return E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, 'Node Services'), + E('div', { 'class': 'cbi-section-descr' }, + wildcard ? 'Services published on *.' + wildcard : 'Published services on this node'), + + uniqueServices.length > 0 ? E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, 'Service'), + E('th', { 'class': 'th' }, 'Domain'), + E('th', { 'class': 'th' }, 'Node URL'), + E('th', { 'class': 'th' }, 'Actions') + ]) + ].concat(uniqueServices.map(function(s) { + var nodeUrl = nodePrefix ? 'https://' + s.name + '.' + wildcard : null; + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, E('strong', {}, s.name)), + E('td', { 'class': 'td' }, E('a', { + 'href': 'https://' + s.domain, + 'target': '_blank', + 'style': 'color: #00d4aa;' + }, s.domain)), + E('td', { 'class': 'td' }, nodeUrl ? E('a', { + 'href': nodeUrl, + 'target': '_blank', + 'style': 'color: #888; font-size: 0.9em;' + }, s.name + '.' + wildcard) : '-'), + E('td', { 'class': 'td' }, E('a', { + 'href': 'https://' + s.domain, + 'target': '_blank', + 'class': 'btn cbi-button-action', + 'style': 'padding: 2px 8px; font-size: 0.85em;' + }, 'Open')) + ]); + }))) : E('p', { 'style': 'color: #888; margin-top: 8px;' }, + 'No services published. Use "metablogizerctl emancipate" to publish sites.') + ]); + }, + renderActionsSection: function(status) { var self = this; diff --git a/package/secubox/secubox-core/Makefile b/package/secubox/secubox-core/Makefile index d8abdc46..c6ddb185 100644 --- a/package/secubox/secubox-core/Makefile +++ b/package/secubox/secubox-core/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-core PKG_VERSION:=0.10.0 -PKG_RELEASE:=11 +PKG_RELEASE:=12 PKG_ARCH:=all PKG_LICENSE:=GPL-2.0 PKG_MAINTAINER:=SecuBox Team diff --git a/package/secubox/secubox-core/root/etc/config/secubox b/package/secubox/secubox-core/root/etc/config/secubox index 25938d12..292ed5a9 100644 --- a/package/secubox/secubox-core/root/etc/config/secubox +++ b/package/secubox/secubox-core/root/etc/config/secubox @@ -5,6 +5,8 @@ config core 'main' option appstore_url 'https://repo.secubox.org/catalog' option appstore_fallback_local '1' option health_check_interval '300' + option watchdog_interval '60' + option led_heartbeat '1' option ai_enabled '0' option ai_mode 'copilot' option device_id '' diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-core b/package/secubox/secubox-core/root/usr/sbin/secubox-core index a96d5508..22f83a10 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox-core +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-core @@ -10,16 +10,82 @@ . /lib/functions.sh . /usr/share/libubox/jshn.sh -SECUBOX_VERSION="0.8.1" +SECUBOX_VERSION="0.8.2" LOG_FILE="/var/log/secubox/core.log" PID_FILE="/var/run/secubox/core.pid" STATE_DIR="/var/run/secubox" WATCHDOG_STATE="/var/run/secubox/watchdog.json" +# LED paths for MochaBin (RGB LEDs: led1, led2, led3) +LED_GREEN="/sys/class/leds/green:led1" +LED_RED="/sys/class/leds/red:led1" +LED_BLUE="/sys/class/leds/blue:led1" +LED_ENABLED=0 + # Services to monitor (init.d name:check_method:restart_delay) # check_method: pid, docker, lxc, port:PORT MONITORED_SERVICES="" +# LED helper functions +led_init() { + # Check if LEDs are available (MochaBin or compatible) + if [ -d "$LED_GREEN" ] && [ -d "$LED_RED" ]; then + LED_ENABLED=1 + # Set trigger to none for manual control + echo none > "$LED_GREEN/trigger" 2>/dev/null + echo none > "$LED_RED/trigger" 2>/dev/null + echo none > "$LED_BLUE/trigger" 2>/dev/null + log debug "LED heartbeat enabled (MochaBin detected)" + else + log debug "LED heartbeat disabled (no compatible LEDs)" + fi +} + +led_set() { + local led="$1" + local state="$2" # 0 = off, 1 = on + [ "$LED_ENABLED" = "1" ] || return 0 + [ -f "${led}/brightness" ] && echo "$state" > "${led}/brightness" 2>/dev/null +} + +led_pulse() { + local led="$1" + [ "$LED_ENABLED" = "1" ] || return 0 + led_set "$led" 255 + # Brief flash - actual timing handled by background subshell + ( sleep 1 && led_set "$led" 0 ) & +} + +# Heartbeat function - shows system status via LED +# Green pulse = healthy, Red pulse = warning/error +led_heartbeat() { + [ "$LED_ENABLED" = "1" ] || return 0 + local status="$1" # healthy, warning, error + + case "$status" in + healthy) + # Single green flash + led_set "$LED_GREEN" 255 + ( sleep 1 && echo 0 > "${LED_GREEN}/brightness" 2>/dev/null ) & + ;; + warning) + # Double red flash + led_set "$LED_RED" 255 + ( sleep 1 && echo 0 > "${LED_RED}/brightness" && sleep 1 && echo 255 > "${LED_RED}/brightness" && sleep 1 && echo 0 > "${LED_RED}/brightness" ) & + ;; + error) + # Long red flash + led_set "$LED_RED" 255 + ( sleep 2 && echo 0 > "${LED_RED}/brightness" 2>/dev/null ) & + ;; + boot) + # Blue pulse during startup + led_set "$LED_BLUE" 255 + ( sleep 2 && echo 0 > "${LED_BLUE}/brightness" 2>/dev/null ) & + ;; + esac +} + # Auto-discover SecuBox services from ctl scripts discover_secubox_services() { local services="" @@ -429,16 +495,24 @@ daemon_mode() { # Write PID echo $$ > "$PID_FILE" + # Initialize LED heartbeat + led_init + led_heartbeat boot + # Get health check interval local health_interval=$(uci -q get secubox.main.health_check_interval || echo "300") # Get watchdog interval (faster than health check) local watchdog_interval=$(uci -q get secubox.main.watchdog_interval || echo "60") + # LED heartbeat setting (enabled by default) + local led_heartbeat_enabled=$(uci -q get secubox.main.led_heartbeat || echo "1") + # Main daemon loop local health_counter=0 local health_cycles=$((health_interval / watchdog_interval)) [ "$health_cycles" -lt 1 ] && health_cycles=1 + local last_health_status="healthy" while true; do # Run watchdog every cycle @@ -447,10 +521,18 @@ daemon_mode() { # Run health check every N cycles health_counter=$((health_counter + 1)) if [ "$health_counter" -ge "$health_cycles" ]; then - run_health_check > /tmp/secubox/health-status.json + local health_output=$(run_health_check) + echo "$health_output" > /tmp/secubox/health-status.json + # Extract status for LED + last_health_status=$(echo "$health_output" | jsonfilter -e '@.status' 2>/dev/null || echo "healthy") health_counter=0 fi + # LED heartbeat pulse (once per watchdog cycle) + if [ "$led_heartbeat_enabled" = "1" ]; then + led_heartbeat "$last_health_status" + fi + # Sleep until next check sleep "$watchdog_interval" done