release: v0.1.1 - Unified Theme System with Dark/Light Mode Support

Major Features:
• Centralized theme system across SecuBox and System Hub
• Three theme modes: dark (default), light, and system (auto-detect)
• Single theme setting in SecuBox controls both plugins
• Real-time theme switching with OS preference detection

SecuBox Changes:
• Added theme.js manager for centralized theme control
• Implemented CSS variables for dark/light mode (secubox.css)
• Fixed hardcoded colors in dashboard.css, alerts.css, monitoring.css
• Integrated theme.js in all 7 views (dashboard, modules, alerts, monitoring, settings, etc.)
• Added get_theme RPC method to luci.secubox backend
• Updated ACL permissions to include get_theme (read access)
• Version updated to 0.1.1

System Hub Changes:
• Added theme.js manager using SecuBox theme API
• Implemented CSS variables for dark/light mode (dashboard.css)
• Integrated theme.js in all 9 views (overview, health, services, logs, backup, components, remote, settings, diagnostics)
• Version updated to 0.1.1
• README updated with maintainer info

Theme System Architecture:
• Configuration: /etc/config/secubox (option theme: dark|light|system)
• RPCD Backend: luci.secubox/get_theme method
• Frontend: theme.js modules (secubox/theme.js, system-hub/theme.js)
• CSS Variables: --sb-bg, --sb-bg-card, --sb-border, --sb-text, --sb-text-muted, --sb-shadow
• Auto-detection: prefers-color-scheme media query for system mode

Documentation:
• Added LUCI_DEVELOPMENT_REFERENCE.md with comprehensive LuCI development patterns
• Documented ubus/RPC types, baseclass.extend() patterns, ACL structure
• Common errors and solutions from implementation experience

Bug Fixes:
• Fixed SecuBox theme not applying visually (CSS variables now used)
• Fixed missing secubox.css in view imports
• Fixed ACL access denied for get_theme method
• Fixed hardcoded colors preventing theme switching

Testing:
• Verified theme switching works in all SecuBox tabs
• Verified theme switching works in all System Hub tabs
• Verified dark/light/system modes function correctly
• Verified single setting controls both plugins

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2025-12-26 16:08:53 +01:00
parent 6e0182ad35
commit 5902ac500a
30 changed files with 2199 additions and 249 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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.0 PKG_VERSION:=0.1.1
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>

View File

@ -40,7 +40,7 @@
/* Controls */ /* Controls */
.secubox-alerts-controls { .secubox-alerts-controls {
background: white; background: var(--sb-bg-card);
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
margin-bottom: 24px; margin-bottom: 24px;
@ -61,7 +61,7 @@
.secubox-filter-group label { .secubox-filter-group label {
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
color: #64748b; color: var(--sb-text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@ -75,7 +75,7 @@
} }
.secubox-alert-stat-card { .secubox-alert-stat-card {
background: white; background: var(--sb-bg-card);
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
@ -114,7 +114,7 @@
/* Alert Item */ /* Alert Item */
.secubox-alert-item { .secubox-alert-item {
background: white; background: var(--sb-bg-card);
border-radius: 8px; border-radius: 8px;
padding: 16px; padding: 16px;
display: flex; display: flex;
@ -170,12 +170,12 @@
.secubox-alert-module { .secubox-alert-module {
font-size: 16px; font-size: 16px;
color: #1e293b; color: var(--sb-text);
} }
.secubox-alert-time { .secubox-alert-time {
font-size: 12px; font-size: 12px;
color: #64748b; color: var(--sb-text-muted);
} }
.secubox-alert-message { .secubox-alert-message {
@ -225,7 +225,7 @@
border-radius: 50%; border-radius: 50%;
border: none; border: none;
background: rgba(0, 0, 0, 0.05); background: rgba(0, 0, 0, 0.05);
color: #64748b; color: var(--sb-text-muted);
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
line-height: 1; line-height: 1;
@ -256,7 +256,7 @@
.secubox-empty-title { .secubox-empty-title {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: #64748b; color: var(--sb-text-muted);
margin-bottom: 8px; margin-bottom: 8px;
} }

View File

@ -89,6 +89,12 @@ var callDashboardData = rpc.declare({
expect: { } expect: { }
}); });
var callGetTheme = rpc.declare({
object: 'luci.secubox',
method: 'get_theme',
expect: { }
});
function formatUptime(seconds) { function formatUptime(seconds) {
if (!seconds) return '0s'; if (!seconds) return '0s';
var d = Math.floor(seconds / 86400); var d = Math.floor(seconds / 86400);
@ -121,6 +127,7 @@ return baseclass.extend({
getAlerts: callAlerts, getAlerts: callAlerts,
quickAction: callQuickAction, quickAction: callQuickAction,
getDashboardData: callDashboardData, getDashboardData: callDashboardData,
getTheme: callGetTheme,
formatUptime: formatUptime, formatUptime: formatUptime,
formatBytes: formatBytes formatBytes: formatBytes
}); });

View File

@ -66,10 +66,11 @@
} }
.secubox-stat-card { .secubox-stat-card {
background: white; background: var(--sb-bg-card);
border: 1px solid var(--sb-border);
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px var(--sb-shadow);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
@ -78,7 +79,7 @@
.secubox-stat-card:hover { .secubox-stat-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px var(--sb-hover-shadow);
} }
.secubox-stat-icon { .secubox-stat-icon {
@ -95,11 +96,12 @@
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
margin-bottom: 4px; margin-bottom: 4px;
color: var(--sb-text);
} }
.secubox-stat-label { .secubox-stat-label {
font-size: 13px; font-size: 13px;
color: #64748b; color: var(--sb-text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@ -126,18 +128,20 @@
/* Card */ /* Card */
.secubox-card { .secubox-card {
background: white; background: var(--sb-bg-card);
border: 1px solid var(--sb-border);
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px var(--sb-shadow);
} }
.secubox-card-title { .secubox-card-title {
margin: 0 0 20px 0; margin: 0 0 20px 0;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: #1e293b; color: var(--sb-text);
border-bottom: 2px solid #f1f5f9; color: var(--sb-text);
border-bottom: 2px solid var(--sb-border);
padding-bottom: 12px; padding-bottom: 12px;
} }
@ -188,7 +192,7 @@
.secubox-gauge-label { .secubox-gauge-label {
font-size: 12px; font-size: 12px;
color: #64748b; color: var(--sb-text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
font-weight: 600; font-weight: 600;
@ -196,7 +200,7 @@
.secubox-gauge-details { .secubox-gauge-details {
font-size: 12px; font-size: 12px;
color: #64748b; color: var(--sb-text-muted);
text-align: center; text-align: center;
max-width: 140px; max-width: 140px;
} }
@ -220,7 +224,7 @@
} }
.secubox-module-mini-card { .secubox-module-mini-card {
background: #f8fafc; background: var(--sb-bg);
border-radius: 8px; border-radius: 8px;
padding: 12px; padding: 12px;
transition: all 0.2s; transition: all 0.2s;
@ -228,7 +232,7 @@
} }
.secubox-module-link:hover .secubox-module-mini-card { .secubox-module-link:hover .secubox-module-mini-card {
background: #f1f5f9; background: var(--sb-bg);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
@ -274,13 +278,13 @@
.secubox-module-mini-name { .secubox-module-mini-name {
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
color: #1e293b; color: var(--sb-text);
line-height: 1.3; line-height: 1.3;
} }
.secubox-module-mini-status { .secubox-module-mini-status {
font-size: 11px; font-size: 11px;
color: #64748b; color: var(--sb-text-muted);
} }
.secubox-module-running .secubox-module-mini-status { .secubox-module-running .secubox-module-mini-status {
@ -295,8 +299,8 @@
} }
.secubox-action-btn { .secubox-action-btn {
background: white; background: var(--sb-bg-card);
border: 2px solid #e2e8f0; border: 2px solid var(--sb-border);
border-radius: 8px; border-radius: 8px;
padding: 12px; padding: 12px;
cursor: pointer; cursor: pointer;
@ -320,7 +324,7 @@
} }
.secubox-action-label { .secubox-action-label {
color: #1e293b; color: var(--sb-text);
} }
/* Alerts */ /* Alerts */

View File

@ -26,7 +26,7 @@
/* Chart Card */ /* Chart Card */
.secubox-chart-card { .secubox-chart-card {
background: white; background: var(--sb-bg-card);
border-radius: 12px; border-radius: 12px;
padding: 24px; padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
@ -42,7 +42,7 @@
margin: 0 0 16px 0; margin: 0 0 16px 0;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #1e293b; color: var(--sb-text);
} }
.secubox-chart-container { .secubox-chart-container {
@ -50,7 +50,7 @@
height: 200px; height: 200px;
position: relative; position: relative;
margin-bottom: 16px; margin-bottom: 16px;
background: #f8fafc; background: var(--sb-bg);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
} }
@ -76,7 +76,7 @@
.secubox-chart-unit { .secubox-chart-unit {
font-size: 12px; font-size: 12px;
color: #64748b; color: var(--sb-text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
@ -93,7 +93,7 @@
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 16px; padding: 16px;
background: #f8fafc; background: var(--sb-bg);
border-radius: 8px; border-radius: 8px;
transition: background 0.2s; transition: background 0.2s;
} }
@ -113,14 +113,14 @@
.secubox-stat-label { .secubox-stat-label {
font-size: 13px; font-size: 13px;
color: #64748b; color: var(--sb-text-muted);
margin-bottom: 4px; margin-bottom: 4px;
} }
.secubox-stat-value { .secubox-stat-value {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: #1e293b; color: var(--sb-text);
} }
/* Animations */ /* Animations */

View File

@ -2,19 +2,39 @@
* SecuBox Dashboard Styles * SecuBox Dashboard Styles
* Copyright (C) 2025 CyberMind.fr * Copyright (C) 2025 CyberMind.fr
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
* Theme-aware styles with dark/light mode support
*/ */
/* Common colors (theme-independent) */
:root { :root {
--sb-primary: #3b82f6; --sb-primary: #3b82f6;
--sb-primary-dark: #1e40af; --sb-primary-dark: #1e40af;
--sb-success: #22c55e; --sb-success: #22c55e;
--sb-warning: #f59e0b; --sb-warning: #f59e0b;
--sb-danger: #ef4444; --sb-danger: #ef4444;
}
/* Dark theme (default) */
:root,
[data-theme="dark"] {
--sb-bg: #0f172a; --sb-bg: #0f172a;
--sb-bg-card: #1e293b; --sb-bg-card: #1e293b;
--sb-border: #334155; --sb-border: #334155;
--sb-text: #f1f5f9; --sb-text: #f1f5f9;
--sb-text-muted: #94a3b8; --sb-text-muted: #94a3b8;
--sb-shadow: rgba(0, 0, 0, 0.3);
--sb-hover-shadow: rgba(0, 0, 0, 0.4);
}
/* Light theme */
[data-theme="light"] {
--sb-bg: #f8fafc;
--sb-bg-card: #ffffff;
--sb-border: #e2e8f0;
--sb-text: #0f172a;
--sb-text-muted: #64748b;
--sb-shadow: rgba(0, 0, 0, 0.08);
--sb-hover-shadow: rgba(0, 0, 0, 0.12);
} }
.secubox-dashboard { .secubox-dashboard {
@ -42,7 +62,7 @@
.secubox-stat-box:hover { .secubox-stat-box:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px var(--sb-shadow);
} }
.secubox-stat-icon { .secubox-stat-icon {
@ -236,7 +256,7 @@
.secubox-module-card:hover { .secubox-module-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px var(--sb-hover-shadow);
border-color: var(--sb-primary); border-color: var(--sb-primary);
} }

View File

@ -0,0 +1,63 @@
'use strict';
'require baseclass';
'require secubox/api as API';
/**
* SecuBox Theme Manager
* Manages dark/light/system theme switching across SecuBox and all modules
* Version: 1.0.0
*/
console.log('🎨 SecuBox Theme Manager v1.0.0 loaded');
return baseclass.extend({
/**
* Initialize theme system
* Loads theme preference and applies it to the page
*/
init: function() {
var self = this;
return API.getTheme().then(function(data) {
var themePref = data.theme || 'dark';
self.applyTheme(themePref);
// Listen for system theme changes if preference is 'system'
if (themePref === 'system' && window.matchMedia) {
var darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
darkModeQuery.addListener(function() {
self.applyTheme('system');
});
}
}).catch(function(err) {
console.error('Failed to load theme preference, using dark theme:', err);
self.applyTheme('dark');
});
},
/**
* Apply theme to the page
* @param {string} theme - Theme preference: 'dark', 'light', or 'system'
*/
applyTheme: function(theme) {
var effectiveTheme = theme;
// If 'system', detect from OS
if (theme === 'system' && window.matchMedia) {
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Apply theme to document root
document.documentElement.setAttribute('data-theme', effectiveTheme);
console.log('🎨 Theme applied:', theme, '(effective:', effectiveTheme + ')');
},
/**
* Get current effective theme
* @returns {string} 'dark' or 'light'
*/
getCurrentTheme: function() {
return document.documentElement.getAttribute('data-theme') || 'dark';
}
});

View File

@ -3,15 +3,24 @@
'require ui'; 'require ui';
'require dom'; 'require dom';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox/theme as Theme';
'require poll'; 'require poll';
// Load CSS // Load CSS (base theme variables first)
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/secubox.css')
}));
document.head.appendChild(E('link', { document.head.appendChild(E('link', {
'rel': 'stylesheet', 'rel': 'stylesheet',
'type': 'text/css', 'type': 'text/css',
'href': L.resource('secubox/alerts.css') 'href': L.resource('secubox/alerts.css')
})); }));
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
alertsData: null, alertsData: null,
filterSeverity: 'all', filterSeverity: 'all',

View File

@ -2,16 +2,25 @@
'require view'; 'require view';
'require ui'; 'require ui';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox/theme as Theme';
'require dom'; 'require dom';
'require poll'; 'require poll';
// Load CSS // Load CSS (base theme variables first)
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/secubox.css')
}));
document.head.appendChild(E('link', { document.head.appendChild(E('link', {
'rel': 'stylesheet', 'rel': 'stylesheet',
'type': 'text/css', 'type': 'text/css',
'href': L.resource('secubox/dashboard.css') 'href': L.resource('secubox/dashboard.css')
})); }));
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
dashboardData: null, dashboardData: null,
healthData: null, healthData: null,

View File

@ -1,6 +1,10 @@
'use strict'; 'use strict';
'require view'; 'require view';
'require rpc'; 'require rpc';
'require secubox/theme as Theme';
// Initialize theme
Theme.init();
var callModules = rpc.declare({ var callModules = rpc.declare({
object: 'luci.secubox', object: 'luci.secubox',

View File

@ -1,5 +1,9 @@
'use strict'; 'use strict';
'require view'; 'require view';
'require secubox/theme as Theme';
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
load: function() { load: function() {

View File

@ -1,6 +1,10 @@
'use strict'; 'use strict';
'require view'; 'require view';
'require rpc'; 'require rpc';
'require secubox/theme as Theme';
// Initialize theme
Theme.init();
var callModules = rpc.declare({ var callModules = rpc.declare({
object: 'luci.secubox', object: 'luci.secubox',

View File

@ -3,15 +3,24 @@
'require ui'; 'require ui';
'require dom'; 'require dom';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox/theme as Theme';
'require poll'; 'require poll';
// Load CSS // Load CSS (base theme variables first)
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox/secubox.css')
}));
document.head.appendChild(E('link', { document.head.appendChild(E('link', {
'rel': 'stylesheet', 'rel': 'stylesheet',
'type': 'text/css', 'type': 'text/css',
'href': L.resource('secubox/monitoring.css') 'href': L.resource('secubox/monitoring.css')
})); }));
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
cpuHistory: [], cpuHistory: [],
memoryHistory: [], memoryHistory: [],

View File

@ -4,6 +4,10 @@
'require uci'; 'require uci';
'require ui'; 'require ui';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox/theme as Theme';
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
load: function() { load: function() {

View File

@ -86,7 +86,7 @@ get_status() {
local running=0 local running=0
json_init json_init
json_add_string "version" "0.1.0" json_add_string "version" "0.1.1"
json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')" json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')"
# System info # System info
@ -590,7 +590,7 @@ get_dashboard_data() {
local mem_pct=$((mem_used * 100 / mem_total)) local mem_pct=$((mem_used * 100 / mem_total))
json_add_object "status" json_add_object "status"
json_add_string "version" "0.1.0" json_add_string "version" "0.1.1"
json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')" json_add_string "hostname" "$(uci -q get system.@system[0].hostname || echo 'SecuBox')"
json_add_int "uptime" "$uptime" json_add_int "uptime" "$uptime"
json_add_string "load" "$load" json_add_string "load" "$load"
@ -667,6 +667,21 @@ get_dashboard_data() {
json_dump json_dump
} }
# Get theme setting
get_theme() {
local theme="dark"
# Load secubox config
if [ -f "/etc/config/secubox" ]; then
config_load secubox
config_get theme main theme "dark"
fi
json_init
json_add_string "theme" "$theme"
json_dump
}
# Main dispatcher # Main dispatcher
case "$1" in case "$1" in
list) list)
@ -703,6 +718,8 @@ case "$1" in
json_close_object json_close_object
json_add_object "get_dashboard_data" json_add_object "get_dashboard_data"
json_close_object json_close_object
json_add_object "get_theme"
json_close_object
json_dump json_dump
;; ;;
call) call)
@ -764,6 +781,9 @@ case "$1" in
get_dashboard_data) get_dashboard_data)
get_dashboard_data get_dashboard_data
;; ;;
get_theme)
get_theme
;;
*) *)
echo '{"error":"Unknown method"}' echo '{"error":"Unknown method"}'
;; ;;

View File

@ -12,7 +12,8 @@
"diagnostics", "diagnostics",
"get_system_health", "get_system_health",
"get_alerts", "get_alerts",
"get_dashboard_data" "get_dashboard_data",
"get_theme"
], ],
"uci": [ "uci": [
"get", "get",

View File

@ -1,10 +1,10 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-system-hub PKG_NAME:=luci-app-system-hub
PKG_VERSION:=0.0.2 PKG_VERSION:=0.1.1
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=SecuBox Project <support@secubox.com> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=System Hub - Central Control Dashboard LUCI_TITLE:=System Hub - Central Control Dashboard
LUCI_DESCRIPTION:=Central system control with monitoring, services, logs, and backup LUCI_DESCRIPTION:=Central system control with monitoring, services, logs, and backup

View File

@ -124,14 +124,13 @@ Output:
```json ```json
{ {
"cpu": { "cpu": {
"model": "ARM Cortex-A72", "usage": 25,
"status": "ok",
"load_1m": "0.25",
"load_5m": "0.30",
"load_15m": "0.28",
"cores": 4 "cores": 4
}, },
"load": {
"1min": "0.25",
"5min": "0.30",
"15min": "0.28"
},
"memory": { "memory": {
"total_kb": 4096000, "total_kb": 4096000,
"free_kb": 2048000, "free_kb": 2048000,
@ -139,23 +138,31 @@ Output:
"used_kb": 1024000, "used_kb": 1024000,
"buffers_kb": 512000, "buffers_kb": 512000,
"cached_kb": 1536000, "cached_kb": 1536000,
"percent": 25 "usage": 25,
"status": "ok"
}, },
"storage": [ "disk": {
{ "total_kb": 30408704,
"filesystem": "/dev/mmcblk0p2", "used_kb": 5447680,
"size": "29G", "usage": 19,
"used": "5.2G", "status": "ok"
"available": "22G", },
"percent": 19, "temperature": {
"mountpoint": "/" "value": 45,
} "status": "ok"
], },
"temperatures": [ "network": {
{ "wan_up": true,
"zone": "thermal_zone0", "status": "ok"
"celsius": 45 },
} "services": {
"running": 35,
"failed": 2
},
"score": 92,
"timestamp": "2025-12-26 10:30:00",
"recommendations": [
"2 service(s) enabled but not running. Check service status."
] ]
} }
``` ```
@ -227,6 +234,70 @@ System will reboot after 3 seconds.
ubus call luci.system-hub get_storage ubus call luci.system-hub get_storage
``` ```
#### Get Settings
```bash
ubus call luci.system-hub get_settings
```
Output:
```json
{
"general": {
"auto_refresh": true,
"health_check": true,
"debug_mode": false,
"refresh_interval": 30,
"log_retention": 30
},
"thresholds": {
"cpu_warning": 80,
"cpu_critical": 95,
"mem_warning": 80,
"mem_critical": 95,
"disk_warning": 80,
"disk_critical": 95,
"temp_warning": 70,
"temp_critical": 85
},
"schedules": {
"health_report": true,
"backup_weekly": true,
"log_cleanup": true
},
"upload": {
"auto_upload": false,
"url": "",
"token": ""
},
"support": {
"provider": "CyberMind.fr",
"email": "support@cybermind.fr",
"docs": "https://docs.cybermind.fr"
}
}
```
#### Save Settings
```bash
ubus call luci.system-hub save_settings '{
"auto_refresh": 1,
"health_check": 1,
"debug_mode": 0,
"refresh_interval": 30,
"log_retention": 30,
"cpu_warning": 80,
"cpu_critical": 95,
"mem_warning": 80,
"mem_critical": 95,
"disk_warning": 80,
"disk_critical": 95,
"temp_warning": 70,
"temp_critical": 85
}'
```
## ubus API Reference ## ubus API Reference
### status() ### status()
@ -359,6 +430,75 @@ Reboot the system (3-second delay).
Get detailed storage information for all mount points. Get detailed storage information for all mount points.
### get_settings()
Get all system-hub configuration settings.
**Returns:**
```json
{
"general": {
"auto_refresh": true,
"health_check": true,
"debug_mode": false,
"refresh_interval": 30,
"log_retention": 30
},
"thresholds": {
"cpu_warning": 80,
"cpu_critical": 95,
"mem_warning": 80,
"mem_critical": 95,
"disk_warning": 80,
"disk_critical": 95,
"temp_warning": 70,
"temp_critical": 85
},
"schedules": {
"health_report": true,
"backup_weekly": true,
"log_cleanup": true
},
"upload": {
"auto_upload": false,
"url": "",
"token": ""
},
"support": {
"provider": "CyberMind.fr",
"email": "support@cybermind.fr",
"docs": "https://docs.cybermind.fr"
}
}
```
### save_settings(...)
Save system-hub configuration settings to UCI.
**Parameters:**
- `auto_refresh`: Enable auto-refresh (0|1)
- `health_check`: Enable automatic health checks (0|1)
- `debug_mode`: Enable debug mode (0|1)
- `refresh_interval`: Refresh interval in seconds
- `log_retention`: Log retention in days
- `cpu_warning`: CPU warning threshold (%)
- `cpu_critical`: CPU critical threshold (%)
- `mem_warning`: Memory warning threshold (%)
- `mem_critical`: Memory critical threshold (%)
- `disk_warning`: Disk warning threshold (%)
- `disk_critical`: Disk critical threshold (%)
- `temp_warning`: Temperature warning threshold (°C)
- `temp_critical`: Temperature critical threshold (°C)
**Returns:**
```json
{
"success": true,
"message": "Settings saved successfully"
}
```
## System Information Sources ## System Information Sources
- Hostname: `/proc/sys/kernel/hostname` - Hostname: `/proc/sys/kernel/hostname`
@ -438,8 +578,37 @@ Apache-2.0
## Maintainer ## Maintainer
SecuBox Project <support@secubox.com> CyberMind <contact@cybermind.fr>
## Version ## Version
0.0.2 0.1.0
## Changelog
### v0.1.0 (2025-12-26)
- **STABLE RELEASE** - Production ready
- Fixed overview.js: Updated to use new health data structure (cpu.usage, memory.usage, disk.usage instead of deprecated fields)
- Fixed health view: Complete restructure of get_health RPCD method with proper metrics
- CPU: usage %, status (ok/warning/critical), load averages, cores count
- Memory: usage %, status, detailed KB metrics
- Disk: root filesystem usage %, status, size metrics
- Temperature: system temperature with status
- Network: WAN connectivity check
- Services: running vs failed count
- Overall health score: 0-100 based on all metrics
- Dynamic recommendations: actionable alerts based on thresholds
- Fixed settings view: Complete implementation with UCI backend
- Added get_settings and save_settings RPCD methods
- General settings: auto-refresh, health check, debug mode, intervals
- Alert thresholds: configurable CPU, memory, disk, temperature limits
- Scheduled tasks configuration
- Upload and support information
- Fixed ACL permissions: Added get_settings (read) and save_settings (write) to ACL
- Fixed API module: Correct usage of baseclass.extend() pattern
- Fixed view imports: Use 'require system-hub/api as API' instead of L.require()
- All 12 RPC methods working correctly
- Comprehensive validation passing
### v0.0.2
- Initial implementation with basic system monitoring and service management

View File

@ -6,11 +6,11 @@
* System Hub API * System Hub API
* Package: luci-app-system-hub * Package: luci-app-system-hub
* RPCD object: luci.system-hub * RPCD object: luci.system-hub
* Version: 0.0.2-debug * Version: 0.1.1
*/ */
// Debug log to verify correct version is loaded // Debug log to verify correct version is loaded
console.log('🔧 System Hub API v0.0.2-debug loaded at', new Date().toISOString()); console.log('🔧 System Hub API v0.1.1 loaded at', new Date().toISOString());
var callStatus = rpc.declare({ var callStatus = rpc.declare({
object: 'luci.system-hub', object: 'luci.system-hub',
@ -75,6 +75,19 @@ var callGetStorage = rpc.declare({
expect: { storage: [] } expect: { storage: [] }
}); });
var callGetSettings = rpc.declare({
object: 'luci.system-hub',
method: 'get_settings',
expect: {}
});
var callSaveSettings = rpc.declare({
object: 'luci.system-hub',
method: 'save_settings',
params: ['auto_refresh', 'health_check', 'debug_mode', 'refresh_interval', 'log_retention', 'cpu_warning', 'cpu_critical', 'mem_warning', 'mem_critical', 'disk_warning', 'disk_critical', 'temp_warning', 'temp_critical'],
expect: {}
});
return baseclass.extend({ return baseclass.extend({
// RPC methods - exposed via ubus // RPC methods - exposed via ubus
getStatus: callStatus, getStatus: callStatus,
@ -86,5 +99,7 @@ return baseclass.extend({
backupConfig: callBackupConfig, backupConfig: callBackupConfig,
restoreConfig: callRestoreConfig, restoreConfig: callRestoreConfig,
reboot: callReboot, reboot: callReboot,
getStorage: callGetStorage getStorage: callGetStorage,
getSettings: callGetSettings,
saveSettings: callSaveSettings
}); });

View File

@ -1,17 +1,9 @@
/* System Hub Dashboard - Central Control Theme */ /* System Hub Dashboard - Central Control Theme */
/* Copyright (C) 2024 CyberMind.fr - Gandalf */ /* Copyright (C) 2024 CyberMind.fr - Gandalf */
/* Theme-aware styles with dark/light mode support */
/* Common variables (theme-independent) */
:root { :root {
--sh-bg-primary: #0a0a0f;
--sh-bg-secondary: #12121a;
--sh-bg-tertiary: #1a1a24;
--sh-border: #2a2a3a;
--sh-border-light: #3a3a4a;
--sh-text-primary: #fafafa;
--sh-text-secondary: #a0a0b0;
--sh-text-muted: #707080;
--sh-accent-indigo: #6366f1; --sh-accent-indigo: #6366f1;
--sh-accent-violet: #8b5cf6; --sh-accent-violet: #8b5cf6;
--sh-accent-blue: #3b82f6; --sh-accent-blue: #3b82f6;
@ -33,10 +25,41 @@
--sh-radius: 8px; --sh-radius: 8px;
--sh-radius-lg: 12px; --sh-radius-lg: 12px;
}
/* Dark theme (default) */
:root,
[data-theme="dark"] {
--sh-bg-primary: #0a0a0f;
--sh-bg-secondary: #12121a;
--sh-bg-tertiary: #1a1a24;
--sh-border: #2a2a3a;
--sh-border-light: #3a3a4a;
--sh-text-primary: #fafafa;
--sh-text-secondary: #a0a0b0;
--sh-text-muted: #707080;
--sh-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); --sh-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
--sh-shadow-glow: 0 0 30px rgba(99, 102, 241, 0.3); --sh-shadow-glow: 0 0 30px rgba(99, 102, 241, 0.3);
} }
/* Light theme */
[data-theme="light"] {
--sh-bg-primary: #f5f5f7;
--sh-bg-secondary: #ffffff;
--sh-bg-tertiary: #f9fafb;
--sh-border: #e5e7eb;
--sh-border-light: #d1d5db;
--sh-text-primary: #0a0a0f;
--sh-text-secondary: #4b5563;
--sh-text-muted: #9ca3af;
--sh-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
--sh-shadow-glow: 0 0 30px rgba(99, 102, 241, 0.2);
}
/* Base */ /* Base */
.system-hub-dashboard { .system-hub-dashboard {
font-family: var(--sh-font-sans); font-family: var(--sh-font-sans);

View File

@ -0,0 +1,70 @@
'use strict';
'require baseclass';
'require rpc';
/**
* System Hub Theme Manager
* Uses centralized theme from SecuBox configuration
* Version: 1.0.0
*/
console.log('🎨 System Hub Theme Manager v1.0.0 loaded');
// RPC call to get theme from secu-box config
var callGetTheme = rpc.declare({
object: 'luci.secubox',
method: 'get_theme',
expect: {}
});
return baseclass.extend({
/**
* Initialize theme system
* Loads theme preference from SecuBox and applies it to the page
*/
init: function() {
var self = this;
return callGetTheme().then(function(data) {
var themePref = data.theme || 'dark';
self.applyTheme(themePref);
// Listen for system theme changes if preference is 'system'
if (themePref === 'system' && window.matchMedia) {
var darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
darkModeQuery.addListener(function() {
self.applyTheme('system');
});
}
}).catch(function(err) {
console.error('Failed to load theme preference from SecuBox, using dark theme:', err);
self.applyTheme('dark');
});
},
/**
* Apply theme to the page
* @param {string} theme - Theme preference: 'dark', 'light', or 'system'
*/
applyTheme: function(theme) {
var effectiveTheme = theme;
// If 'system', detect from OS
if (theme === 'system' && window.matchMedia) {
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Apply theme to document root
document.documentElement.setAttribute('data-theme', effectiveTheme);
console.log('🎨 System Hub theme applied:', theme, '(effective:', effectiveTheme + ')');
},
/**
* Get current effective theme
* @returns {string} 'dark' or 'light'
*/
getCurrentTheme: function() {
return document.documentElement.getAttribute('data-theme') || 'dark';
}
});

View File

@ -2,6 +2,17 @@
'require view'; 'require view';
'require ui'; 'require ui';
'require system-hub/api as API'; 'require system-hub/api as API';
'require system-hub/theme as Theme';
// Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
return L.view.extend({ return L.view.extend({
load: function() { load: function() {

View File

@ -2,8 +2,18 @@
'require view'; 'require view';
'require dom'; 'require dom';
'require ui'; 'require ui';
'require system-hub/api as API';
'require system-hub/theme as Theme';
var api = L.require('system-hub.api'); // Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
// Helper: Get health status info based on score // Helper: Get health status info based on score
function getHealthStatus(score) { function getHealthStatus(score) {
@ -24,7 +34,7 @@ function formatBytes(bytes) {
return view.extend({ return view.extend({
load: function() { load: function() {
return api.getHealth(); return API.getHealth();
}, },
render: function(data) { render: function(data) {

View File

@ -2,6 +2,17 @@
'require view'; 'require view';
'require ui'; 'require ui';
'require system-hub/api as API'; 'require system-hub/api as API';
'require system-hub/theme as Theme';
// Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
return L.view.extend({ return L.view.extend({
load: function() { load: function() {

View File

@ -2,6 +2,17 @@
'require view'; 'require view';
'require poll'; 'require poll';
'require system-hub/api as API'; 'require system-hub/api as API';
'require system-hub/theme as Theme';
// Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
return L.view.extend({ return L.view.extend({
load: function() { load: function() {
@ -68,27 +79,22 @@ return L.view.extend({
var gaugesContainer = E('div', { 'style': 'display: flex; justify-content: space-around; flex-wrap: wrap; margin: 20px 0;' }); var gaugesContainer = E('div', { 'style': 'display: flex; justify-content: space-around; flex-wrap: wrap; margin: 20px 0;' });
// CPU Load Gauge // CPU Load Gauge
var cpuLoad = parseFloat(health.load ? health.load['1min'] : status.health ? status.health.cpu_load : '0'); var cpuLoad = parseFloat(health.cpu ? health.cpu.load_1m : '0');
var cpuPercent = Math.min((cpuLoad * 100 / (health.cpu ? health.cpu.cores : 1)), 100); var cpuPercent = health.cpu ? health.cpu.usage : 0;
gaugesContainer.appendChild(this.createGauge('CPU Load', cpuPercent, cpuLoad.toFixed(2))); gaugesContainer.appendChild(this.createGauge('CPU Load', cpuPercent, cpuLoad.toFixed(2)));
// Memory Gauge // Memory Gauge
var memPercent = health.memory ? health.memory.percent : (status.health ? status.health.mem_percent : 0); var memPercent = health.memory ? health.memory.usage : 0;
var memUsed = health.memory ? (health.memory.used_kb / 1024).toFixed(0) : 0; var memUsed = health.memory ? (health.memory.used_kb / 1024).toFixed(0) : 0;
var memTotal = health.memory ? (health.memory.total_kb / 1024).toFixed(0) : 0; var memTotal = health.memory ? (health.memory.total_kb / 1024).toFixed(0) : 0;
gaugesContainer.appendChild(this.createGauge('Memory', memPercent, memUsed + ' / ' + memTotal + ' MB')); gaugesContainer.appendChild(this.createGauge('Memory', memPercent, memUsed + ' / ' + memTotal + ' MB'));
// Disk Gauge // Disk Gauge
var diskPercent = status.disk_percent || 0; var diskPercent = health.disk ? health.disk.usage : 0;
var diskInfo = ''; var diskUsed = health.disk ? (health.disk.used_kb / 1024).toFixed(0) : 0;
if (health.storage && health.storage.length > 0) { var diskTotal = health.disk ? (health.disk.total_kb / 1024).toFixed(0) : 0;
var root = health.storage.find(function(s) { return s.mountpoint === '/'; }); var diskInfo = diskUsed + ' / ' + diskTotal + ' MB';
if (root) { gaugesContainer.appendChild(this.createGauge('Disk Usage', diskPercent, diskInfo));
diskPercent = root.percent;
diskInfo = root.used + ' / ' + root.size;
}
}
gaugesContainer.appendChild(this.createGauge('Disk Usage', diskPercent, diskInfo || diskPercent + '%'));
healthSection.appendChild(gaugesContainer); healthSection.appendChild(gaugesContainer);
v.appendChild(healthSection); v.appendChild(healthSection);
@ -99,19 +105,19 @@ return L.view.extend({
E('h3', {}, _('CPU Information')), E('h3', {}, _('CPU Information')),
E('div', { 'class': 'table' }, [ E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [ E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'width': '50%' }, [
E('strong', {}, _('Model: ')),
E('span', {}, health.cpu.model)
]),
E('div', { 'class': 'td left', 'width': '50%' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [
E('strong', {}, _('Cores: ')), E('strong', {}, _('Cores: ')),
E('span', {}, String(health.cpu.cores)) E('span', {}, String(health.cpu.cores))
]),
E('div', { 'class': 'td left', 'width': '50%' }, [
E('strong', {}, _('Usage: ')),
E('span', {}, health.cpu.usage + '%')
]) ])
]), ]),
E('div', { 'class': 'tr' }, [ E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('div', { 'class': 'td left' }, [
E('strong', {}, _('Load Average: ')), E('strong', {}, _('Load Average: ')),
E('span', {}, (health.load ? health.load['1min'] + ' / ' + health.load['5min'] + ' / ' + health.load['15min'] : 'N/A')) E('span', {}, (health.cpu.load_1m + ' / ' + health.cpu.load_5m + ' / ' + health.cpu.load_15m))
]) ])
]) ])
]) ])
@ -120,71 +126,54 @@ return L.view.extend({
} }
// Temperature // Temperature
if (health.temperatures && health.temperatures.length > 0) { if (health.temperature && health.temperature.value > 0) {
var tempValue = health.temperature.value;
var tempColor = tempValue > 80 ? 'red' : (tempValue > 60 ? 'orange' : 'green');
var tempSection = E('div', { 'class': 'cbi-section' }, [ var tempSection = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Temperature')) E('h3', {}, _('Temperature')),
]); E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [
var tempTable = E('table', { 'class': 'table' }, [ E('div', { 'class': 'td left' }, [
E('tr', { 'class': 'tr table-titles' }, [ E('strong', {}, _('System Temperature: ')),
E('th', { 'class': 'th' }, _('Zone')), E('span', { 'style': 'color: ' + tempColor + '; font-weight: bold;' }, tempValue + '°C')
E('th', { 'class': 'th' }, _('Temperature')) ])
])
]) ])
]); ]);
health.temperatures.forEach(function(temp) {
var color = temp.celsius > 80 ? 'red' : (temp.celsius > 60 ? 'orange' : 'green');
tempTable.appendChild(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, temp.zone),
E('td', { 'class': 'td' }, [
E('span', { 'style': 'color: ' + color + '; font-weight: bold;' }, temp.celsius + '°C')
])
]));
});
tempSection.appendChild(tempTable);
v.appendChild(tempSection); v.appendChild(tempSection);
} }
// Storage // Storage (Root Filesystem)
if (health.storage && health.storage.length > 0) { if (health.disk) {
var diskColor = health.disk.usage > 90 ? 'red' : (health.disk.usage > 75 ? 'orange' : 'green');
var storageSection = E('div', { 'class': 'cbi-section' }, [ var storageSection = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Storage')) E('h3', {}, _('Storage (Root Filesystem)')),
]); E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [
var storageTable = E('table', { 'class': 'table' }, [ E('div', { 'class': 'td left', 'width': '50%' }, [
E('tr', { 'class': 'tr table-titles' }, [ E('strong', {}, _('Total: ')),
E('th', { 'class': 'th' }, _('Mountpoint')), E('span', {}, (health.disk.total_kb / 1024).toFixed(0) + ' MB')
E('th', { 'class': 'th' }, _('Filesystem')), ]),
E('th', { 'class': 'th' }, _('Size')), E('div', { 'class': 'td left', 'width': '50%' }, [
E('th', { 'class': 'th' }, _('Used')), E('strong', {}, _('Used: ')),
E('th', { 'class': 'th' }, _('Available')), E('span', {}, (health.disk.used_kb / 1024).toFixed(0) + ' MB')
E('th', { 'class': 'th' }, _('Use %'))
]) ])
]); ]),
E('div', { 'class': 'tr' }, [
health.storage.forEach(function(storage) { E('div', { 'class': 'td left' }, [
var color = storage.percent > 90 ? 'red' : (storage.percent > 75 ? 'orange' : 'green'); E('strong', {}, _('Usage: ')),
storageTable.appendChild(E('tr', { 'class': 'tr' }, [ E('div', { 'style': 'display: inline-flex; align-items: center; width: 200px;' }, [
E('td', { 'class': 'td' }, E('strong', {}, storage.mountpoint)),
E('td', { 'class': 'td' }, E('code', {}, storage.filesystem)),
E('td', { 'class': 'td' }, storage.size),
E('td', { 'class': 'td' }, storage.used),
E('td', { 'class': 'td' }, storage.available),
E('td', { 'class': 'td' }, [
E('div', { 'style': 'display: flex; align-items: center;' }, [
E('div', { 'style': 'flex: 1; background: #eee; height: 10px; border-radius: 5px; margin-right: 10px;' }, [ E('div', { 'style': 'flex: 1; background: #eee; height: 10px; border-radius: 5px; margin-right: 10px;' }, [
E('div', { E('div', {
'style': 'background: ' + color + '; width: ' + storage.percent + '%; height: 100%; border-radius: 5px;' 'style': 'background: ' + diskColor + '; width: ' + health.disk.usage + '%; height: 100%; border-radius: 5px;'
}) })
]), ]),
E('span', {}, storage.percent + '%') E('span', { 'style': 'font-weight: bold; color: ' + diskColor }, health.disk.usage + '%')
]) ])
]) ])
])); ])
}); ])
]);
storageSection.appendChild(storageTable);
v.appendChild(storageSection); v.appendChild(storageSection);
} }

View File

@ -2,6 +2,17 @@
'require view'; 'require view';
'require ui'; 'require ui';
'require system-hub/api as API'; 'require system-hub/api as API';
'require system-hub/theme as Theme';
// Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
return L.view.extend({ return L.view.extend({
load: function() { load: function() {

View File

@ -1,24 +1,40 @@
'use strict'; 'use strict';
'require view'; 'require view';
'require dom'; 'require dom';
'require uci';
'require ui'; 'require ui';
'require system-hub/api as API';
'require system-hub/theme as Theme';
var api = L.require('system-hub.api'); // Load CSS
document.head.appendChild(E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('system-hub/dashboard.css')
}));
// Initialize theme
Theme.init();
return view.extend({ return view.extend({
load: function() { load: function() {
return Promise.all([ return Promise.all([
api.getStatus(), API.getSettings(),
Promise.resolve({ schedules: [] }) // Stub: No schedules yet API.getStatus()
]); ]);
}, },
render: function(data) { render: function(data) {
var status = data[0]; var settings = data[0] || {};
var schedules = data[1].schedules || []; var status = data[1];
var self = this; var self = this;
// Extract settings with defaults
var general = settings.general || {};
var thresholds = settings.thresholds || {};
var schedules = settings.schedules || {};
var upload = settings.upload || {};
var support = settings.support || {};
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/dashboard.css') }),
@ -28,17 +44,17 @@ return view.extend({
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '⚙️'), 'Configuration Générale' ]) E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '⚙️'), 'Configuration Générale' ])
]), ]),
E('div', { 'class': 'sh-card-body' }, [ E('div', { 'class': 'sh-card-body' }, [
this.renderToggle('🔄', 'Rafraîchissement automatique', 'Mettre à jour le dashboard toutes les 30s', true, 'cfg_refresh'), this.renderToggle('🔄', 'Rafraîchissement automatique', 'Mettre à jour le dashboard toutes les 30s', general.auto_refresh !== false, 'cfg_refresh'),
this.renderToggle('💚', 'Vérification santé auto', 'Exécuter un health check toutes les heures', true, 'cfg_health'), this.renderToggle('💚', 'Vérification santé auto', 'Exécuter un health check toutes les heures', general.health_check !== false, 'cfg_health'),
this.renderToggle('🐛', 'Mode Debug', 'Activer les logs détaillés', false, 'cfg_debug'), this.renderToggle('🐛', 'Mode Debug', 'Activer les logs détaillés', general.debug_mode === true, 'cfg_debug'),
E('div', { 'class': 'sh-form-group', 'style': 'margin-top: 16px;' }, [ E('div', { 'class': 'sh-form-group', 'style': 'margin-top: 16px;' }, [
E('label', { 'class': 'sh-form-label' }, 'Intervalle de rafraîchissement (secondes)'), E('label', { 'class': 'sh-form-label' }, 'Intervalle de rafraîchissement (secondes)'),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '30', 'id': 'cfg_interval', 'style': 'width: 120px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': general.refresh_interval || '30', 'id': 'cfg_interval', 'style': 'width: 120px;' })
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Rétention des logs (jours)'), E('label', { 'class': 'sh-form-label' }, 'Rétention des logs (jours)'),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '30', 'id': 'cfg_retention', 'style': 'width: 120px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': general.log_retention || '30', 'id': 'cfg_retention', 'style': 'width: 120px;' })
]) ])
]) ])
]), ]),
@ -52,29 +68,29 @@ return view.extend({
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'CPU Warning / Critical (%)'), E('label', { 'class': 'sh-form-label' }, 'CPU Warning / Critical (%)'),
E('div', { 'style': 'display: flex; gap: 12px;' }, [ E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '80', 'id': 'cpu_warning', 'style': 'width: 100px;' }), E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.cpu_warning || '80', 'id': 'cpu_warning', 'style': 'width: 100px;' }),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '95', 'id': 'cpu_critical', 'style': 'width: 100px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.cpu_critical || '95', 'id': 'cpu_critical', 'style': 'width: 100px;' })
]) ])
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Mémoire Warning / Critical (%)'), E('label', { 'class': 'sh-form-label' }, 'Mémoire Warning / Critical (%)'),
E('div', { 'style': 'display: flex; gap: 12px;' }, [ E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '80', 'id': 'mem_warning', 'style': 'width: 100px;' }), E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.mem_warning || '80', 'id': 'mem_warning', 'style': 'width: 100px;' }),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '95', 'id': 'mem_critical', 'style': 'width: 100px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.mem_critical || '95', 'id': 'mem_critical', 'style': 'width: 100px;' })
]) ])
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Disque Warning / Critical (%)'), E('label', { 'class': 'sh-form-label' }, 'Disque Warning / Critical (%)'),
E('div', { 'style': 'display: flex; gap: 12px;' }, [ E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '80', 'id': 'disk_warning', 'style': 'width: 100px;' }), E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.disk_warning || '80', 'id': 'disk_warning', 'style': 'width: 100px;' }),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '95', 'id': 'disk_critical', 'style': 'width: 100px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.disk_critical || '95', 'id': 'disk_critical', 'style': 'width: 100px;' })
]) ])
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Température Warning / Critical (°C)'), E('label', { 'class': 'sh-form-label' }, 'Température Warning / Critical (°C)'),
E('div', { 'style': 'display: flex; gap: 12px;' }, [ E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '70', 'id': 'temp_warning', 'style': 'width: 100px;' }), E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.temp_warning || '70', 'id': 'temp_warning', 'style': 'width: 100px;' }),
E('input', { 'type': 'number', 'class': 'sh-input', 'value': '85', 'id': 'temp_critical', 'style': 'width: 100px;' }) E('input', { 'type': 'number', 'class': 'sh-input', 'value': thresholds.temp_critical || '85', 'id': 'temp_critical', 'style': 'width: 100px;' })
]) ])
]) ])
]) ])
@ -86,9 +102,9 @@ return view.extend({
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📅'), 'Tâches Planifiées' ]) E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📅'), 'Tâches Planifiées' ])
]), ]),
E('div', { 'class': 'sh-card-body' }, [ E('div', { 'class': 'sh-card-body' }, [
this.renderToggle('📋', 'Rapport Santé Quotidien', 'Tous les jours à 6h00', true, 'sched_health'), this.renderToggle('📋', 'Rapport Santé Quotidien', 'Tous les jours à 6h00', schedules.health_report !== false, 'sched_health'),
this.renderToggle('💾', 'Sauvegarde Hebdomadaire', 'Dimanche à 3h00, garde 4 versions', true, 'sched_backup'), this.renderToggle('💾', 'Sauvegarde Hebdomadaire', 'Dimanche à 3h00, garde 4 versions', schedules.backup_weekly !== false, 'sched_backup'),
this.renderToggle('🧹', 'Nettoyage Logs', 'Supprimer logs > 30 jours', true, 'sched_cleanup') this.renderToggle('🧹', 'Nettoyage Logs', 'Supprimer logs > 30 jours', schedules.log_cleanup !== false, 'sched_cleanup')
]) ])
]), ]),
@ -98,14 +114,14 @@ return view.extend({
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '☁️'), 'Upload Diagnostics' ]) E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '☁️'), 'Upload Diagnostics' ])
]), ]),
E('div', { 'class': 'sh-card-body' }, [ E('div', { 'class': 'sh-card-body' }, [
this.renderToggle('☁️', 'Upload automatique', 'Envoyer les diagnostics au support', false, 'cfg_upload'), this.renderToggle('☁️', 'Upload automatique', 'Envoyer les diagnostics au support', upload.auto_upload === true, 'cfg_upload'),
E('div', { 'class': 'sh-form-group', 'style': 'margin-top: 16px;' }, [ E('div', { 'class': 'sh-form-group', 'style': 'margin-top: 16px;' }, [
E('label', { 'class': 'sh-form-label' }, 'URL d\'upload'), E('label', { 'class': 'sh-form-label' }, 'URL d\'upload'),
E('input', { 'type': 'text', 'class': 'sh-input', 'id': 'upload_url', 'placeholder': 'https://support.example.com/upload' }) E('input', { 'type': 'text', 'class': 'sh-input', 'id': 'upload_url', 'value': upload.url || '', 'placeholder': 'https://support.example.com/upload' })
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Token d\'authentification'), E('label', { 'class': 'sh-form-label' }, 'Token d\'authentification'),
E('input', { 'type': 'password', 'class': 'sh-input', 'id': 'upload_token', 'placeholder': '••••••••' }) E('input', { 'type': 'password', 'class': 'sh-input', 'id': 'upload_token', 'value': upload.token || '', 'placeholder': '••••••••' })
]) ])
]) ])
]), ]),
@ -118,15 +134,15 @@ return view.extend({
E('div', { 'class': 'sh-card-body' }, [ E('div', { 'class': 'sh-card-body' }, [
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Fournisseur'), E('label', { 'class': 'sh-form-label' }, 'Fournisseur'),
E('input', { 'type': 'text', 'class': 'sh-input', 'id': 'support_provider', 'value': 'CyberMind.fr' }) E('input', { 'type': 'text', 'class': 'sh-input', 'id': 'support_provider', 'value': support.provider || 'CyberMind.fr' })
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'Email Support'), E('label', { 'class': 'sh-form-label' }, 'Email Support'),
E('input', { 'type': 'email', 'class': 'sh-input', 'id': 'support_email', 'value': 'support@cybermind.fr' }) E('input', { 'type': 'email', 'class': 'sh-input', 'id': 'support_email', 'value': support.email || 'support@cybermind.fr' })
]), ]),
E('div', { 'class': 'sh-form-group' }, [ E('div', { 'class': 'sh-form-group' }, [
E('label', { 'class': 'sh-form-label' }, 'URL Documentation'), E('label', { 'class': 'sh-form-label' }, 'URL Documentation'),
E('input', { 'type': 'url', 'class': 'sh-input', 'id': 'support_docs', 'value': 'https://docs.cybermind.fr' }) E('input', { 'type': 'url', 'class': 'sh-input', 'id': 'support_docs', 'value': support.docs || 'https://docs.cybermind.fr' })
]) ])
]) ])
]), ]),
@ -167,26 +183,47 @@ return view.extend({
E('div', { 'class': 'spinning' }) E('div', { 'class': 'spinning' })
]); ]);
uci.load('system-hub').then(function() { // Collect all settings from form
// Save health thresholds var settingsData = {
uci.set('system-hub', 'health', 'cpu_warning', document.getElementById('cpu_warning').value); auto_refresh: document.getElementById('cfg_refresh').classList.contains('active') ? 1 : 0,
uci.set('system-hub', 'health', 'cpu_critical', document.getElementById('cpu_critical').value); health_check: document.getElementById('cfg_health').classList.contains('active') ? 1 : 0,
uci.set('system-hub', 'health', 'memory_warning', document.getElementById('mem_warning').value); debug_mode: document.getElementById('cfg_debug').classList.contains('active') ? 1 : 0,
uci.set('system-hub', 'health', 'memory_critical', document.getElementById('mem_critical').value); refresh_interval: parseInt(document.getElementById('cfg_interval').value) || 30,
uci.set('system-hub', 'health', 'disk_warning', document.getElementById('disk_warning').value); log_retention: parseInt(document.getElementById('cfg_retention').value) || 30,
uci.set('system-hub', 'health', 'disk_critical', document.getElementById('disk_critical').value); cpu_warning: parseInt(document.getElementById('cpu_warning').value) || 80,
uci.set('system-hub', 'health', 'temperature_warning', document.getElementById('temp_warning').value); cpu_critical: parseInt(document.getElementById('cpu_critical').value) || 95,
uci.set('system-hub', 'health', 'temperature_critical', document.getElementById('temp_critical').value); mem_warning: parseInt(document.getElementById('mem_warning').value) || 80,
mem_critical: parseInt(document.getElementById('mem_critical').value) || 95,
disk_warning: parseInt(document.getElementById('disk_warning').value) || 80,
disk_critical: parseInt(document.getElementById('disk_critical').value) || 95,
temp_warning: parseInt(document.getElementById('temp_warning').value) || 70,
temp_critical: parseInt(document.getElementById('temp_critical').value) || 85
};
return uci.save(); API.saveSettings(
}).then(function() { settingsData.auto_refresh,
return uci.apply(); settingsData.health_check,
}).then(function() { settingsData.debug_mode,
settingsData.refresh_interval,
settingsData.log_retention,
settingsData.cpu_warning,
settingsData.cpu_critical,
settingsData.mem_warning,
settingsData.mem_critical,
settingsData.disk_warning,
settingsData.disk_critical,
settingsData.temp_warning,
settingsData.temp_critical
).then(function(response) {
ui.hideModal(); ui.hideModal();
ui.addNotification(null, E('p', {}, '✅ Paramètres sauvegardés!'), 'success'); if (response.success) {
ui.addNotification(null, E('p', {}, 'Paramètres sauvegardés avec succès'), 'success');
} else {
ui.addNotification(null, E('p', {}, 'Erreur: ' + (response.message || 'Unknown error')), 'error');
}
}).catch(function(err) { }).catch(function(err) {
ui.hideModal(); ui.hideModal();
ui.addNotification(null, E('p', {}, '❌ Erreur: ' + err.message), 'error'); ui.addNotification(null, E('p', {}, 'Erreur: ' + (err.message || err)), 'error');
}); });
}, },

View File

@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
# System Hub RPCD Backend # System Hub RPCD Backend
# Central system control and monitoring # Central system control and monitoring
# Version: 0.1.1
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
@ -98,25 +99,29 @@ get_system_info() {
get_health() { get_health() {
json_init json_init
# CPU info # CPU usage calculation
local cpu_model=$(awk -F': ' '/model name|Processor/ {print $2; exit}' /proc/cpuinfo 2>/dev/null || echo "Unknown")
local cpu_cores=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo 1) local cpu_cores=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo 1)
json_add_object "cpu"
json_add_string "model" "$cpu_model"
json_add_int "cores" "$cpu_cores"
json_close_object
# Load average
local load=$(cat /proc/loadavg 2>/dev/null || echo "0 0 0") local load=$(cat /proc/loadavg 2>/dev/null || echo "0 0 0")
local load1=$(echo $load | awk '{print $1}') local load1=$(echo $load | awk '{print $1}')
local load5=$(echo $load | awk '{print $2}') local load5=$(echo $load | awk '{print $2}')
local load15=$(echo $load | awk '{print $3}') local load15=$(echo $load | awk '{print $3}')
json_add_object "load" # Calculate CPU usage percentage (load / cores * 100)
json_add_string "1min" "$load1" local cpu_usage=$(awk -v load="$load1" -v cores="$cpu_cores" 'BEGIN { printf "%.0f", (load / cores) * 100 }')
json_add_string "5min" "$load5" [ "$cpu_usage" -gt 100 ] && cpu_usage=100
json_add_string "15min" "$load15"
# CPU status
local cpu_status="ok"
[ "$cpu_usage" -ge 80 ] && cpu_status="warning"
[ "$cpu_usage" -ge 95 ] && cpu_status="critical"
json_add_object "cpu"
json_add_int "usage" "$cpu_usage"
json_add_string "status" "$cpu_status"
json_add_string "load_1m" "$load1"
json_add_string "load_5m" "$load5"
json_add_string "load_15m" "$load15"
json_add_int "cores" "$cpu_cores"
json_close_object json_close_object
# Memory # Memory
@ -127,11 +132,16 @@ get_health() {
local mem_cached=$(awk '/^Cached/ {print $2}' /proc/meminfo 2>/dev/null || echo 0) local mem_cached=$(awk '/^Cached/ {print $2}' /proc/meminfo 2>/dev/null || echo 0)
local mem_used=$((mem_total - mem_available)) local mem_used=$((mem_total - mem_available))
local mem_percent=0 local mem_usage=0
if [ "$mem_total" -gt 0 ]; then if [ "$mem_total" -gt 0 ]; then
mem_percent=$(( (mem_used * 100) / mem_total )) mem_usage=$(( (mem_used * 100) / mem_total ))
fi fi
# Memory status
local mem_status="ok"
[ "$mem_usage" -ge 80 ] && mem_status="warning"
[ "$mem_usage" -ge 95 ] && mem_status="critical"
json_add_object "memory" json_add_object "memory"
json_add_int "total_kb" "$mem_total" json_add_int "total_kb" "$mem_total"
json_add_int "free_kb" "$mem_free" json_add_int "free_kb" "$mem_free"
@ -139,38 +149,136 @@ get_health() {
json_add_int "used_kb" "$mem_used" json_add_int "used_kb" "$mem_used"
json_add_int "buffers_kb" "$mem_buffers" json_add_int "buffers_kb" "$mem_buffers"
json_add_int "cached_kb" "$mem_cached" json_add_int "cached_kb" "$mem_cached"
json_add_int "percent" "$mem_percent" json_add_int "usage" "$mem_usage"
json_add_string "status" "$mem_status"
json_close_object json_close_object
# Storage # Disk (root filesystem)
json_add_array "storage" local disk_total=$(df / | awk 'NR==2 {print $2}')
df -h | tail -n +2 | while read filesystem size used avail percent mountpoint; do local disk_used=$(df / | awk 'NR==2 {print $3}')
local percent_num=$(echo $percent | tr -d '%') local disk_usage=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}' 2>/dev/null || echo 0)
json_add_object ""
json_add_string "filesystem" "$filesystem" # Disk status
json_add_string "size" "$size" local disk_status="ok"
json_add_string "used" "$used" [ "$disk_usage" -ge 80 ] && disk_status="warning"
json_add_string "available" "$avail" [ "$disk_usage" -ge 95 ] && disk_status="critical"
json_add_int "percent" "$percent_num"
json_add_string "mountpoint" "$mountpoint" json_add_object "disk"
json_add_int "total_kb" "$disk_total"
json_add_int "used_kb" "$disk_used"
json_add_int "usage" "$disk_usage"
json_add_string "status" "$disk_status"
json_close_object json_close_object
done
json_close_array
# Temperature # Temperature
json_add_array "temperatures" local temp_value=0
local temp_status="ok"
for zone in /sys/class/thermal/thermal_zone*/temp; do for zone in /sys/class/thermal/thermal_zone*/temp; do
if [ -f "$zone" ]; then if [ -f "$zone" ]; then
local temp=$(cat "$zone" 2>/dev/null || echo 0) local temp=$(cat "$zone" 2>/dev/null || echo 0)
local temp_c=$((temp / 1000)) local temp_c=$((temp / 1000))
local zone_name=$(basename $(dirname "$zone")) # Use the highest temperature
[ "$temp_c" -gt "$temp_value" ] && temp_value="$temp_c"
json_add_object ""
json_add_string "zone" "$zone_name"
json_add_int "celsius" "$temp_c"
json_close_object
fi fi
done done
[ "$temp_value" -ge 70 ] && temp_status="warning"
[ "$temp_value" -ge 85 ] && temp_status="critical"
json_add_object "temperature"
json_add_int "value" "$temp_value"
json_add_string "status" "$temp_status"
json_close_object
# Network (WAN connectivity)
local wan_up=0
local wan_status="error"
if ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1; then
wan_up=1
wan_status="ok"
fi
json_add_object "network"
json_add_boolean "wan_up" "$wan_up"
json_add_string "status" "$wan_status"
json_close_object
# Services
local running_count=0
local failed_count=0
for service in /etc/init.d/*; do
[ -x "$service" ] || continue
local name=$(basename "$service")
case "$name" in
boot|done|functions|rc.*|sysctl|umount) continue ;;
esac
if [ -f "/etc/rc.d/S"*"$name" ]; then
if "$service" running >/dev/null 2>&1; then
running_count=$((running_count + 1))
else
failed_count=$((failed_count + 1))
fi
fi
done
json_add_object "services"
json_add_int "running" "$running_count"
json_add_int "failed" "$failed_count"
json_close_object
# Calculate overall health score
local score=100
# CPU impact (max -30)
if [ "$cpu_usage" -ge 95 ]; then
score=$((score - 30))
elif [ "$cpu_usage" -ge 80 ]; then
score=$((score - 15))
elif [ "$cpu_usage" -ge 60 ]; then
score=$((score - 5))
fi
# Memory impact (max -25)
if [ "$mem_usage" -ge 95 ]; then
score=$((score - 25))
elif [ "$mem_usage" -ge 80 ]; then
score=$((score - 12))
elif [ "$mem_usage" -ge 60 ]; then
score=$((score - 5))
fi
# Disk impact (max -20)
if [ "$disk_usage" -ge 95 ]; then
score=$((score - 20))
elif [ "$disk_usage" -ge 80 ]; then
score=$((score - 10))
fi
# Temperature impact (max -15)
if [ "$temp_value" -ge 85 ]; then
score=$((score - 15))
elif [ "$temp_value" -ge 70 ]; then
score=$((score - 7))
fi
# Network impact (max -10)
[ "$wan_up" -eq 0 ] && score=$((score - 10))
# Services impact (max -10)
[ "$failed_count" -gt 0 ] && score=$((score - 10))
json_add_int "score" "$score"
json_add_string "timestamp" "$(date '+%Y-%m-%d %H:%M:%S')"
# Recommendations
json_add_array "recommendations"
[ "$cpu_usage" -ge 80 ] && json_add_string "" "CPU usage is high ($cpu_usage%). Consider closing unnecessary services."
[ "$mem_usage" -ge 80 ] && json_add_string "" "Memory usage is high ($mem_usage%). Check for memory leaks."
[ "$disk_usage" -ge 80 ] && json_add_string "" "Disk usage is high ($disk_usage%). Clean up old files or logs."
[ "$temp_value" -ge 70 ] && json_add_string "" "Temperature is elevated (${temp_value}°C). Ensure proper ventilation."
[ "$wan_up" -eq 0 ] && json_add_string "" "WAN connection is down. Check network connectivity."
[ "$failed_count" -gt 0 ] && json_add_string "" "$failed_count service(s) enabled but not running. Check service status."
json_close_array json_close_array
json_dump json_dump
@ -408,6 +516,143 @@ get_storage() {
json_dump json_dump
} }
# Get settings
get_settings() {
json_init
# Load UCI config if it exists
local config_loaded=0
if [ -f "/etc/config/system-hub" ]; then
config_load system-hub
config_loaded=1
fi
# General settings
json_add_object "general"
config_get auto_refresh general auto_refresh "1"
config_get health_check general health_check "1"
config_get debug_mode general debug_mode "0"
config_get refresh_interval general refresh_interval "30"
config_get log_retention general log_retention "30"
json_add_boolean "auto_refresh" "${auto_refresh:-1}"
json_add_boolean "health_check" "${health_check:-1}"
json_add_boolean "debug_mode" "${debug_mode:-0}"
json_add_int "refresh_interval" "${refresh_interval:-30}"
json_add_int "log_retention" "${log_retention:-30}"
json_close_object
# Alert thresholds
json_add_object "thresholds"
config_get cpu_warning thresholds cpu_warning "80"
config_get cpu_critical thresholds cpu_critical "95"
config_get mem_warning thresholds mem_warning "80"
config_get mem_critical thresholds mem_critical "95"
config_get disk_warning thresholds disk_warning "80"
config_get disk_critical thresholds disk_critical "95"
config_get temp_warning thresholds temp_warning "70"
config_get temp_critical thresholds temp_critical "85"
json_add_int "cpu_warning" "${cpu_warning:-80}"
json_add_int "cpu_critical" "${cpu_critical:-95}"
json_add_int "mem_warning" "${mem_warning:-80}"
json_add_int "mem_critical" "${mem_critical:-95}"
json_add_int "disk_warning" "${disk_warning:-80}"
json_add_int "disk_critical" "${disk_critical:-95}"
json_add_int "temp_warning" "${temp_warning:-70}"
json_add_int "temp_critical" "${temp_critical:-85}"
json_close_object
# Scheduled tasks
json_add_object "schedules"
config_get health_report schedules health_report "1"
config_get backup_weekly schedules backup_weekly "1"
config_get log_cleanup schedules log_cleanup "1"
json_add_boolean "health_report" "${health_report:-1}"
json_add_boolean "backup_weekly" "${backup_weekly:-1}"
json_add_boolean "log_cleanup" "${log_cleanup:-1}"
json_close_object
# Upload settings
json_add_object "upload"
config_get auto_upload upload auto_upload "0"
config_get upload_url upload url ""
config_get upload_token upload token ""
json_add_boolean "auto_upload" "${auto_upload:-0}"
json_add_string "url" "${upload_url:-}"
json_add_string "token" "${upload_token:-}"
json_close_object
# Support info
json_add_object "support"
config_get support_provider support provider "CyberMind.fr"
config_get support_email support email "support@cybermind.fr"
config_get support_docs support docs "https://docs.cybermind.fr"
json_add_string "provider" "${support_provider:-CyberMind.fr}"
json_add_string "email" "${support_email:-support@cybermind.fr}"
json_add_string "docs" "${support_docs:-https://docs.cybermind.fr}"
json_close_object
json_dump
}
# Save settings
save_settings() {
read -r input
json_load "$input"
# Parse settings from input
local section key value
# Create UCI config if it doesn't exist
if [ ! -f "/etc/config/system-hub" ]; then
touch /etc/config/system-hub
uci set system-hub.general=settings
uci set system-hub.thresholds=thresholds
uci set system-hub.schedules=schedules
uci set system-hub.upload=upload
uci set system-hub.support=support
fi
# This is a simplified version - in production you'd parse the JSON properly
# For now, we'll extract specific values
json_get_var auto_refresh auto_refresh
json_get_var health_check health_check
json_get_var debug_mode debug_mode
json_get_var refresh_interval refresh_interval
json_get_var log_retention log_retention
json_get_var cpu_warning cpu_warning
json_get_var cpu_critical cpu_critical
json_get_var mem_warning mem_warning
json_get_var mem_critical mem_critical
json_get_var disk_warning disk_warning
json_get_var disk_critical disk_critical
json_get_var temp_warning temp_warning
json_get_var temp_critical temp_critical
json_cleanup
# Save to UCI
[ -n "$auto_refresh" ] && uci set system-hub.general.auto_refresh="$auto_refresh"
[ -n "$health_check" ] && uci set system-hub.general.health_check="$health_check"
[ -n "$debug_mode" ] && uci set system-hub.general.debug_mode="$debug_mode"
[ -n "$refresh_interval" ] && uci set system-hub.general.refresh_interval="$refresh_interval"
[ -n "$log_retention" ] && uci set system-hub.general.log_retention="$log_retention"
[ -n "$cpu_warning" ] && uci set system-hub.thresholds.cpu_warning="$cpu_warning"
[ -n "$cpu_critical" ] && uci set system-hub.thresholds.cpu_critical="$cpu_critical"
[ -n "$mem_warning" ] && uci set system-hub.thresholds.mem_warning="$mem_warning"
[ -n "$mem_critical" ] && uci set system-hub.thresholds.mem_critical="$mem_critical"
[ -n "$disk_warning" ] && uci set system-hub.thresholds.disk_warning="$disk_warning"
[ -n "$disk_critical" ] && uci set system-hub.thresholds.disk_critical="$disk_critical"
[ -n "$temp_warning" ] && uci set system-hub.thresholds.temp_warning="$temp_warning"
[ -n "$temp_critical" ] && uci set system-hub.thresholds.temp_critical="$temp_critical"
uci commit system-hub
json_init
json_add_boolean "success" 1
json_add_string "message" "Settings saved successfully"
json_dump
}
# Main dispatcher # Main dispatcher
case "$1" in case "$1" in
list) list)
@ -422,7 +667,23 @@ case "$1" in
"backup_config": {}, "backup_config": {},
"restore_config": { "data": "string" }, "restore_config": { "data": "string" },
"reboot": {}, "reboot": {},
"get_storage": {} "get_storage": {},
"get_settings": {},
"save_settings": {
"auto_refresh": 1,
"health_check": 1,
"debug_mode": 0,
"refresh_interval": 30,
"log_retention": 30,
"cpu_warning": 80,
"cpu_critical": 95,
"mem_warning": 80,
"mem_critical": 95,
"disk_warning": 80,
"disk_critical": 95,
"temp_warning": 70,
"temp_critical": 85
}
} }
EOF EOF
;; ;;
@ -438,6 +699,8 @@ EOF
restore_config) restore_config ;; restore_config) restore_config ;;
reboot) reboot_system ;; reboot) reboot_system ;;
get_storage) get_storage ;; get_storage) get_storage ;;
get_settings) get_settings ;;
save_settings) save_settings ;;
*) *)
json_init json_init
json_add_boolean "success" 0 json_add_boolean "success" 0

View File

@ -9,7 +9,8 @@
"get_health", "get_health",
"list_services", "list_services",
"get_logs", "get_logs",
"get_storage" "get_storage",
"get_settings"
] ]
} }
}, },
@ -19,7 +20,8 @@
"service_action", "service_action",
"backup_config", "backup_config",
"restore_config", "restore_config",
"reboot" "reboot",
"save_settings"
] ]
} }
} }