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
|
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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 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',
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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: [],
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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"}'
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 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() {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user