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:
parent
6e0182ad35
commit
5902ac500a
1181
LUCI_DEVELOPMENT_REFERENCE.md
Normal file
1181
LUCI_DEVELOPMENT_REFERENCE.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-secubox
|
||||
PKG_VERSION:=0.1.0
|
||||
PKG_VERSION:=0.1.1
|
||||
PKG_RELEASE:=1
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
|
||||
/* Controls */
|
||||
.secubox-alerts-controls {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
@ -61,7 +61,7 @@
|
||||
.secubox-filter-group label {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
.secubox-alert-stat-card {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
@ -114,7 +114,7 @@
|
||||
|
||||
/* Alert Item */
|
||||
.secubox-alert-item {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
@ -170,12 +170,12 @@
|
||||
|
||||
.secubox-alert-module {
|
||||
font-size: 16px;
|
||||
color: #1e293b;
|
||||
color: var(--sb-text);
|
||||
}
|
||||
|
||||
.secubox-alert-time {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
}
|
||||
|
||||
.secubox-alert-message {
|
||||
@ -225,7 +225,7 @@
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
@ -256,7 +256,7 @@
|
||||
.secubox-empty-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
||||
@ -89,6 +89,12 @@ var callDashboardData = rpc.declare({
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callGetTheme = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
method: 'get_theme',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds) return '0s';
|
||||
var d = Math.floor(seconds / 86400);
|
||||
@ -121,6 +127,7 @@ return baseclass.extend({
|
||||
getAlerts: callAlerts,
|
||||
quickAction: callQuickAction,
|
||||
getDashboardData: callDashboardData,
|
||||
getTheme: callGetTheme,
|
||||
formatUptime: formatUptime,
|
||||
formatBytes: formatBytes
|
||||
});
|
||||
|
||||
@ -66,10 +66,11 @@
|
||||
}
|
||||
|
||||
.secubox-stat-card {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border: 1px solid var(--sb-border);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2px 8px var(--sb-shadow);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
@ -78,7 +79,7 @@
|
||||
|
||||
.secubox-stat-card:hover {
|
||||
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 {
|
||||
@ -95,11 +96,12 @@
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
color: var(--sb-text);
|
||||
}
|
||||
|
||||
.secubox-stat-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
@ -126,18 +128,20 @@
|
||||
|
||||
/* Card */
|
||||
.secubox-card {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border: 1px solid var(--sb-border);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2px 8px var(--sb-shadow);
|
||||
}
|
||||
|
||||
.secubox-card-title {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
border-bottom: 2px solid #f1f5f9;
|
||||
color: var(--sb-text);
|
||||
color: var(--sb-text);
|
||||
border-bottom: 2px solid var(--sb-border);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
@ -188,7 +192,7 @@
|
||||
|
||||
.secubox-gauge-label {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
@ -196,7 +200,7 @@
|
||||
|
||||
.secubox-gauge-details {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
text-align: center;
|
||||
max-width: 140px;
|
||||
}
|
||||
@ -220,7 +224,7 @@
|
||||
}
|
||||
|
||||
.secubox-module-mini-card {
|
||||
background: #f8fafc;
|
||||
background: var(--sb-bg);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
transition: all 0.2s;
|
||||
@ -228,7 +232,7 @@
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
@ -274,13 +278,13 @@
|
||||
.secubox-module-mini-name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
color: var(--sb-text);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.secubox-module-mini-status {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
}
|
||||
|
||||
.secubox-module-running .secubox-module-mini-status {
|
||||
@ -295,8 +299,8 @@
|
||||
}
|
||||
|
||||
.secubox-action-btn {
|
||||
background: white;
|
||||
border: 2px solid #e2e8f0;
|
||||
background: var(--sb-bg-card);
|
||||
border: 2px solid var(--sb-border);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
@ -320,7 +324,7 @@
|
||||
}
|
||||
|
||||
.secubox-action-label {
|
||||
color: #1e293b;
|
||||
color: var(--sb-text);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
/* Chart Card */
|
||||
.secubox-chart-card {
|
||||
background: white;
|
||||
background: var(--sb-bg-card);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
@ -42,7 +42,7 @@
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
color: var(--sb-text);
|
||||
}
|
||||
|
||||
.secubox-chart-container {
|
||||
@ -50,7 +50,7 @@
|
||||
height: 200px;
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
background: #f8fafc;
|
||||
background: var(--sb-bg);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -76,7 +76,7 @@
|
||||
|
||||
.secubox-chart-unit {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
@ -93,7 +93,7 @@
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
background: var(--sb-bg);
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
@ -113,14 +113,14 @@
|
||||
|
||||
.secubox-stat-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
color: var(--sb-text-muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.secubox-stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
color: var(--sb-text);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
|
||||
@ -2,19 +2,39 @@
|
||||
* SecuBox Dashboard Styles
|
||||
* Copyright (C) 2025 CyberMind.fr
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Theme-aware styles with dark/light mode support
|
||||
*/
|
||||
|
||||
/* Common colors (theme-independent) */
|
||||
:root {
|
||||
--sb-primary: #3b82f6;
|
||||
--sb-primary-dark: #1e40af;
|
||||
--sb-success: #22c55e;
|
||||
--sb-warning: #f59e0b;
|
||||
--sb-danger: #ef4444;
|
||||
}
|
||||
|
||||
/* Dark theme (default) */
|
||||
:root,
|
||||
[data-theme="dark"] {
|
||||
--sb-bg: #0f172a;
|
||||
--sb-bg-card: #1e293b;
|
||||
--sb-border: #334155;
|
||||
--sb-text: #f1f5f9;
|
||||
--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 {
|
||||
@ -42,7 +62,7 @@
|
||||
|
||||
.secubox-stat-box:hover {
|
||||
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 {
|
||||
@ -236,7 +256,7 @@
|
||||
|
||||
.secubox-module-card:hover {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
}
|
||||
});
|
||||
@ -3,15 +3,24 @@
|
||||
'require ui';
|
||||
'require dom';
|
||||
'require secubox/api as API';
|
||||
'require secubox/theme as Theme';
|
||||
'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', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('secubox/alerts.css')
|
||||
}));
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
return view.extend({
|
||||
alertsData: null,
|
||||
filterSeverity: 'all',
|
||||
|
||||
@ -2,16 +2,25 @@
|
||||
'require view';
|
||||
'require ui';
|
||||
'require secubox/api as API';
|
||||
'require secubox/theme as Theme';
|
||||
'require dom';
|
||||
'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', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('secubox/dashboard.css')
|
||||
}));
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
return view.extend({
|
||||
dashboardData: null,
|
||||
healthData: null,
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require secubox/theme as Theme';
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
var callModules = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require secubox/theme as Theme';
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require secubox/theme as Theme';
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
var callModules = rpc.declare({
|
||||
object: 'luci.secubox',
|
||||
|
||||
@ -3,15 +3,24 @@
|
||||
'require ui';
|
||||
'require dom';
|
||||
'require secubox/api as API';
|
||||
'require secubox/theme as Theme';
|
||||
'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', {
|
||||
'rel': 'stylesheet',
|
||||
'type': 'text/css',
|
||||
'href': L.resource('secubox/monitoring.css')
|
||||
}));
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
return view.extend({
|
||||
cpuHistory: [],
|
||||
memoryHistory: [],
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require secubox/api as API';
|
||||
'require secubox/theme as Theme';
|
||||
|
||||
// Initialize theme
|
||||
Theme.init();
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
|
||||
@ -86,7 +86,7 @@ get_status() {
|
||||
local running=0
|
||||
|
||||
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')"
|
||||
|
||||
# System info
|
||||
@ -590,7 +590,7 @@ get_dashboard_data() {
|
||||
local mem_pct=$((mem_used * 100 / mem_total))
|
||||
|
||||
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_int "uptime" "$uptime"
|
||||
json_add_string "load" "$load"
|
||||
@ -667,6 +667,21 @@ get_dashboard_data() {
|
||||
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
|
||||
case "$1" in
|
||||
list)
|
||||
@ -703,6 +718,8 @@ case "$1" in
|
||||
json_close_object
|
||||
json_add_object "get_dashboard_data"
|
||||
json_close_object
|
||||
json_add_object "get_theme"
|
||||
json_close_object
|
||||
json_dump
|
||||
;;
|
||||
call)
|
||||
@ -764,6 +781,9 @@ case "$1" in
|
||||
get_dashboard_data)
|
||||
get_dashboard_data
|
||||
;;
|
||||
get_theme)
|
||||
get_theme
|
||||
;;
|
||||
*)
|
||||
echo '{"error":"Unknown method"}'
|
||||
;;
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
"diagnostics",
|
||||
"get_system_health",
|
||||
"get_alerts",
|
||||
"get_dashboard_data"
|
||||
"get_dashboard_data",
|
||||
"get_theme"
|
||||
],
|
||||
"uci": [
|
||||
"get",
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-system-hub
|
||||
PKG_VERSION:=0.0.2
|
||||
PKG_VERSION:=0.1.1
|
||||
PKG_RELEASE:=1
|
||||
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_DESCRIPTION:=Central system control with monitoring, services, logs, and backup
|
||||
|
||||
@ -124,14 +124,13 @@ Output:
|
||||
```json
|
||||
{
|
||||
"cpu": {
|
||||
"model": "ARM Cortex-A72",
|
||||
"usage": 25,
|
||||
"status": "ok",
|
||||
"load_1m": "0.25",
|
||||
"load_5m": "0.30",
|
||||
"load_15m": "0.28",
|
||||
"cores": 4
|
||||
},
|
||||
"load": {
|
||||
"1min": "0.25",
|
||||
"5min": "0.30",
|
||||
"15min": "0.28"
|
||||
},
|
||||
"memory": {
|
||||
"total_kb": 4096000,
|
||||
"free_kb": 2048000,
|
||||
@ -139,23 +138,31 @@ Output:
|
||||
"used_kb": 1024000,
|
||||
"buffers_kb": 512000,
|
||||
"cached_kb": 1536000,
|
||||
"percent": 25
|
||||
"usage": 25,
|
||||
"status": "ok"
|
||||
},
|
||||
"storage": [
|
||||
{
|
||||
"filesystem": "/dev/mmcblk0p2",
|
||||
"size": "29G",
|
||||
"used": "5.2G",
|
||||
"available": "22G",
|
||||
"percent": 19,
|
||||
"mountpoint": "/"
|
||||
}
|
||||
],
|
||||
"temperatures": [
|
||||
{
|
||||
"zone": "thermal_zone0",
|
||||
"celsius": 45
|
||||
}
|
||||
"disk": {
|
||||
"total_kb": 30408704,
|
||||
"used_kb": 5447680,
|
||||
"usage": 19,
|
||||
"status": "ok"
|
||||
},
|
||||
"temperature": {
|
||||
"value": 45,
|
||||
"status": "ok"
|
||||
},
|
||||
"network": {
|
||||
"wan_up": true,
|
||||
"status": "ok"
|
||||
},
|
||||
"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
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
### status()
|
||||
@ -359,6 +430,75 @@ Reboot the system (3-second delay).
|
||||
|
||||
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
|
||||
|
||||
- Hostname: `/proc/sys/kernel/hostname`
|
||||
@ -438,8 +578,37 @@ Apache-2.0
|
||||
|
||||
## Maintainer
|
||||
|
||||
SecuBox Project <support@secubox.com>
|
||||
CyberMind <contact@cybermind.fr>
|
||||
|
||||
## 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
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
* System Hub API
|
||||
* Package: luci-app-system-hub
|
||||
* RPCD object: luci.system-hub
|
||||
* Version: 0.0.2-debug
|
||||
* Version: 0.1.1
|
||||
*/
|
||||
|
||||
// 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({
|
||||
object: 'luci.system-hub',
|
||||
@ -75,6 +75,19 @@ var callGetStorage = rpc.declare({
|
||||
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({
|
||||
// RPC methods - exposed via ubus
|
||||
getStatus: callStatus,
|
||||
@ -86,5 +99,7 @@ return baseclass.extend({
|
||||
backupConfig: callBackupConfig,
|
||||
restoreConfig: callRestoreConfig,
|
||||
reboot: callReboot,
|
||||
getStorage: callGetStorage
|
||||
getStorage: callGetStorage,
|
||||
getSettings: callGetSettings,
|
||||
saveSettings: callSaveSettings
|
||||
});
|
||||
|
||||
@ -1,17 +1,9 @@
|
||||
/* System Hub Dashboard - Central Control Theme */
|
||||
/* Copyright (C) 2024 CyberMind.fr - Gandalf */
|
||||
/* Theme-aware styles with dark/light mode support */
|
||||
|
||||
/* Common variables (theme-independent) */
|
||||
: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-violet: #8b5cf6;
|
||||
--sh-accent-blue: #3b82f6;
|
||||
@ -19,24 +11,55 @@
|
||||
--sh-accent-green: #22c55e;
|
||||
--sh-accent-amber: #f59e0b;
|
||||
--sh-accent-red: #ef4444;
|
||||
|
||||
|
||||
--sh-success: #22c55e;
|
||||
--sh-warning: #f59e0b;
|
||||
--sh-danger: #ef4444;
|
||||
--sh-info: #3b82f6;
|
||||
|
||||
|
||||
--sh-gradient: linear-gradient(135deg, #6366f1, #8b5cf6, #a855f7);
|
||||
--sh-gradient-soft: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.1));
|
||||
|
||||
|
||||
--sh-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
||||
--sh-font-sans: 'Inter', -apple-system, sans-serif;
|
||||
|
||||
|
||||
--sh-radius: 8px;
|
||||
--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-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 */
|
||||
.system-hub-dashboard {
|
||||
font-family: var(--sh-font-sans);
|
||||
|
||||
@ -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';
|
||||
}
|
||||
});
|
||||
@ -2,6 +2,17 @@
|
||||
'require view';
|
||||
'require ui';
|
||||
'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({
|
||||
load: function() {
|
||||
|
||||
@ -2,8 +2,18 @@
|
||||
'require view';
|
||||
'require dom';
|
||||
'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
|
||||
function getHealthStatus(score) {
|
||||
@ -24,7 +34,7 @@ function formatBytes(bytes) {
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return api.getHealth();
|
||||
return API.getHealth();
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
|
||||
@ -2,6 +2,17 @@
|
||||
'require view';
|
||||
'require ui';
|
||||
'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({
|
||||
load: function() {
|
||||
|
||||
@ -2,6 +2,17 @@
|
||||
'require view';
|
||||
'require poll';
|
||||
'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({
|
||||
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;' });
|
||||
|
||||
// CPU Load Gauge
|
||||
var cpuLoad = parseFloat(health.load ? health.load['1min'] : status.health ? status.health.cpu_load : '0');
|
||||
var cpuPercent = Math.min((cpuLoad * 100 / (health.cpu ? health.cpu.cores : 1)), 100);
|
||||
var cpuLoad = parseFloat(health.cpu ? health.cpu.load_1m : '0');
|
||||
var cpuPercent = health.cpu ? health.cpu.usage : 0;
|
||||
gaugesContainer.appendChild(this.createGauge('CPU Load', cpuPercent, cpuLoad.toFixed(2)));
|
||||
|
||||
// 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 memTotal = health.memory ? (health.memory.total_kb / 1024).toFixed(0) : 0;
|
||||
gaugesContainer.appendChild(this.createGauge('Memory', memPercent, memUsed + ' / ' + memTotal + ' MB'));
|
||||
|
||||
// Disk Gauge
|
||||
var diskPercent = status.disk_percent || 0;
|
||||
var diskInfo = '';
|
||||
if (health.storage && health.storage.length > 0) {
|
||||
var root = health.storage.find(function(s) { return s.mountpoint === '/'; });
|
||||
if (root) {
|
||||
diskPercent = root.percent;
|
||||
diskInfo = root.used + ' / ' + root.size;
|
||||
}
|
||||
}
|
||||
gaugesContainer.appendChild(this.createGauge('Disk Usage', diskPercent, diskInfo || diskPercent + '%'));
|
||||
var diskPercent = health.disk ? health.disk.usage : 0;
|
||||
var diskUsed = health.disk ? (health.disk.used_kb / 1024).toFixed(0) : 0;
|
||||
var diskTotal = health.disk ? (health.disk.total_kb / 1024).toFixed(0) : 0;
|
||||
var diskInfo = diskUsed + ' / ' + diskTotal + ' MB';
|
||||
gaugesContainer.appendChild(this.createGauge('Disk Usage', diskPercent, diskInfo));
|
||||
|
||||
healthSection.appendChild(gaugesContainer);
|
||||
v.appendChild(healthSection);
|
||||
@ -99,19 +105,19 @@ return L.view.extend({
|
||||
E('h3', {}, _('CPU Information')),
|
||||
E('div', { 'class': 'table' }, [
|
||||
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('strong', {}, _('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': 'td left' }, [
|
||||
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
|
||||
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' }, [
|
||||
E('h3', {}, _('Temperature'))
|
||||
]);
|
||||
|
||||
var tempTable = E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Zone')),
|
||||
E('th', { 'class': 'th' }, _('Temperature'))
|
||||
E('h3', {}, _('Temperature')),
|
||||
E('div', { 'class': 'table' }, [
|
||||
E('div', { 'class': 'tr' }, [
|
||||
E('div', { 'class': 'td left' }, [
|
||||
E('strong', {}, _('System Temperature: ')),
|
||||
E('span', { 'style': 'color: ' + tempColor + '; font-weight: bold;' }, tempValue + '°C')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Storage
|
||||
if (health.storage && health.storage.length > 0) {
|
||||
// Storage (Root Filesystem)
|
||||
if (health.disk) {
|
||||
var diskColor = health.disk.usage > 90 ? 'red' : (health.disk.usage > 75 ? 'orange' : 'green');
|
||||
var storageSection = E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Storage'))
|
||||
]);
|
||||
|
||||
var storageTable = E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Mountpoint')),
|
||||
E('th', { 'class': 'th' }, _('Filesystem')),
|
||||
E('th', { 'class': 'th' }, _('Size')),
|
||||
E('th', { 'class': 'th' }, _('Used')),
|
||||
E('th', { 'class': 'th' }, _('Available')),
|
||||
E('th', { 'class': 'th' }, _('Use %'))
|
||||
])
|
||||
]);
|
||||
|
||||
health.storage.forEach(function(storage) {
|
||||
var color = storage.percent > 90 ? 'red' : (storage.percent > 75 ? 'orange' : 'green');
|
||||
storageTable.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
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': 'background: ' + color + '; width: ' + storage.percent + '%; height: 100%; border-radius: 5px;'
|
||||
})
|
||||
]),
|
||||
E('span', {}, storage.percent + '%')
|
||||
E('h3', {}, _('Storage (Root Filesystem)')),
|
||||
E('div', { 'class': 'table' }, [
|
||||
E('div', { 'class': 'tr' }, [
|
||||
E('div', { 'class': 'td left', 'width': '50%' }, [
|
||||
E('strong', {}, _('Total: ')),
|
||||
E('span', {}, (health.disk.total_kb / 1024).toFixed(0) + ' MB')
|
||||
]),
|
||||
E('div', { 'class': 'td left', 'width': '50%' }, [
|
||||
E('strong', {}, _('Used: ')),
|
||||
E('span', {}, (health.disk.used_kb / 1024).toFixed(0) + ' MB')
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'tr' }, [
|
||||
E('div', { 'class': 'td left' }, [
|
||||
E('strong', {}, _('Usage: ')),
|
||||
E('div', { 'style': 'display: inline-flex; align-items: center; width: 200px;' }, [
|
||||
E('div', { 'style': 'flex: 1; background: #eee; height: 10px; border-radius: 5px; margin-right: 10px;' }, [
|
||||
E('div', {
|
||||
'style': 'background: ' + diskColor + '; width: ' + health.disk.usage + '%; height: 100%; border-radius: 5px;'
|
||||
})
|
||||
]),
|
||||
E('span', { 'style': 'font-weight: bold; color: ' + diskColor }, health.disk.usage + '%')
|
||||
])
|
||||
])
|
||||
])
|
||||
]));
|
||||
});
|
||||
|
||||
storageSection.appendChild(storageTable);
|
||||
])
|
||||
]);
|
||||
v.appendChild(storageSection);
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,17 @@
|
||||
'require view';
|
||||
'require ui';
|
||||
'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({
|
||||
load: function() {
|
||||
|
||||
@ -1,24 +1,40 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require uci';
|
||||
'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({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
api.getStatus(),
|
||||
Promise.resolve({ schedules: [] }) // Stub: No schedules yet
|
||||
API.getSettings(),
|
||||
API.getStatus()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0];
|
||||
var schedules = data[1].schedules || [];
|
||||
var settings = data[0] || {};
|
||||
var status = data[1];
|
||||
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' }, [
|
||||
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-body' }, [
|
||||
this.renderToggle('🔄', 'Rafraîchissement automatique', 'Mettre à jour le dashboard toutes les 30s', true, 'cfg_refresh'),
|
||||
this.renderToggle('💚', 'Vérification santé auto', 'Exécuter un health check toutes les heures', true, 'cfg_health'),
|
||||
this.renderToggle('🐛', 'Mode Debug', 'Activer les logs détaillés', false, 'cfg_debug'),
|
||||
|
||||
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', general.health_check !== false, 'cfg_health'),
|
||||
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('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('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('label', { 'class': 'sh-form-label' }, 'CPU Warning / Critical (%)'),
|
||||
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': '95', 'id': 'cpu_critical', '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': thresholds.cpu_critical || '95', 'id': 'cpu_critical', 'style': 'width: 100px;' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, 'Mémoire Warning / Critical (%)'),
|
||||
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': '95', 'id': 'mem_critical', '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': thresholds.mem_critical || '95', 'id': 'mem_critical', 'style': 'width: 100px;' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, 'Disque Warning / Critical (%)'),
|
||||
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': '95', 'id': 'disk_critical', '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': thresholds.disk_critical || '95', 'id': 'disk_critical', 'style': 'width: 100px;' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, 'Température Warning / Critical (°C)'),
|
||||
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': '85', 'id': 'temp_critical', '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': thresholds.temp_critical || '85', 'id': 'temp_critical', 'style': 'width: 100px;' })
|
||||
])
|
||||
])
|
||||
])
|
||||
@ -86,30 +102,30 @@ 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-body' }, [
|
||||
this.renderToggle('📋', 'Rapport Santé Quotidien', 'Tous les jours à 6h00', true, 'sched_health'),
|
||||
this.renderToggle('💾', 'Sauvegarde Hebdomadaire', 'Dimanche à 3h00, garde 4 versions', true, 'sched_backup'),
|
||||
this.renderToggle('🧹', 'Nettoyage Logs', 'Supprimer logs > 30 jours', true, 'sched_cleanup')
|
||||
this.renderToggle('📋', 'Rapport Santé Quotidien', 'Tous les jours à 6h00', schedules.health_report !== false, 'sched_health'),
|
||||
this.renderToggle('💾', 'Sauvegarde Hebdomadaire', 'Dimanche à 3h00, garde 4 versions', schedules.backup_weekly !== false, 'sched_backup'),
|
||||
this.renderToggle('🧹', 'Nettoyage Logs', 'Supprimer logs > 30 jours', schedules.log_cleanup !== false, 'sched_cleanup')
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
// Upload Configuration
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '☁️'), 'Upload Diagnostics' ])
|
||||
]),
|
||||
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('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('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': '••••••••' })
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
// Support Info
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
@ -118,15 +134,15 @@ return view.extend({
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
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('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('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' })
|
||||
]);
|
||||
|
||||
uci.load('system-hub').then(function() {
|
||||
// Save health thresholds
|
||||
uci.set('system-hub', 'health', 'cpu_warning', document.getElementById('cpu_warning').value);
|
||||
uci.set('system-hub', 'health', 'cpu_critical', document.getElementById('cpu_critical').value);
|
||||
uci.set('system-hub', 'health', 'memory_warning', document.getElementById('mem_warning').value);
|
||||
uci.set('system-hub', 'health', 'memory_critical', document.getElementById('mem_critical').value);
|
||||
uci.set('system-hub', 'health', 'disk_warning', document.getElementById('disk_warning').value);
|
||||
uci.set('system-hub', 'health', 'disk_critical', document.getElementById('disk_critical').value);
|
||||
uci.set('system-hub', 'health', 'temperature_warning', document.getElementById('temp_warning').value);
|
||||
uci.set('system-hub', 'health', 'temperature_critical', document.getElementById('temp_critical').value);
|
||||
// Collect all settings from form
|
||||
var settingsData = {
|
||||
auto_refresh: document.getElementById('cfg_refresh').classList.contains('active') ? 1 : 0,
|
||||
health_check: document.getElementById('cfg_health').classList.contains('active') ? 1 : 0,
|
||||
debug_mode: document.getElementById('cfg_debug').classList.contains('active') ? 1 : 0,
|
||||
refresh_interval: parseInt(document.getElementById('cfg_interval').value) || 30,
|
||||
log_retention: parseInt(document.getElementById('cfg_retention').value) || 30,
|
||||
cpu_warning: parseInt(document.getElementById('cpu_warning').value) || 80,
|
||||
cpu_critical: parseInt(document.getElementById('cpu_critical').value) || 95,
|
||||
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();
|
||||
}).then(function() {
|
||||
return uci.apply();
|
||||
}).then(function() {
|
||||
API.saveSettings(
|
||||
settingsData.auto_refresh,
|
||||
settingsData.health_check,
|
||||
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.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) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, '❌ Erreur: ' + err.message), 'error');
|
||||
ui.addNotification(null, E('p', {}, 'Erreur: ' + (err.message || err)), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
# System Hub RPCD Backend
|
||||
# Central system control and monitoring
|
||||
# Version: 0.1.1
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
@ -98,25 +99,29 @@ get_system_info() {
|
||||
get_health() {
|
||||
json_init
|
||||
|
||||
# CPU info
|
||||
local cpu_model=$(awk -F': ' '/model name|Processor/ {print $2; exit}' /proc/cpuinfo 2>/dev/null || echo "Unknown")
|
||||
# CPU usage calculation
|
||||
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 load1=$(echo $load | awk '{print $1}')
|
||||
local load5=$(echo $load | awk '{print $2}')
|
||||
local load15=$(echo $load | awk '{print $3}')
|
||||
|
||||
json_add_object "load"
|
||||
json_add_string "1min" "$load1"
|
||||
json_add_string "5min" "$load5"
|
||||
json_add_string "15min" "$load15"
|
||||
# Calculate CPU usage percentage (load / cores * 100)
|
||||
local cpu_usage=$(awk -v load="$load1" -v cores="$cpu_cores" 'BEGIN { printf "%.0f", (load / cores) * 100 }')
|
||||
[ "$cpu_usage" -gt 100 ] && cpu_usage=100
|
||||
|
||||
# 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
|
||||
|
||||
# Memory
|
||||
@ -126,12 +131,17 @@ get_health() {
|
||||
local mem_buffers=$(awk '/Buffers/ {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_percent=0
|
||||
|
||||
local mem_usage=0
|
||||
if [ "$mem_total" -gt 0 ]; then
|
||||
mem_percent=$(( (mem_used * 100) / mem_total ))
|
||||
mem_usage=$(( (mem_used * 100) / mem_total ))
|
||||
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_int "total_kb" "$mem_total"
|
||||
json_add_int "free_kb" "$mem_free"
|
||||
@ -139,38 +149,136 @@ get_health() {
|
||||
json_add_int "used_kb" "$mem_used"
|
||||
json_add_int "buffers_kb" "$mem_buffers"
|
||||
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
|
||||
|
||||
# Storage
|
||||
json_add_array "storage"
|
||||
df -h | tail -n +2 | while read filesystem size used avail percent mountpoint; do
|
||||
local percent_num=$(echo $percent | tr -d '%')
|
||||
json_add_object ""
|
||||
json_add_string "filesystem" "$filesystem"
|
||||
json_add_string "size" "$size"
|
||||
json_add_string "used" "$used"
|
||||
json_add_string "available" "$avail"
|
||||
json_add_int "percent" "$percent_num"
|
||||
json_add_string "mountpoint" "$mountpoint"
|
||||
json_close_object
|
||||
done
|
||||
json_close_array
|
||||
# Disk (root filesystem)
|
||||
local disk_total=$(df / | awk 'NR==2 {print $2}')
|
||||
local disk_used=$(df / | awk 'NR==2 {print $3}')
|
||||
local disk_usage=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}' 2>/dev/null || echo 0)
|
||||
|
||||
# Disk status
|
||||
local disk_status="ok"
|
||||
[ "$disk_usage" -ge 80 ] && disk_status="warning"
|
||||
[ "$disk_usage" -ge 95 ] && disk_status="critical"
|
||||
|
||||
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
|
||||
|
||||
# Temperature
|
||||
json_add_array "temperatures"
|
||||
local temp_value=0
|
||||
local temp_status="ok"
|
||||
for zone in /sys/class/thermal/thermal_zone*/temp; do
|
||||
if [ -f "$zone" ]; then
|
||||
local temp=$(cat "$zone" 2>/dev/null || echo 0)
|
||||
local temp_c=$((temp / 1000))
|
||||
local zone_name=$(basename $(dirname "$zone"))
|
||||
|
||||
json_add_object ""
|
||||
json_add_string "zone" "$zone_name"
|
||||
json_add_int "celsius" "$temp_c"
|
||||
json_close_object
|
||||
# Use the highest temperature
|
||||
[ "$temp_c" -gt "$temp_value" ] && temp_value="$temp_c"
|
||||
fi
|
||||
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_dump
|
||||
@ -393,7 +501,7 @@ get_storage() {
|
||||
|
||||
df -h | tail -n +2 | while read filesystem size used avail percent mountpoint; do
|
||||
local percent_num=$(echo $percent | tr -d '%')
|
||||
|
||||
|
||||
json_add_object ""
|
||||
json_add_string "filesystem" "$filesystem"
|
||||
json_add_string "size" "$size"
|
||||
@ -408,6 +516,143 @@ get_storage() {
|
||||
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
|
||||
case "$1" in
|
||||
list)
|
||||
@ -422,7 +667,23 @@ case "$1" in
|
||||
"backup_config": {},
|
||||
"restore_config": { "data": "string" },
|
||||
"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
|
||||
;;
|
||||
@ -438,6 +699,8 @@ EOF
|
||||
restore_config) restore_config ;;
|
||||
reboot) reboot_system ;;
|
||||
get_storage) get_storage ;;
|
||||
get_settings) get_settings ;;
|
||||
save_settings) save_settings ;;
|
||||
*)
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
"get_health",
|
||||
"list_services",
|
||||
"get_logs",
|
||||
"get_storage"
|
||||
"get_storage",
|
||||
"get_settings"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -19,7 +20,8 @@
|
||||
"service_action",
|
||||
"backup_config",
|
||||
"restore_config",
|
||||
"reboot"
|
||||
"reboot",
|
||||
"save_settings"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user