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
|
||||
|
||||
PKG_NAME:=luci-app-secubox
|
||||
PKG_VERSION:=0.1.1
|
||||
PKG_VERSION:=0.1.2
|
||||
PKG_RELEASE:=1
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
config secubox 'main'
|
||||
option enabled '1'
|
||||
option version '0.1.1'
|
||||
option version '0.1.2'
|
||||
option auto_discovery '1'
|
||||
option notifications '1'
|
||||
option theme 'dark'
|
||||
|
||||
@ -124,14 +124,26 @@ get_status() {
|
||||
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() {
|
||||
json_init
|
||||
json_add_array "modules"
|
||||
|
||||
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)
|
||||
|
||||
for module in $module_sections; do
|
||||
@ -140,7 +152,7 @@ get_modules() {
|
||||
config_get name "$module" name "$module"
|
||||
config_get desc "$module" description ""
|
||||
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 package "$module" package ""
|
||||
config_get config "$module" config ""
|
||||
@ -149,6 +161,12 @@ get_modules() {
|
||||
local is_installed=$(check_module_installed "$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_string "id" "$module"
|
||||
json_add_string "name" "$name"
|
||||
@ -161,6 +179,55 @@ get_modules() {
|
||||
json_add_string "version" "$version"
|
||||
json_add_boolean "installed" "$is_installed"
|
||||
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
|
||||
done
|
||||
|
||||
@ -176,10 +243,12 @@ get_modules_by_category() {
|
||||
json_add_array "modules"
|
||||
|
||||
config_load secubox 2>/dev/null || true
|
||||
local processed_modules=""
|
||||
|
||||
# List all module sections from UCI config
|
||||
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
|
||||
local mod_category
|
||||
config_get mod_category "$module" category "other"
|
||||
@ -198,6 +267,12 @@ get_modules_by_category() {
|
||||
local is_installed=$(check_module_installed "$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_string "id" "$module"
|
||||
json_add_string "name" "$name"
|
||||
@ -205,8 +280,61 @@ get_modules_by_category() {
|
||||
json_add_string "icon" "$icon"
|
||||
json_add_string "color" "$color"
|
||||
json_add_string "version" "$version"
|
||||
json_add_string "package" "$package"
|
||||
json_add_boolean "installed" "$is_installed"
|
||||
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
|
||||
fi
|
||||
done
|
||||
|
||||
@ -88,11 +88,26 @@ var callSaveSettings = rpc.declare({
|
||||
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({
|
||||
// RPC methods - exposed via ubus
|
||||
getStatus: callStatus,
|
||||
getSystemInfo: callGetSystemInfo,
|
||||
getHealth: callGetHealth,
|
||||
getComponents: callGetComponents,
|
||||
getComponentsByCategory: callGetComponentsByCategory,
|
||||
listServices: callListServices,
|
||||
serviceAction: callServiceAction,
|
||||
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';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require ui';
|
||||
|
||||
var api = L.require('system-hub.api');
|
||||
|
||||
// Stub: Get components (planned feature - returns mock data)
|
||||
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'
|
||||
});
|
||||
}
|
||||
'require dom';
|
||||
'require poll';
|
||||
'require system-hub.api as API';
|
||||
'require system-hub.theme as Theme';
|
||||
|
||||
return view.extend({
|
||||
componentsData: [],
|
||||
currentFilter: 'all',
|
||||
|
||||
load: function() {
|
||||
return getComponents();
|
||||
return Promise.all([
|
||||
API.getComponents(),
|
||||
Theme.getTheme()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var components = data.components || [];
|
||||
var self = this;
|
||||
var components = (data[0] && data[0].modules) || [];
|
||||
var theme = data[1];
|
||||
|
||||
var installed = components.filter(function(c) { return c.status === 'installed'; });
|
||||
var planned = components.filter(function(c) { return c.status === 'planned'; });
|
||||
this.componentsData = components;
|
||||
|
||||
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
|
||||
|
||||
// Installed Components
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🧩'), 'Composants Installés' ]),
|
||||
E('div', { 'class': 'sh-card-badge' }, installed.length + ' actifs')
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/components.css') }),
|
||||
|
||||
// Header with filter tabs
|
||||
E('div', { 'class': 'sh-components-header' }, [
|
||||
E('h2', { 'class': 'sh-page-title' }, [
|
||||
E('span', { 'class': 'sh-title-icon' }, '🧩'),
|
||||
' System Components'
|
||||
]),
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
E('div', { 'class': 'sh-components-grid' },
|
||||
installed.map(function(c) { return self.renderComponent(c); })
|
||||
)
|
||||
])
|
||||
this.renderFilterTabs()
|
||||
]),
|
||||
|
||||
// Planned Components
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
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); })
|
||||
)
|
||||
])
|
||||
|
||||
// Components grid
|
||||
E('div', { 'class': 'sh-components-grid', 'id': 'components-grid' },
|
||||
this.renderComponentsGrid(components, this.currentFilter)
|
||||
)
|
||||
]);
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
renderComponent: function(c) {
|
||||
renderFilterTabs: function() {
|
||||
var self = this;
|
||||
return E('div', { 'class': 'sh-component-card', 'style': '--component-color: ' + c.color }, [
|
||||
E('div', { 'class': 'sh-component-header' }, [
|
||||
var tabs = [
|
||||
{ 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-icon' }, getComponentIcon(c.icon)),
|
||||
E('div', {}, [
|
||||
E('div', { 'class': 'sh-component-name' }, c.name),
|
||||
E('div', { 'class': 'sh-component-desc' }, c.description)
|
||||
E('h3', { 'class': 'sh-component-name' }, component.name || component.id),
|
||||
E('div', { 'class': 'sh-component-meta' }, [
|
||||
E('span', { 'class': 'sh-component-version' },
|
||||
'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') },
|
||||
c.running ? 'Running' : 'Stopped')
|
||||
E('div', {
|
||||
'class': 'sh-status-indicator sh-status-' + statusClass,
|
||||
'title': isRunning ? 'Running' : (isInstalled ? 'Stopped' : 'Not Installed')
|
||||
})
|
||||
]),
|
||||
E('div', { 'class': 'sh-component-actions' }, [
|
||||
E('div', {
|
||||
'class': 'sh-component-action',
|
||||
'click': function() { self.manageComponent(c.id, c.running ? 'stop' : 'start'); }
|
||||
}, c.running ? '⏹️ Stop' : '▶️ Start'),
|
||||
E('div', {
|
||||
'class': 'sh-component-action',
|
||||
'click': function() { self.manageComponent(c.id, 'restart'); }
|
||||
}, '🔄 Restart'),
|
||||
E('div', {
|
||||
'class': 'sh-component-action',
|
||||
'click': function() { window.location.href = c.web_port ? 'http://' + window.location.hostname + ':' + c.web_port : '#'; }
|
||||
}, '📊 Open')
|
||||
])
|
||||
|
||||
E('div', { 'class': 'sh-component-card-body' }, [
|
||||
E('p', { 'class': 'sh-component-description' },
|
||||
component.description || 'System component')
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'sh-component-card-actions' },
|
||||
this.renderComponentActions(component)
|
||||
)
|
||||
]);
|
||||
},
|
||||
|
||||
renderRoadmapItem: function(c) {
|
||||
return E('div', { 'class': 'sh-roadmap-item' }, [
|
||||
E('div', { 'class': 'sh-roadmap-icon' }, getComponentIcon(c.icon)),
|
||||
E('div', { 'class': 'sh-roadmap-info' }, [
|
||||
E('div', { 'class': 'sh-roadmap-name' }, c.name),
|
||||
E('div', { 'class': 'sh-roadmap-desc' }, c.description)
|
||||
]),
|
||||
E('div', { 'class': 'sh-roadmap-date' }, c.roadmap_date || 'TBD')
|
||||
]);
|
||||
renderComponentActions: function(component) {
|
||||
var self = this;
|
||||
var actions = [];
|
||||
|
||||
if (component.installed) {
|
||||
if (component.running) {
|
||||
// Stop button
|
||||
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) {
|
||||
ui.showModal(_('Gestion Composant'), [
|
||||
E('p', {}, 'Action: ' + action + ' sur ' + id + '...'),
|
||||
handleComponentAction: function(componentId, action) {
|
||||
var self = this;
|
||||
|
||||
ui.showModal(_('Component Action'), [
|
||||
E('p', {}, 'Performing ' + action + ' on ' + componentId + '...'),
|
||||
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();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, '✅ ' + result.message), 'success');
|
||||
window.location.reload();
|
||||
|
||||
if (result && result.success) {
|
||||
ui.addNotification(null,
|
||||
E('p', {}, '✅ ' + componentId + ' ' + action + ' successful'),
|
||||
'success');
|
||||
|
||||
// Refresh components
|
||||
setTimeout(function() {
|
||||
self.updateComponentsGrid();
|
||||
}, 2000);
|
||||
} 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,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
|
||||
@ -653,6 +653,47 @@ save_settings() {
|
||||
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
|
||||
case "$1" in
|
||||
list)
|
||||
@ -661,6 +702,8 @@ case "$1" in
|
||||
"status": {},
|
||||
"get_system_info": {},
|
||||
"get_health": {},
|
||||
"get_components": {},
|
||||
"get_components_by_category": { "category": "string" },
|
||||
"list_services": {},
|
||||
"service_action": { "service": "string", "action": "string" },
|
||||
"get_logs": { "lines": 100, "filter": "" },
|
||||
@ -692,6 +735,8 @@ EOF
|
||||
status) status ;;
|
||||
get_system_info) get_system_info ;;
|
||||
get_health) get_health ;;
|
||||
get_components) get_components ;;
|
||||
get_components_by_category) get_components_by_category ;;
|
||||
list_services) list_services ;;
|
||||
service_action) service_action ;;
|
||||
get_logs) get_logs ;;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user