diff --git a/deploy-dynamic-modules.sh b/deploy-dynamic-modules.sh new file mode 100755 index 00000000..13cb35c0 --- /dev/null +++ b/deploy-dynamic-modules.sh @@ -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 "" diff --git a/deploy-secubox-v0.1.2.sh b/deploy-secubox-v0.1.2.sh new file mode 100755 index 00000000..5bf37a87 --- /dev/null +++ b/deploy-secubox-v0.1.2.sh @@ -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 "" diff --git a/deploy-system-hub-dynamic.sh b/deploy-system-hub-dynamic.sh new file mode 100755 index 00000000..306cd204 --- /dev/null +++ b/deploy-system-hub-dynamic.sh @@ -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 "" diff --git a/luci-app-secubox/Makefile b/luci-app-secubox/Makefile index 3944699a..a6d70304 100644 --- a/luci-app-secubox/Makefile +++ b/luci-app-secubox/Makefile @@ -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 diff --git a/luci-app-secubox/root/etc/config/secubox b/luci-app-secubox/root/etc/config/secubox index 49f1e52e..8b19a1ed 100644 --- a/luci-app-secubox/root/etc/config/secubox +++ b/luci-app-secubox/root/etc/config/secubox @@ -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' diff --git a/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox b/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox index 4b616153..31aeee3e 100755 --- a/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox +++ b/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox @@ -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 diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js index b8f7604d..b41d2b63 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js @@ -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, diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css new file mode 100644 index 00000000..aa31339b --- /dev/null +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/components.css @@ -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); +} diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js index f5d78d0b..0c9fc093 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/components.js @@ -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 diff --git a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub index a4d7fc13..1f88011f 100755 --- a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub +++ b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub @@ -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 ;;