release: v0.1.2-alpha - Dynamic Module Detection & Responsive Cards
Major Features: • SecuBox v0.1.2: Real-time module auto-detection via opkg • System Hub: Dynamic component detection leveraging SecuBox • Responsive card grid layout for modules and components • Category filtering tabs (All/Security/Monitoring/Network/System) • Auto-refresh every 30 seconds with real-time status SecuBox Changes: • Added detect_real_modules() function to scan opkg for installed packages • Enhanced get_modules() with dual-source detection (UCI + auto-detected) • Enhanced get_modules_by_category() with same dual-source logic • Auto-categorization based on package name patterns • Real version detection from opkg for installed packages • Added in_uci flag to distinguish module sources • Responsive modules.js with card-based layout • New modules.css with theme support and animations System Hub Changes: • Added get_components() and get_components_by_category() to RPCD • Components leverage SecuBox module detection via ubus • Completely rewritten components.js with responsive cards • New components.css matching SecuBox design language • Extended API with getComponents() methods • Unified component management with quick actions Deployment: • Added deploy-secubox-v0.1.2.sh for SecuBox deployment • Added deploy-system-hub-dynamic.sh for System Hub deployment • Added deploy-dynamic-modules.sh for combined deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3dcc89d3a3
commit
801601591c
102
deploy-dynamic-modules.sh
Executable file
102
deploy-dynamic-modules.sh
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy Both SecuBox v0.1.2 and System Hub Dynamic Components
|
||||||
|
# Complete deployment of responsive, auto-detecting module/component system
|
||||||
|
|
||||||
|
ROUTER="root@192.168.8.191"
|
||||||
|
|
||||||
|
echo "🚀 Deploying Dynamic Module System to $ROUTER"
|
||||||
|
echo "=================================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ===== SecuBox v0.1.2 =====
|
||||||
|
echo "📦 [1/2] Deploying SecuBox v0.1.2..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo " → Config file..."
|
||||||
|
scp luci-app-secubox/root/etc/config/secubox \
|
||||||
|
"$ROUTER:/etc/config/secubox"
|
||||||
|
|
||||||
|
echo " → RPCD backend with auto-detection..."
|
||||||
|
scp luci-app-secubox/root/usr/libexec/rpcd/luci.secubox \
|
||||||
|
"$ROUTER:/usr/libexec/rpcd/"
|
||||||
|
|
||||||
|
echo " → Modules view..."
|
||||||
|
scp luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/view/secubox/"
|
||||||
|
|
||||||
|
echo " → Modules CSS..."
|
||||||
|
scp luci-app-secubox/htdocs/luci-static/resources/secubox/modules.css \
|
||||||
|
"$ROUTER:/www/luci-static/resources/secubox/"
|
||||||
|
|
||||||
|
echo " → Setting permissions..."
|
||||||
|
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.secubox"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/view/secubox/modules.js"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/secubox/modules.css"
|
||||||
|
ssh "$ROUTER" "chmod 644 /etc/config/secubox"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " ✅ SecuBox v0.1.2 deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ===== System Hub Dynamic Components =====
|
||||||
|
echo "🧩 [2/2] Deploying System Hub Dynamic Components..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo " → RPCD backend..."
|
||||||
|
scp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub \
|
||||||
|
"$ROUTER:/usr/libexec/rpcd/"
|
||||||
|
|
||||||
|
echo " → Components view..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/view/system-hub/"
|
||||||
|
|
||||||
|
echo " → Components CSS..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css \
|
||||||
|
"$ROUTER:/www/luci-static/resources/system-hub/"
|
||||||
|
|
||||||
|
echo " → API with getComponents..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/system-hub/"
|
||||||
|
|
||||||
|
echo " → Setting permissions..."
|
||||||
|
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.system-hub"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/view/system-hub/components.js"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/system-hub/components.css"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/system-hub/api.js"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " ✅ System Hub deployed"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ===== Restart Services =====
|
||||||
|
echo "🔄 Restarting services..."
|
||||||
|
ssh "$ROUTER" "/etc/init.d/rpcd restart"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=================================================="
|
||||||
|
echo "✅ Dynamic Module System Deployed Successfully!"
|
||||||
|
echo "=================================================="
|
||||||
|
echo ""
|
||||||
|
echo "🎯 Features Deployed:"
|
||||||
|
echo ""
|
||||||
|
echo "SecuBox v0.1.2:"
|
||||||
|
echo " • Real-time module auto-detection via opkg"
|
||||||
|
echo " • Dual-source module list (UCI + auto-detected)"
|
||||||
|
echo " • Real version detection from installed packages"
|
||||||
|
echo " • Auto-categorization for detected modules"
|
||||||
|
echo " • Responsive card grid layout"
|
||||||
|
echo " • Category filter tabs"
|
||||||
|
echo " • Auto-refresh every 30 seconds"
|
||||||
|
echo ""
|
||||||
|
echo "System Hub - Components:"
|
||||||
|
echo " • Dynamic component detection (leverages SecuBox)"
|
||||||
|
echo " • Responsive card grid layout"
|
||||||
|
echo " • Category filter tabs"
|
||||||
|
echo " • Real-time status indicators"
|
||||||
|
echo " • Quick action buttons"
|
||||||
|
echo " • Auto-refresh every 30 seconds"
|
||||||
|
echo ""
|
||||||
|
echo "👉 Refresh your browser (Ctrl+Shift+R) to see changes:"
|
||||||
|
echo " • SecuBox → Modules"
|
||||||
|
echo " • System Hub → Components"
|
||||||
|
echo ""
|
||||||
49
deploy-secubox-v0.1.2.sh
Executable file
49
deploy-secubox-v0.1.2.sh
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy SecuBox v0.1.2 - Dynamic Module Detection
|
||||||
|
# Real-time module auto-discovery with responsive cards
|
||||||
|
|
||||||
|
ROUTER="root@192.168.8.191"
|
||||||
|
|
||||||
|
echo "🚀 Deploying SecuBox v0.1.2 to $ROUTER"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "📦 Deploying updated config..."
|
||||||
|
scp luci-app-secubox/root/etc/config/secubox \
|
||||||
|
"$ROUTER:/etc/config/secubox"
|
||||||
|
|
||||||
|
echo "🔧 Deploying enhanced RPCD backend with auto-detection..."
|
||||||
|
scp luci-app-secubox/root/usr/libexec/rpcd/luci.secubox \
|
||||||
|
"$ROUTER:/usr/libexec/rpcd/"
|
||||||
|
|
||||||
|
echo "📄 Deploying modules view..."
|
||||||
|
scp luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/view/secubox/"
|
||||||
|
|
||||||
|
echo "🎨 Deploying modules CSS..."
|
||||||
|
scp luci-app-secubox/htdocs/luci-static/resources/secubox/modules.css \
|
||||||
|
"$ROUTER:/www/luci-static/resources/secubox/"
|
||||||
|
|
||||||
|
echo "🔄 Setting permissions and restarting services..."
|
||||||
|
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.secubox"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/view/secubox/modules.js"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/secubox/modules.css"
|
||||||
|
ssh "$ROUTER" "chmod 644 /etc/config/secubox"
|
||||||
|
ssh "$ROUTER" "/etc/init.d/rpcd restart"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ SecuBox v0.1.2 deployed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 New Features:"
|
||||||
|
echo " • Real-time module auto-detection via opkg"
|
||||||
|
echo " • Dual-source module list (UCI + auto-detected)"
|
||||||
|
echo " • Real version detection from installed packages"
|
||||||
|
echo " • Auto-categorization for detected modules"
|
||||||
|
echo " • Responsive card grid layout"
|
||||||
|
echo " • Category filter tabs (All/Security/Monitoring/Network/System)"
|
||||||
|
echo " • Module versions displayed on cards"
|
||||||
|
echo " • Quick action buttons (Start/Stop/Restart/Dashboard)"
|
||||||
|
echo " • Auto-refresh every 30 seconds"
|
||||||
|
echo " • in_uci flag to distinguish module sources"
|
||||||
|
echo ""
|
||||||
|
echo "👉 Refresh your browser (Ctrl+Shift+R) and go to SecuBox → Modules"
|
||||||
|
echo ""
|
||||||
47
deploy-system-hub-dynamic.sh
Executable file
47
deploy-system-hub-dynamic.sh
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy System Hub - Dynamic Components
|
||||||
|
# Responsive component cards leveraging SecuBox module detection
|
||||||
|
|
||||||
|
ROUTER="root@192.168.8.191"
|
||||||
|
|
||||||
|
echo "🚀 Deploying System Hub Dynamic Components to $ROUTER"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "🔧 Deploying enhanced RPCD backend..."
|
||||||
|
scp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub \
|
||||||
|
"$ROUTER:/usr/libexec/rpcd/"
|
||||||
|
|
||||||
|
echo "📄 Deploying components view..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/view/system-hub/"
|
||||||
|
|
||||||
|
echo "🎨 Deploying components CSS..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css \
|
||||||
|
"$ROUTER:/www/luci-static/resources/system-hub/"
|
||||||
|
|
||||||
|
echo "📡 Deploying updated API..."
|
||||||
|
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js \
|
||||||
|
"$ROUTER:/www/luci-static/resources/system-hub/"
|
||||||
|
|
||||||
|
echo "🔄 Setting permissions and restarting services..."
|
||||||
|
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.system-hub"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/view/system-hub/components.js"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/system-hub/components.css"
|
||||||
|
ssh "$ROUTER" "chmod 644 /www/luci-static/resources/system-hub/api.js"
|
||||||
|
ssh "$ROUTER" "/etc/init.d/rpcd restart"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ System Hub Dynamic Components deployed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "🎯 New Features:"
|
||||||
|
echo " • Dynamic component detection (leverages SecuBox)"
|
||||||
|
echo " • Responsive card grid layout"
|
||||||
|
echo " • Category filter tabs (All/Security/Monitoring/Network/System)"
|
||||||
|
echo " • Real-time status indicators"
|
||||||
|
echo " • Component versions displayed"
|
||||||
|
echo " • Quick action buttons (Start/Stop/Restart/Dashboard)"
|
||||||
|
echo " • Auto-refresh every 30 seconds"
|
||||||
|
echo " • Unified component management"
|
||||||
|
echo ""
|
||||||
|
echo "👉 Refresh your browser (Ctrl+Shift+R) and go to System Hub → Components"
|
||||||
|
echo ""
|
||||||
@ -1,7 +1,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-secubox
|
PKG_NAME:=luci-app-secubox
|
||||||
PKG_VERSION:=0.1.1
|
PKG_VERSION:=0.1.2
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
config secubox 'main'
|
config secubox 'main'
|
||||||
option enabled '1'
|
option enabled '1'
|
||||||
option version '0.1.1'
|
option version '0.1.2'
|
||||||
option auto_discovery '1'
|
option auto_discovery '1'
|
||||||
option notifications '1'
|
option notifications '1'
|
||||||
option theme 'dark'
|
option theme 'dark'
|
||||||
|
|||||||
@ -124,14 +124,26 @@ get_status() {
|
|||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get detailed modules list
|
# Detect real installed luci-app packages
|
||||||
|
detect_real_modules() {
|
||||||
|
opkg list-installed 2>/dev/null | grep "^luci-app-" | grep -v "^luci-app-secubox " | while read package version; do
|
||||||
|
# Extract module ID from package name
|
||||||
|
local module_id=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-dashboard$//' | sed 's/-/_/g')
|
||||||
|
echo "$module_id:$package:$version"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get detailed modules list (UCI + auto-detected)
|
||||||
get_modules() {
|
get_modules() {
|
||||||
json_init
|
json_init
|
||||||
json_add_array "modules"
|
json_add_array "modules"
|
||||||
|
|
||||||
config_load secubox 2>/dev/null || true
|
config_load secubox 2>/dev/null || true
|
||||||
|
|
||||||
# List all module sections from UCI config
|
# Create associative array to track processed modules
|
||||||
|
local processed_modules=""
|
||||||
|
|
||||||
|
# First, add modules from UCI config
|
||||||
local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1)
|
local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1)
|
||||||
|
|
||||||
for module in $module_sections; do
|
for module in $module_sections; do
|
||||||
@ -140,7 +152,7 @@ get_modules() {
|
|||||||
config_get name "$module" name "$module"
|
config_get name "$module" name "$module"
|
||||||
config_get desc "$module" description ""
|
config_get desc "$module" description ""
|
||||||
config_get category "$module" category "other"
|
config_get category "$module" category "other"
|
||||||
config_get icon "$module" icon "box"
|
config_get icon "$module" icon "📦"
|
||||||
config_get color "$module" color "#64748b"
|
config_get color "$module" color "#64748b"
|
||||||
config_get package "$module" package ""
|
config_get package "$module" package ""
|
||||||
config_get config "$module" config ""
|
config_get config "$module" config ""
|
||||||
@ -149,6 +161,12 @@ get_modules() {
|
|||||||
local is_installed=$(check_module_installed "$module")
|
local is_installed=$(check_module_installed "$module")
|
||||||
local is_running=$(check_module_running "$module")
|
local is_running=$(check_module_running "$module")
|
||||||
|
|
||||||
|
# Get real version from opkg if installed
|
||||||
|
if [ "$is_installed" = "1" ] && [ -n "$package" ]; then
|
||||||
|
local real_version=$(opkg list-installed "$package" 2>/dev/null | awk '{print $3}' | sed 's/-.*$//')
|
||||||
|
[ -n "$real_version" ] && version="$real_version"
|
||||||
|
fi
|
||||||
|
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
json_add_string "id" "$module"
|
json_add_string "id" "$module"
|
||||||
json_add_string "name" "$name"
|
json_add_string "name" "$name"
|
||||||
@ -161,6 +179,55 @@ get_modules() {
|
|||||||
json_add_string "version" "$version"
|
json_add_string "version" "$version"
|
||||||
json_add_boolean "installed" "$is_installed"
|
json_add_boolean "installed" "$is_installed"
|
||||||
json_add_boolean "running" "$is_running"
|
json_add_boolean "running" "$is_running"
|
||||||
|
json_add_boolean "in_uci" "1"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
processed_modules="$processed_modules $module"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Second, add auto-detected modules not in UCI
|
||||||
|
detect_real_modules | while IFS=: read module_id package version; do
|
||||||
|
# Skip if already processed
|
||||||
|
echo "$processed_modules" | grep -q " $module_id " && continue
|
||||||
|
|
||||||
|
# Auto-detect properties based on package name
|
||||||
|
local name=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++){$i=toupper(substr($i,1,1)) substr($i,2)}}1')
|
||||||
|
local category="other"
|
||||||
|
local icon="📦"
|
||||||
|
local color="#64748b"
|
||||||
|
|
||||||
|
# Auto-categorize
|
||||||
|
case "$package" in
|
||||||
|
*crowdsec*|*guardian*|*auth*)
|
||||||
|
category="security"; icon="🛡️"; color="#22c55e"
|
||||||
|
;;
|
||||||
|
*netdata*|*monitoring*)
|
||||||
|
category="monitoring"; icon="📊"; color="#00ab44"
|
||||||
|
;;
|
||||||
|
*network*|*wireguard*|*bandwidth*|*traffic*)
|
||||||
|
category="network"; icon="🌐"; color="#3b82f6"
|
||||||
|
;;
|
||||||
|
*system*|*hub*)
|
||||||
|
category="system"; icon="⚙️"; color="#6366f1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local clean_module=$(echo "$module_id" | sed 's/_/-/g')
|
||||||
|
local is_running=$(check_module_running "$module_id")
|
||||||
|
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "id" "$module_id"
|
||||||
|
json_add_string "name" "$name"
|
||||||
|
json_add_string "description" "Auto-detected module"
|
||||||
|
json_add_string "category" "$category"
|
||||||
|
json_add_string "icon" "$icon"
|
||||||
|
json_add_string "color" "$color"
|
||||||
|
json_add_string "package" "$package"
|
||||||
|
json_add_string "config" "$clean_module"
|
||||||
|
json_add_string "version" "$version"
|
||||||
|
json_add_boolean "installed" "1"
|
||||||
|
json_add_boolean "running" "$is_running"
|
||||||
|
json_add_boolean "in_uci" "0"
|
||||||
json_close_object
|
json_close_object
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -176,10 +243,12 @@ get_modules_by_category() {
|
|||||||
json_add_array "modules"
|
json_add_array "modules"
|
||||||
|
|
||||||
config_load secubox 2>/dev/null || true
|
config_load secubox 2>/dev/null || true
|
||||||
|
local processed_modules=""
|
||||||
|
|
||||||
# List all module sections from UCI config
|
# List all module sections from UCI config
|
||||||
local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1)
|
local module_sections=$(uci -q show secubox | grep "=module$" | cut -d. -f2 | cut -d= -f1)
|
||||||
|
|
||||||
|
# First pass: UCI-defined modules in this category
|
||||||
for module in $module_sections; do
|
for module in $module_sections; do
|
||||||
local mod_category
|
local mod_category
|
||||||
config_get mod_category "$module" category "other"
|
config_get mod_category "$module" category "other"
|
||||||
@ -198,6 +267,12 @@ get_modules_by_category() {
|
|||||||
local is_installed=$(check_module_installed "$module")
|
local is_installed=$(check_module_installed "$module")
|
||||||
local is_running=$(check_module_running "$module")
|
local is_running=$(check_module_running "$module")
|
||||||
|
|
||||||
|
# Get real version from opkg if installed
|
||||||
|
if [ "$is_installed" = "1" ] && [ -n "$package" ]; then
|
||||||
|
local real_version=$(opkg list-installed "$package" 2>/dev/null | awk '{print $3}' | sed 's/-.*$//')
|
||||||
|
[ -n "$real_version" ] && version="$real_version"
|
||||||
|
fi
|
||||||
|
|
||||||
json_add_object ""
|
json_add_object ""
|
||||||
json_add_string "id" "$module"
|
json_add_string "id" "$module"
|
||||||
json_add_string "name" "$name"
|
json_add_string "name" "$name"
|
||||||
@ -205,8 +280,61 @@ get_modules_by_category() {
|
|||||||
json_add_string "icon" "$icon"
|
json_add_string "icon" "$icon"
|
||||||
json_add_string "color" "$color"
|
json_add_string "color" "$color"
|
||||||
json_add_string "version" "$version"
|
json_add_string "version" "$version"
|
||||||
|
json_add_string "package" "$package"
|
||||||
json_add_boolean "installed" "$is_installed"
|
json_add_boolean "installed" "$is_installed"
|
||||||
json_add_boolean "running" "$is_running"
|
json_add_boolean "running" "$is_running"
|
||||||
|
json_add_boolean "in_uci" "1"
|
||||||
|
json_close_object
|
||||||
|
|
||||||
|
processed_modules="$processed_modules $module"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Second pass: Auto-detected modules not in UCI
|
||||||
|
detect_real_modules | while IFS=: read module_id package version; do
|
||||||
|
# Skip if already processed from UCI
|
||||||
|
echo "$processed_modules" | grep -q " $module_id " && continue
|
||||||
|
|
||||||
|
# Auto-categorize
|
||||||
|
local mod_category icon color name
|
||||||
|
case "$package" in
|
||||||
|
*crowdsec*|*guardian*|*auth*)
|
||||||
|
mod_category="security"; icon="🛡️"; color="#22c55e"
|
||||||
|
;;
|
||||||
|
*netdata*|*monitoring*)
|
||||||
|
mod_category="monitoring"; icon="📊"; color="#00ab44"
|
||||||
|
;;
|
||||||
|
*network*|*wireguard*|*bandwidth*|*traffic*|*cdn*)
|
||||||
|
mod_category="network"; icon="🌐"; color="#3b82f6"
|
||||||
|
;;
|
||||||
|
*system*|*hub*|*vhost*)
|
||||||
|
mod_category="system"; icon="⚙️"; color="#6366f1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
mod_category="other"; icon="📦"; color="#64748b"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Only include if matches requested category
|
||||||
|
if [ "$mod_category" = "$category" ]; then
|
||||||
|
# Generate nice name from package
|
||||||
|
name=$(echo "$package" | sed 's/^luci-app-//' | sed 's/-dashboard$//' | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
||||||
|
|
||||||
|
local is_installed="1" # Must be installed if detected
|
||||||
|
local is_running=$(check_module_running "$module_id")
|
||||||
|
|
||||||
|
json_add_object ""
|
||||||
|
json_add_string "id" "$module_id"
|
||||||
|
json_add_string "name" "$name"
|
||||||
|
json_add_string "description" "Auto-detected module"
|
||||||
|
json_add_string "icon" "$icon"
|
||||||
|
json_add_string "color" "$color"
|
||||||
|
json_add_string "version" "$version"
|
||||||
|
json_add_string "package" "$package"
|
||||||
|
json_add_string "category" "$mod_category"
|
||||||
|
json_add_boolean "installed" "$is_installed"
|
||||||
|
json_add_boolean "running" "$is_running"
|
||||||
|
json_add_boolean "in_uci" "0"
|
||||||
json_close_object
|
json_close_object
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
@ -88,11 +88,26 @@ var callSaveSettings = rpc.declare({
|
|||||||
expect: {}
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callGetComponents = rpc.declare({
|
||||||
|
object: 'luci.system-hub',
|
||||||
|
method: 'get_components',
|
||||||
|
expect: { modules: [] }
|
||||||
|
});
|
||||||
|
|
||||||
|
var callGetComponentsByCategory = rpc.declare({
|
||||||
|
object: 'luci.system-hub',
|
||||||
|
method: 'get_components_by_category',
|
||||||
|
params: ['category'],
|
||||||
|
expect: { modules: [] }
|
||||||
|
});
|
||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
// RPC methods - exposed via ubus
|
// RPC methods - exposed via ubus
|
||||||
getStatus: callStatus,
|
getStatus: callStatus,
|
||||||
getSystemInfo: callGetSystemInfo,
|
getSystemInfo: callGetSystemInfo,
|
||||||
getHealth: callGetHealth,
|
getHealth: callGetHealth,
|
||||||
|
getComponents: callGetComponents,
|
||||||
|
getComponentsByCategory: callGetComponentsByCategory,
|
||||||
listServices: callListServices,
|
listServices: callListServices,
|
||||||
serviceAction: callServiceAction,
|
serviceAction: callServiceAction,
|
||||||
getLogs: callGetLogs,
|
getLogs: callGetLogs,
|
||||||
|
|||||||
@ -0,0 +1,336 @@
|
|||||||
|
/**
|
||||||
|
* System Hub - Components Page Styles
|
||||||
|
* Responsive card layout with theme support
|
||||||
|
* Version: 0.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* === Header & Filters === */
|
||||||
|
.sh-components-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-page-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: var(--sh-text-primary, #1e293b);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-title-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
background: var(--sh-bg-secondary, #f8fafc);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--sh-border, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-filter-tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--sh-bg-card, #ffffff);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sh-text-secondary, #64748b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-filter-tab:hover {
|
||||||
|
background: var(--sh-hover-bg, #f1f5f9);
|
||||||
|
border-color: var(--sh-primary, #6366f1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-filter-tab.active {
|
||||||
|
background: var(--sh-primary, #6366f1);
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: var(--sh-primary, #6366f1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-tab-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-tab-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Components Grid === */
|
||||||
|
.sh-components-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sh-components-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Component Card === */
|
||||||
|
.sh-component-card {
|
||||||
|
background: var(--sh-bg-card, #ffffff);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--sh-border, #e2e8f0);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px var(--sh-hover-shadow, rgba(0, 0, 0, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--sh-bg-secondary, #f8fafc);
|
||||||
|
border-bottom: 1px solid var(--sh-border, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: var(--sh-text-primary, #1e293b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-version,
|
||||||
|
.sh-component-category {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-version {
|
||||||
|
background: var(--sh-primary, #6366f1);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-category {
|
||||||
|
background: var(--sh-bg-tertiary, #f1f5f9);
|
||||||
|
color: var(--sh-text-secondary, #64748b);
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Status Indicator === */
|
||||||
|
.sh-status-indicator {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-status-running {
|
||||||
|
background: #22c55e;
|
||||||
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-status-stopped {
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-status-not-installed {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Component Card Body === */
|
||||||
|
.sh-component-card-body {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-component-description {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--sh-text-secondary, #64748b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Component Actions === */
|
||||||
|
.sh-component-card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: var(--sh-bg-secondary, #f8fafc);
|
||||||
|
border-top: 1px solid var(--sh-border, #e2e8f0);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-action-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: fit-content;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-success {
|
||||||
|
background: #22c55e;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-success:hover {
|
||||||
|
background: #16a34a;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-danger {
|
||||||
|
background: #ef4444;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-warning {
|
||||||
|
background: #f59e0b;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-warning:hover {
|
||||||
|
background: #d97706;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-primary {
|
||||||
|
background: var(--sh-primary, #6366f1);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-primary:hover {
|
||||||
|
background: #4f46e5;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-btn-secondary {
|
||||||
|
background: var(--sh-bg-tertiary, #f1f5f9);
|
||||||
|
color: var(--sh-text-secondary, #64748b);
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Empty State === */
|
||||||
|
.sh-empty-state {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
background: var(--sh-bg-secondary, #f8fafc);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px dashed var(--sh-border, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-empty-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sh-empty-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--sh-text-secondary, #64748b);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Dark Mode Support === */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--sh-text-primary: #f1f5f9;
|
||||||
|
--sh-text-secondary: #cbd5e1;
|
||||||
|
--sh-bg-primary: #0f172a;
|
||||||
|
--sh-bg-secondary: #1e293b;
|
||||||
|
--sh-bg-tertiary: #334155;
|
||||||
|
--sh-bg-card: #1e293b;
|
||||||
|
--sh-border: #334155;
|
||||||
|
--sh-hover-bg: #334155;
|
||||||
|
--sh-hover-shadow: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-component-card {
|
||||||
|
background: var(--sh-bg-card);
|
||||||
|
border-color: var(--sh-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-component-card-header,
|
||||||
|
[data-theme="dark"] .sh-component-card-actions {
|
||||||
|
background: var(--sh-bg-tertiary);
|
||||||
|
border-color: var(--sh-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-filter-tabs {
|
||||||
|
background: var(--sh-bg-secondary);
|
||||||
|
border-color: var(--sh-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-filter-tab {
|
||||||
|
background: var(--sh-bg-tertiary);
|
||||||
|
color: var(--sh-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-filter-tab:hover {
|
||||||
|
background: var(--sh-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-component-category {
|
||||||
|
background: var(--sh-bg-tertiary);
|
||||||
|
color: var(--sh-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .sh-empty-state {
|
||||||
|
background: var(--sh-bg-secondary);
|
||||||
|
border-color: var(--sh-border);
|
||||||
|
}
|
||||||
@ -1,159 +1,265 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
'require view';
|
'require view';
|
||||||
'require dom';
|
|
||||||
'require ui';
|
'require ui';
|
||||||
|
'require dom';
|
||||||
var api = L.require('system-hub.api');
|
'require poll';
|
||||||
|
'require system-hub.api as API';
|
||||||
// Stub: Get components (planned feature - returns mock data)
|
'require system-hub.theme as Theme';
|
||||||
function getComponents() {
|
|
||||||
return Promise.resolve({
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
id: 'netdata',
|
|
||||||
name: 'Netdata',
|
|
||||||
description: 'Real-time performance monitoring',
|
|
||||||
status: 'installed',
|
|
||||||
running: true,
|
|
||||||
icon: '📊',
|
|
||||||
color: '#00C851',
|
|
||||||
web_port: 19999
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'crowdsec',
|
|
||||||
name: 'CrowdSec',
|
|
||||||
description: 'Collaborative security engine',
|
|
||||||
status: 'installed',
|
|
||||||
running: true,
|
|
||||||
icon: '🛡️',
|
|
||||||
color: '#0091EA',
|
|
||||||
web_port: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'netifyd',
|
|
||||||
name: 'Netifyd',
|
|
||||||
description: 'Deep packet inspection',
|
|
||||||
status: 'planned',
|
|
||||||
roadmap_date: 'Q1 2026'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Get component icon
|
|
||||||
function getComponentIcon(icon) {
|
|
||||||
return icon || '📦';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub: Manage component (planned feature)
|
|
||||||
function manageComponent(id, action) {
|
|
||||||
return Promise.resolve({
|
|
||||||
success: true,
|
|
||||||
message: 'Component ' + id + ' ' + action + ' - Feature coming soon'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
|
componentsData: [],
|
||||||
|
currentFilter: 'all',
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
return getComponents();
|
return Promise.all([
|
||||||
|
API.getComponents(),
|
||||||
|
Theme.getTheme()
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
var components = data.components || [];
|
var components = (data[0] && data[0].modules) || [];
|
||||||
var self = this;
|
var theme = data[1];
|
||||||
|
|
||||||
var installed = components.filter(function(c) { return c.status === 'installed'; });
|
this.componentsData = components;
|
||||||
var planned = components.filter(function(c) { return c.status === 'planned'; });
|
|
||||||
|
|
||||||
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/components.css') }),
|
||||||
|
|
||||||
// Installed Components
|
// Header with filter tabs
|
||||||
E('div', { 'class': 'sh-card' }, [
|
E('div', { 'class': 'sh-components-header' }, [
|
||||||
E('div', { 'class': 'sh-card-header' }, [
|
E('h2', { 'class': 'sh-page-title' }, [
|
||||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🧩'), 'Composants Installés' ]),
|
E('span', { 'class': 'sh-title-icon' }, '🧩'),
|
||||||
E('div', { 'class': 'sh-card-badge' }, installed.length + ' actifs')
|
' System Components'
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-card-body' }, [
|
this.renderFilterTabs()
|
||||||
E('div', { 'class': 'sh-components-grid' },
|
|
||||||
installed.map(function(c) { return self.renderComponent(c); })
|
|
||||||
)
|
|
||||||
])
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Planned Components
|
// Components grid
|
||||||
E('div', { 'class': 'sh-card' }, [
|
E('div', { 'class': 'sh-components-grid', 'id': 'components-grid' },
|
||||||
E('div', { 'class': 'sh-card-header' }, [
|
this.renderComponentsGrid(components, this.currentFilter)
|
||||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🗓️'), 'Roadmap - Composants Planifiés' ])
|
)
|
||||||
]),
|
|
||||||
E('div', { 'class': 'sh-card-body' },
|
|
||||||
planned.map(function(c) { return self.renderRoadmapItem(c); })
|
|
||||||
)
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Setup auto-refresh
|
||||||
|
poll.add(L.bind(function() {
|
||||||
|
return API.getComponents().then(L.bind(function(result) {
|
||||||
|
if (result && result.modules) {
|
||||||
|
this.componentsData = result.modules;
|
||||||
|
this.updateComponentsGrid();
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
}, this), 30);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderComponent: function(c) {
|
renderFilterTabs: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
return E('div', { 'class': 'sh-component-card', 'style': '--component-color: ' + c.color }, [
|
var tabs = [
|
||||||
E('div', { 'class': 'sh-component-header' }, [
|
{ id: 'all', label: 'All Components', icon: '📦' },
|
||||||
|
{ id: 'security', label: 'Security', icon: '🛡️' },
|
||||||
|
{ id: 'monitoring', label: 'Monitoring', icon: '📊' },
|
||||||
|
{ id: 'network', label: 'Network', icon: '🌐' },
|
||||||
|
{ id: 'system', label: 'System', icon: '⚙️' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return E('div', { 'class': 'sh-filter-tabs' },
|
||||||
|
tabs.map(function(tab) {
|
||||||
|
return E('button', {
|
||||||
|
'class': 'sh-filter-tab' + (self.currentFilter === tab.id ? ' active' : ''),
|
||||||
|
'click': function(ev) {
|
||||||
|
self.handleFilterChange(tab.id, ev.target);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
E('span', { 'class': 'sh-tab-icon' }, tab.icon),
|
||||||
|
E('span', { 'class': 'sh-tab-label' }, tab.label)
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFilterChange: function(filterId, targetElement) {
|
||||||
|
this.currentFilter = filterId;
|
||||||
|
|
||||||
|
// Update active tab
|
||||||
|
var tabs = document.querySelectorAll('.sh-filter-tab');
|
||||||
|
tabs.forEach(function(tab) {
|
||||||
|
tab.classList.remove('active');
|
||||||
|
});
|
||||||
|
targetElement.closest('.sh-filter-tab').classList.add('active');
|
||||||
|
|
||||||
|
// Update grid
|
||||||
|
this.updateComponentsGrid();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderComponentsGrid: function(components, filter) {
|
||||||
|
var filtered = filter === 'all'
|
||||||
|
? components
|
||||||
|
: components.filter(function(c) { return c.category === filter; });
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
return E('div', { 'class': 'sh-empty-state' }, [
|
||||||
|
E('div', { 'class': 'sh-empty-icon' }, '📦'),
|
||||||
|
E('div', { 'class': 'sh-empty-text' },
|
||||||
|
filter === 'all'
|
||||||
|
? 'No components found'
|
||||||
|
: 'No ' + filter + ' components found')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered.map(L.bind(this.renderComponentCard, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
renderComponentCard: function(component) {
|
||||||
|
var self = this;
|
||||||
|
var isRunning = component.running;
|
||||||
|
var isInstalled = component.installed;
|
||||||
|
var statusClass = isRunning ? 'running' : (isInstalled ? 'stopped' : 'not-installed');
|
||||||
|
|
||||||
|
return E('div', {
|
||||||
|
'class': 'sh-component-card sh-component-' + statusClass,
|
||||||
|
'style': 'border-left: 4px solid ' + (component.color || '#64748b')
|
||||||
|
}, [
|
||||||
|
E('div', { 'class': 'sh-component-card-header' }, [
|
||||||
|
E('div', { 'class': 'sh-component-icon' }, component.icon || '📦'),
|
||||||
E('div', { 'class': 'sh-component-info' }, [
|
E('div', { 'class': 'sh-component-info' }, [
|
||||||
E('div', { 'class': 'sh-component-icon' }, getComponentIcon(c.icon)),
|
E('h3', { 'class': 'sh-component-name' }, component.name || component.id),
|
||||||
E('div', {}, [
|
E('div', { 'class': 'sh-component-meta' }, [
|
||||||
E('div', { 'class': 'sh-component-name' }, c.name),
|
E('span', { 'class': 'sh-component-version' },
|
||||||
E('div', { 'class': 'sh-component-desc' }, c.description)
|
'v' + (component.version || '0.0.9')),
|
||||||
|
E('span', { 'class': 'sh-component-category' },
|
||||||
|
component.category || 'other')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-component-status ' + (c.running ? 'running' : 'stopped') },
|
E('div', {
|
||||||
c.running ? 'Running' : 'Stopped')
|
'class': 'sh-status-indicator sh-status-' + statusClass,
|
||||||
|
'title': isRunning ? 'Running' : (isInstalled ? 'Stopped' : 'Not Installed')
|
||||||
|
})
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-component-actions' }, [
|
|
||||||
E('div', {
|
E('div', { 'class': 'sh-component-card-body' }, [
|
||||||
'class': 'sh-component-action',
|
E('p', { 'class': 'sh-component-description' },
|
||||||
'click': function() { self.manageComponent(c.id, c.running ? 'stop' : 'start'); }
|
component.description || 'System component')
|
||||||
}, c.running ? '⏹️ Stop' : '▶️ Start'),
|
]),
|
||||||
E('div', {
|
|
||||||
'class': 'sh-component-action',
|
E('div', { 'class': 'sh-component-card-actions' },
|
||||||
'click': function() { self.manageComponent(c.id, 'restart'); }
|
this.renderComponentActions(component)
|
||||||
}, '🔄 Restart'),
|
)
|
||||||
E('div', {
|
|
||||||
'class': 'sh-component-action',
|
|
||||||
'click': function() { window.location.href = c.web_port ? 'http://' + window.location.hostname + ':' + c.web_port : '#'; }
|
|
||||||
}, '📊 Open')
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderRoadmapItem: function(c) {
|
renderComponentActions: function(component) {
|
||||||
return E('div', { 'class': 'sh-roadmap-item' }, [
|
var self = this;
|
||||||
E('div', { 'class': 'sh-roadmap-icon' }, getComponentIcon(c.icon)),
|
var actions = [];
|
||||||
E('div', { 'class': 'sh-roadmap-info' }, [
|
|
||||||
E('div', { 'class': 'sh-roadmap-name' }, c.name),
|
if (component.installed) {
|
||||||
E('div', { 'class': 'sh-roadmap-desc' }, c.description)
|
if (component.running) {
|
||||||
]),
|
// Stop button
|
||||||
E('div', { 'class': 'sh-roadmap-date' }, c.roadmap_date || 'TBD')
|
actions.push(
|
||||||
]);
|
E('button', {
|
||||||
|
'class': 'sh-action-btn sh-btn-danger',
|
||||||
|
'click': function() { self.handleComponentAction(component.id, 'stop'); }
|
||||||
|
}, [
|
||||||
|
E('span', {}, '⏹️'),
|
||||||
|
' Stop'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restart button
|
||||||
|
actions.push(
|
||||||
|
E('button', {
|
||||||
|
'class': 'sh-action-btn sh-btn-warning',
|
||||||
|
'click': function() { self.handleComponentAction(component.id, 'restart'); }
|
||||||
|
}, [
|
||||||
|
E('span', {}, '🔄'),
|
||||||
|
' Restart'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dashboard button (if has dashboard)
|
||||||
|
if (component.package && component.package.includes('dashboard')) {
|
||||||
|
var dashboardUrl = '/cgi-bin/luci/admin/secubox/' + component.category + '/' + component.id;
|
||||||
|
actions.push(
|
||||||
|
E('a', {
|
||||||
|
'class': 'sh-action-btn sh-btn-primary',
|
||||||
|
'href': dashboardUrl
|
||||||
|
}, [
|
||||||
|
E('span', {}, '📊'),
|
||||||
|
' Dashboard'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Start button
|
||||||
|
actions.push(
|
||||||
|
E('button', {
|
||||||
|
'class': 'sh-action-btn sh-btn-success',
|
||||||
|
'click': function() { self.handleComponentAction(component.id, 'start'); }
|
||||||
|
}, [
|
||||||
|
E('span', {}, '▶️'),
|
||||||
|
' Start'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Install button
|
||||||
|
actions.push(
|
||||||
|
E('button', {
|
||||||
|
'class': 'sh-action-btn sh-btn-secondary',
|
||||||
|
'disabled': 'disabled',
|
||||||
|
'title': 'Manual installation required'
|
||||||
|
}, [
|
||||||
|
E('span', {}, '📥'),
|
||||||
|
' Not Installed'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
},
|
},
|
||||||
|
|
||||||
manageComponent: function(id, action) {
|
handleComponentAction: function(componentId, action) {
|
||||||
ui.showModal(_('Gestion Composant'), [
|
var self = this;
|
||||||
E('p', {}, 'Action: ' + action + ' sur ' + id + '...'),
|
|
||||||
|
ui.showModal(_('Component Action'), [
|
||||||
|
E('p', {}, 'Performing ' + action + ' on ' + componentId + '...'),
|
||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
manageComponent(id, action).then(function(result) {
|
// Call service action via system-hub API
|
||||||
|
API.serviceAction(componentId, action).then(function(result) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
|
||||||
ui.addNotification(null, E('p', {}, '✅ ' + result.message), 'success');
|
if (result && result.success) {
|
||||||
window.location.reload();
|
ui.addNotification(null,
|
||||||
|
E('p', {}, '✅ ' + componentId + ' ' + action + ' successful'),
|
||||||
|
'success');
|
||||||
|
|
||||||
|
// Refresh components
|
||||||
|
setTimeout(function() {
|
||||||
|
self.updateComponentsGrid();
|
||||||
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
ui.addNotification(null, E('p', {}, '❌ ' + (result.error || 'Erreur')), 'error');
|
ui.addNotification(null,
|
||||||
|
E('p', {}, '❌ Failed to ' + action + ' ' + componentId),
|
||||||
|
'error');
|
||||||
}
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
ui.hideModal();
|
||||||
|
ui.addNotification(null,
|
||||||
|
E('p', {}, '❌ Error: ' + (err.message || err)),
|
||||||
|
'error');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateComponentsGrid: function() {
|
||||||
|
var grid = document.getElementById('components-grid');
|
||||||
|
if (grid) {
|
||||||
|
dom.content(grid, this.renderComponentsGrid(this.componentsData, this.currentFilter));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
handleSave: null,
|
handleSave: null,
|
||||||
handleReset: null
|
handleReset: null
|
||||||
|
|||||||
@ -653,6 +653,47 @@ save_settings() {
|
|||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get components (leverages secubox module detection)
|
||||||
|
get_components() {
|
||||||
|
# Call secubox backend to get modules, which are the system components
|
||||||
|
local result=$(ubus call luci.secubox get_modules 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
# Pass through the secubox modules as components
|
||||||
|
echo "$result"
|
||||||
|
else
|
||||||
|
# Fallback if secubox is not available
|
||||||
|
json_init
|
||||||
|
json_add_array "modules"
|
||||||
|
json_close_array
|
||||||
|
json_dump
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get components by category
|
||||||
|
get_components_by_category() {
|
||||||
|
local input
|
||||||
|
read -r input
|
||||||
|
json_load "$input"
|
||||||
|
|
||||||
|
local category
|
||||||
|
json_get_var category category
|
||||||
|
json_cleanup
|
||||||
|
|
||||||
|
# Call secubox backend with category filter
|
||||||
|
local result=$(ubus call luci.secubox get_modules_by_category "{\"category\":\"$category\"}" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
echo "$result"
|
||||||
|
else
|
||||||
|
# Fallback
|
||||||
|
json_init
|
||||||
|
json_add_array "modules"
|
||||||
|
json_close_array
|
||||||
|
json_dump
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Main dispatcher
|
# Main dispatcher
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
list)
|
||||||
@ -661,6 +702,8 @@ case "$1" in
|
|||||||
"status": {},
|
"status": {},
|
||||||
"get_system_info": {},
|
"get_system_info": {},
|
||||||
"get_health": {},
|
"get_health": {},
|
||||||
|
"get_components": {},
|
||||||
|
"get_components_by_category": { "category": "string" },
|
||||||
"list_services": {},
|
"list_services": {},
|
||||||
"service_action": { "service": "string", "action": "string" },
|
"service_action": { "service": "string", "action": "string" },
|
||||||
"get_logs": { "lines": 100, "filter": "" },
|
"get_logs": { "lines": 100, "filter": "" },
|
||||||
@ -692,6 +735,8 @@ EOF
|
|||||||
status) status ;;
|
status) status ;;
|
||||||
get_system_info) get_system_info ;;
|
get_system_info) get_system_info ;;
|
||||||
get_health) get_health ;;
|
get_health) get_health ;;
|
||||||
|
get_components) get_components ;;
|
||||||
|
get_components_by_category) get_components_by_category ;;
|
||||||
list_services) list_services ;;
|
list_services) list_services ;;
|
||||||
service_action) service_action ;;
|
service_action) service_action ;;
|
||||||
get_logs) get_logs ;;
|
get_logs) get_logs ;;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user