This commit is contained in:
CyberMind-FR 2025-12-29 09:03:49 +01:00
parent 76955f48d0
commit 9b9becd0a8
31 changed files with 842 additions and 216 deletions

25
.claude/HISTORY.md Normal file
View File

@ -0,0 +1,25 @@
# SecuBox UI & Theme History
_Last updated: 2025-12-28_
1. **Unified Dashboard Refresh (2025-12-20)**
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
- Introduced shared `secubox/common.css` design tokens.
2. **Modules & Monitoring Modernization (2025-12-24)**
- Modules view adopted the same header/tabs plus live chip counters.
- Monitoring cards switched to SVG sparkline charts with auto-refresh.
3. **Alerts + Settings Overhaul (2025-12-27)**
- Alerts page now mirrors the dashboard style, dynamic header chips, and filtering controls.
- Settings view gained the SecuNav tabs, chips, and shared design language.
4. **Theme Synchronisation & Deployment (2025-12-28)**
- All SecuBox views call `Theme.init()` to respect dark/light/system preferences.
- Navigation bar now darkens automatically for dark/cyberpunk themes.
- Monitoring menu entry simplified (no `/overview` shim) to prevent LuCI tab duplication.
5. **Router Deployment Notes**
- Use `secubox-tools/deploy-secubox-dashboard.sh` for view-only pushes.
- Use `secubox-tools/deploy-secubox-v0.1.2.sh` for RPCD/config updates.
- Always clear `/tmp/luci-*` after copying UI assets.

21
.claude/TODO.md Normal file
View File

@ -0,0 +1,21 @@
# SecuBox UI TODOs (Claude Edition)
1. **Theme Manager Enhancements**
- Expose cyberpunk option inside SecuBox Settings (currently dark/light/system only).
- Persist theme choice back to `/etc/config/secubox` and reflect instantly without refresh.
2. **Navigation Component**
- Convert `SecuNav.renderTabs()` into a reusable LuCI widget (avoid duplicating `Theme.init` in each view).
- Provide a compact variant for nested modules (e.g., CDN Cache, Network Modes).
3. **Monitoring UX**
- Add empty-state copy while charts warm up.
- Display bandwidth units dynamically (Kbps/Mbps/Gbps) based on rate.
4. **Docs & Tooling**
- Document deployment scripts in `README.md` (what each script copies).
- Add lint/upload pre-check (LuCI `lua -l luci.dispatcher`) to prevent syntax errors before SCP.
5. **Testing**
- Capture screenshot baselines for dark/light themes (use `secubox-tools/`?).
- Automate browser cache busting (append `?v=<git sha>` to view URLs).

21
.claude/WIP.md Normal file
View File

@ -0,0 +1,21 @@
# Work In Progress (Claude)
## Active Threads
- **SecuBox Nav Theme Support**
Status: ✅ COMPLETE (2025-12-28)
Notes: All view tabs now respect theme colors; monitoring menu cleaned up.
- **Backup/Restore UX Alignment**
Status: ✅ COMPLETE
Notes: System Hub header and restore flow updated; API now accepts `file_name`.
## Next Up
- Port the new chip header layout to remaining SecuBox derivative apps (client-guardian, auth-guardian).
- Re-run deployment scripts on target routers after each batch to ensure consistency.
## Blockers / Risks
- Theme manager still only exposes dark/light/system options in Settings UI. Cyberpunk mode exists but is hidden.
- No automated regression tests for LuCI views; manual verification required after each SCP deploy.

21
.codex/HISTORY.md Normal file
View File

@ -0,0 +1,21 @@
# SecuBox Dev History (Codex Notes)
- **2025-12-18 Theme Foundations**
Added `secubox/common.css` (design tokens) + SecuNav component.
- **2025-12-20 Dashboard Revamp**
Dashboard got hero chips, module grid, and API auto-refresh.
- **2025-12-24 Modules + Monitoring Upgrade**
Modules view has filter tabs, responsive cards, and live stats.
Monitoring view now renders SVG spark lines with poll-based updates.
- **2025-12-26 Alerts + Settings Refresh**
Alerts converted to chip header + stats cards.
Settings adopted shared layout and is now theme-aware.
- **2025-12-28 Theme & Menu Fixes**
Every view initializes `Theme.init`.
Navigation tabs respond to dark/light/cyberpunk palettes.
Monitoring menu simplified (no `/overview` tab).
CSS updated so chip headers stay on a single row (with responsive wrap).

21
.codex/TODO.md Normal file
View File

@ -0,0 +1,21 @@
# TODO (Codex)
1. **Theme Selector**
- Extend SecuBox Settings to expose all Theme Manager variants (dark/light/system/cyberpunk).
- Live preview when flipping options; persist via RPC.
2. **Component Library**
- Extract header chips/nav tabs into standalone modules under `luci-static/resources/secubox/components/`.
- Provide TypeScript typings (or JS docstrings) for easier reuse.
3. **Validation Scripts**
- Add `npm run lint:ui` (eslint + prettier) for LuCI JS.
- Add `npm run check:luci` to run `lua -l luci.dispatcher` before SCP deploys.
4. **Docs**
- Update `.codex/context.md` with quick deployment recipes (`deploy-secubox-v0.1.2.sh`, etc.).
- Record router credentials requirements (currently warns about missing root password).
5. **Automation**
- Create `secubox-tools/deploy-theme-only.sh` for CSS/JS pushes (no RPC).
- Add `make snapshot` script to package updated LuCI app for feeds.

17
.codex/WIP.md Normal file
View File

@ -0,0 +1,17 @@
# WIP Tracker (Codex)
## Completed Today
- Synced navigation styling with theme preferences (dark/light/cyberpunk).
- Ensured Monitoring view hides legacy LuCI tab bar and uses direct menu route.
- Added Theme.init call to Settings view.
## In Progress
- Preparing follow-up refactor to deduplicate Theme initialization logic.
- Evaluating automated deployment pipeline (rsync/scp wrappers) for `secubox-tools`.
## Reminders
- After editing LuCI JS, always deploy via `secubox-tools/deploy-secubox-dashboard.sh` or targeted SCP + `rm -rf /tmp/luci-*`.
- Router currently lacks a root password; set one before exposing it to networks.

View File

@ -20,6 +20,7 @@ Ce document définit les standards, bonnes pratiques et validations obligatoires
7. [Common Errors & Solutions](#common-errors--solutions) 7. [Common Errors & Solutions](#common-errors--solutions)
8. [Validation Checklist](#validation-checklist) 8. [Validation Checklist](#validation-checklist)
9. [Deployment Procedures](#deployment-procedures) 9. [Deployment Procedures](#deployment-procedures)
10. [AI Assistant Context Files](#ai-assistant-context-files)
--- ---
@ -171,6 +172,8 @@ graph TB
**Slim variant:** When the page only needs 23 metrics, use `.sh-page-header-lite` + `.sh-header-chip` (see `luci-app-vhost-manager` and `luci-app-secubox` settings). Chips carry an emoji/icon, a tiny label, and the value; colors (`.success`, `.danger`, `.warn`) communicate state. This variant replaces the bulky hero blocks from older demos. **Slim variant:** When the page only needs 23 metrics, use `.sh-page-header-lite` + `.sh-header-chip` (see `luci-app-vhost-manager` and `luci-app-secubox` settings). Chips carry an emoji/icon, a tiny label, and the value; colors (`.success`, `.danger`, `.warn`) communicate state. This variant replaces the bulky hero blocks from older demos.
**Version chip:** Always expose the package version from the RPC backend (read from `/usr/lib/opkg/info/<pkg>.control`) and display it as the first chip (`icon: 🏷️`). That keeps the UI and `PKG_VERSION` in sync without hunting for hard-coded strings.
**HTML Structure:** **HTML Structure:**
```javascript ```javascript
E('div', { 'class': 'sh-page-header' }, [ E('div', { 'class': 'sh-page-header' }, [
@ -1834,6 +1837,30 @@ var(--sh-hover-bg) /* Hover background */
--- ---
## AI Assistant Context Files
SecuBox work is shared between Claude and Codex assistants. Keep the context folders synchronized so any agent can resume work quickly:
| Directory | File | Usage |
|-----------|------|-------|
| `.claude/` | `HISTORY.md` | Chronological log of UI/theme changes and major deployments |
| `.claude/` | `TODO.md` | High-level backlog (UX polish, docs, automation ideas) |
| `.claude/` | `WIP.md` | Active tasks, risks, and immediate next steps |
| `.codex/` | `HISTORY.md` | Mirrors the development timeline for Codex sessions |
| `.codex/` | `TODO.md` | Tooling-focused tasks (linting, scripts, build automation) |
| `.codex/` | `WIP.md` | Status tracker for ongoing Codex efforts |
**Maintenance rules**
1. **Update after each session:** When finishing work, append a short bullet to HISTORY and adjust WIP/TODO so they reflect the new state.
2. **Reference deployment scripts:** Note which `secubox-tools/*.sh` script was used (dashboard vs. full deploy) so the next assistant knows how to reproduce it.
3. **Keep entries concise:** A single paragraph or bullet per update is enough; detailed specs remain in DOCS.
4. **Cross-check before big changes:** Read both folders before starting work to avoid conflicts or duplicate efforts.
Treat these files as living handoff notes—if they drift, onboarding a new AI/teammate becomes significantly slower.
---
## Conclusion ## Conclusion
Ce guide doit être consulté **AVANT** de: Ce guide doit être consulté **AVANT** de:

View File

@ -82,6 +82,7 @@ return view.extend({
renderHeader: function(status) { renderHeader: function(status) {
var stats = [ var stats = [
{ icon: '🏷️', label: _('Version'), value: status.version || _('Unknown') },
{ icon: '🟢', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' }, { icon: '🟢', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' },
{ icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) }, { icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) },
{ icon: '📁', label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() } { icon: '📁', label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() }

View File

@ -6,6 +6,17 @@
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/info/luci-app-cdn-cache.control"
if [ -f "$ctrl" ]; then
awk -F': ' '/^Version/ { print $2; exit }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
CACHE_DIR=$(uci -q get cdn-cache.main.cache_dir || echo "/var/cache/cdn") CACHE_DIR=$(uci -q get cdn-cache.main.cache_dir || echo "/var/cache/cdn")
STATS_FILE="/var/run/cdn-cache-stats.json" STATS_FILE="/var/run/cdn-cache-stats.json"
LOG_FILE="/var/log/cdn-cache.log" LOG_FILE="/var/log/cdn-cache.log"
@ -50,6 +61,7 @@ get_status() {
local max_size=$(uci -q get cdn-cache.main.cache_size || echo "1024") local max_size=$(uci -q get cdn-cache.main.cache_size || echo "1024")
json_init json_init
json_add_string "version" "$PKG_VERSION"
json_add_boolean "enabled" "$enabled" json_add_boolean "enabled" "$enabled"
json_add_boolean "running" "$running" json_add_boolean "running" "$running"
json_add_string "pid" "$pid" json_add_string "pid" "$pid"

View File

@ -364,9 +364,10 @@ return view.extend({
return view; return view;
}, },
renderHeader: function(status, currentModeInfo) { renderHeader: function(status, currentModeInfo) {
var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router'); var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router');
var stats = [ var stats = [
{ label: _('Version'), value: status.version || _('Unknown'), icon: '🏷️' },
{ label: _('Mode'), value: modeName, icon: '🧭' }, { label: _('Mode'), value: modeName, icon: '🧭' },
{ label: _('WAN IP'), value: status.wan_ip || _('Unknown'), icon: '🌍' }, { label: _('WAN IP'), value: status.wan_ip || _('Unknown'), icon: '🌍' },
{ label: _('LAN IP'), value: status.lan_ip || _('Unknown'), icon: '🏠' } { label: _('LAN IP'), value: status.lan_ip || _('Unknown'), icon: '🏠' }

View File

@ -6,6 +6,17 @@
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/info/luci-app-network-modes.control"
if [ -f "$ctrl" ]; then
awk -F': ' '/^Version/ { print $2; exit }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
CONFIG_FILE="/etc/config/network-modes" CONFIG_FILE="/etc/config/network-modes"
BACKUP_DIR="/etc/network-modes-backup" BACKUP_DIR="/etc/network-modes-backup"
PCAP_DIR="/var/log/pcap" PCAP_DIR="/var/log/pcap"
@ -18,6 +29,7 @@ get_status() {
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router") local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
local last_change=$(uci -q get network-modes.config.last_change || echo "Never") local last_change=$(uci -q get network-modes.config.last_change || echo "Never")
json_add_string "version" "$PKG_VERSION"
json_add_string "current_mode" "$current_mode" json_add_string "current_mode" "$current_mode"
json_add_string "last_change" "$last_change" json_add_string "last_change" "$last_change"

View File

@ -294,6 +294,47 @@ pre {
border-radius: 2px; border-radius: 2px;
} }
.secubox-nav-tabs {
box-shadow: 0 16px 30px rgba(15, 23, 42, 0.15);
}
[data-theme="dark"] .secubox-nav-tabs {
background: rgba(11, 15, 28, 0.92);
border-color: rgba(148, 163, 184, 0.2);
box-shadow: 0 18px 32px rgba(2, 6, 23, 0.65);
}
[data-theme="dark"] .secubox-nav-tabs .sh-nav-tab {
color: rgba(226, 232, 240, 0.85);
}
[data-theme="dark"] .secubox-nav-tabs .sh-nav-tab:hover {
background: rgba(99, 102, 241, 0.15);
}
[data-theme="dark"] .secubox-nav-tabs .sh-nav-tab.active {
background: rgba(255, 255, 255, 0.12);
color: #ffffff;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}
[data-theme="cyberpunk"] .secubox-nav-tabs {
background: rgba(9, 12, 32, 0.95);
border-color: rgba(58, 97, 220, 0.45);
box-shadow: 0 24px 45px rgba(6, 14, 46, 0.8);
}
[data-theme="cyberpunk"] .secubox-nav-tabs .sh-nav-tab {
color: rgba(222, 237, 255, 0.9);
}
[data-theme="cyberpunk"] .secubox-nav-tabs .sh-nav-tab.active {
background: rgba(102, 126, 234, 0.25);
color: #7dd3fc;
border: 1px solid rgba(125, 211, 252, 0.8);
box-shadow: 0 18px 40px rgba(45, 212, 191, 0.4);
}
/* === Filter Tabs === */ /* === Filter Tabs === */
.sh-filter-tabs { .sh-filter-tabs {
display: flex; display: flex;
@ -443,14 +484,15 @@ pre {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: nowrap;
overflow-x: auto;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05);
} }
.sh-header-meta { .sh-header-meta {
display: flex; display: flex;
gap: 10px; gap: 10px;
flex-wrap: wrap; flex-wrap: nowrap;
align-items: center; align-items: center;
} }
@ -507,3 +549,12 @@ pre {
color: #b45309; color: #b45309;
} }
@media (max-width: 900px) {
.sh-page-header-lite {
flex-wrap: wrap;
}
.sh-header-meta {
flex-wrap: wrap;
}
}

View File

@ -4,7 +4,7 @@
var tabs = [ var tabs = [
{ id: 'dashboard', icon: '🚀', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, { id: 'dashboard', icon: '🚀', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] },
{ id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] }, { id: 'modules', icon: '🧩', label: _('Modules'), path: ['admin', 'secubox', 'modules'] },
{ id: 'monitoring', icon: '📡', label: _('Monitoring'), path: ['admin', 'secubox', 'monitoring', 'overview'] }, { id: 'monitoring', icon: '📡', label: _('Monitoring'), path: ['admin', 'secubox', 'monitoring'] },
{ id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'alerts'] }, { id: 'alerts', icon: '⚠️', label: _('Alerts'), path: ['admin', 'secubox', 'alerts'] },
{ id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] } { id: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] }
]; ];

View File

@ -44,8 +44,10 @@ return view.extend({
var self = this; var self = this;
var container = E('div', { 'class': 'secubox-alerts-page' }, [ var container = E('div', { 'class': 'secubox-alerts-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
SecuNav.renderTabs('alerts'), SecuNav.renderTabs('alerts'),
this.renderHeader(), this.renderHeader(),
this.renderHeaderActions(),
this.renderControls(), this.renderControls(),
this.renderStats(), this.renderStats(),
this.renderAlertsList() this.renderAlertsList()
@ -62,33 +64,47 @@ return view.extend({
}, },
renderHeader: function() { renderHeader: function() {
var self = this; var stats = this.getAlertStats();
return E('div', { 'class': 'secubox-page-header' }, [ return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [ E('div', {}, [
E('h2', {}, '⚠️ System Alerts'), E('h2', { 'class': 'sh-page-title' }, [
E('p', { 'class': 'secubox-page-subtitle' }, E('span', { 'class': 'sh-page-title-icon' }, '⚠️'),
'Monitor and manage system alerts and notifications') _('System Alerts')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Monitor and manage system alerts and notifications'))
]), ]),
E('div', { 'class': 'secubox-header-actions' }, [ E('div', { 'class': 'sh-header-meta' }, [
E('button', { this.renderHeaderChip('total', '📊', _('Total'), stats.total),
'class': 'cbi-button cbi-button-action', this.renderHeaderChip('errors', '❌', _('Errors'), stats.errors, stats.errors ? 'danger' : ''),
'click': function() { this.renderHeaderChip('warnings', '⚠️', _('Warnings'), stats.warnings, stats.warnings ? 'warn' : ''),
self.clearAllAlerts(); this.renderHeaderChip('info', '', _('Info'), stats.info),
} this.renderHeaderChip('ack', '🧹', _('Dismissed'), stats.dismissed || 0)
}, '🗑️ Clear All'),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': function() {
self.refreshData().then(function() {
self.updateAlertsList();
ui.addNotification(null, E('p', 'Alerts refreshed'), 'info');
});
}
}, '🔄 Refresh')
]) ])
]); ]);
}, },
renderHeaderActions: function() {
var self = this;
return E('div', { 'class': 'secubox-header-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
self.clearAllAlerts();
}
}, '🗑️ Clear All'),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': function() {
self.refreshData().then(function() {
self.updateAlertsList();
ui.addNotification(null, E('p', 'Alerts refreshed'), 'info');
});
}
}, '🔄 Refresh')
]);
},
renderControls: function() { renderControls: function() {
var self = this; var self = this;
@ -144,17 +160,22 @@ return view.extend({
}, },
renderStats: function() { renderStats: function() {
return E('div', { 'id': 'secubox-alerts-stats', 'class': 'secubox-alerts-stats' },
this.renderStatCards());
},
renderStatCards: function() {
var alerts = this.alertsData.alerts || []; var alerts = this.alertsData.alerts || [];
var errorCount = alerts.filter(function(a) { return a.severity === 'error'; }).length; var errorCount = alerts.filter(function(a) { return a.severity === 'error'; }).length;
var warningCount = alerts.filter(function(a) { return a.severity === 'warning'; }).length; var warningCount = alerts.filter(function(a) { return a.severity === 'warning'; }).length;
var infoCount = alerts.filter(function(a) { return a.severity === 'info'; }).length; var infoCount = alerts.filter(function(a) { return a.severity === 'info'; }).length;
return E('div', { 'class': 'secubox-alerts-stats' }, [ return [
this.renderStatCard('Total Alerts', alerts.length, '📊', '#6366f1'), this.renderStatCard('Total Alerts', alerts.length, '📊', '#6366f1'),
this.renderStatCard('Errors', errorCount, '❌', '#ef4444'), this.renderStatCard('Errors', errorCount, '❌', '#ef4444'),
this.renderStatCard('Warnings', warningCount, '⚠️', '#f59e0b'), this.renderStatCard('Warnings', warningCount, '⚠️', '#f59e0b'),
this.renderStatCard('Info', infoCount, '', '#3b82f6') this.renderStatCard('Info', infoCount, '', '#3b82f6')
]); ];
}, },
renderStatCard: function(label, value, icon, color) { renderStatCard: function(label, value, icon, color) {
@ -285,6 +306,54 @@ return view.extend({
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
}, },
getAlertStats: function() {
var alerts = this.alertsData.alerts || [];
var errorCount = alerts.filter(function(a) { return a.severity === 'error'; }).length;
var warningCount = alerts.filter(function(a) { return a.severity === 'warning'; }).length;
var infoCount = alerts.filter(function(a) { return a.severity === 'info'; }).length;
var dismissed = alerts.filter(function(a) { return a.dismissed || a.acknowledged; }).length;
return {
total: alerts.length,
errors: errorCount,
warnings: warningCount,
info: infoCount,
dismissed: dismissed
};
},
renderHeaderChip: function(id, icon, label, value, tone) {
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', { 'id': 'secubox-alerts-chip-' + id }, value.toString())
])
]);
},
updateHeaderStats: function() {
var stats = this.getAlertStats();
this.setHeaderChipValue('total', stats.total);
this.setHeaderChipValue('errors', stats.errors, stats.errors ? 'danger' : '');
this.setHeaderChipValue('warnings', stats.warnings, stats.warnings ? 'warn' : '');
this.setHeaderChipValue('info', stats.info);
this.setHeaderChipValue('ack', stats.dismissed);
},
setHeaderChipValue: function(id, value, tone) {
var target = document.getElementById('secubox-alerts-chip-' + id);
if (target)
target.textContent = value.toString();
var chip = target && target.closest('.sh-header-chip');
if (chip) {
chip.classList.remove('success', 'warn', 'danger');
if (tone)
chip.classList.add(tone);
}
},
updateAlertsList: function() { updateAlertsList: function() {
var container = document.getElementById('alerts-container'); var container = document.getElementById('alerts-container');
if (container) { if (container) {
@ -296,6 +365,7 @@ return view.extend({
// Update stats // Update stats
this.updateStats(); this.updateStats();
this.updateHeaderStats();
}, },
updateModuleFilter: function() { updateModuleFilter: function() {
@ -323,7 +393,9 @@ return view.extend({
}, },
updateStats: function() { updateStats: function() {
// Stats are re-rendered with the full list, so no need to update var statsContainer = document.getElementById('secubox-alerts-stats');
if (statsContainer)
dom.content(statsContainer, this.renderStatCards());
}, },
clearAllAlerts: function() { clearAllAlerts: function() {

View File

@ -4,7 +4,7 @@
'require dom'; 'require dom';
'require poll'; 'require poll';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox-theme/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/nav as SecuNav'; 'require secubox/nav as SecuNav';
// Load theme resources once // Load theme resources once
@ -72,15 +72,16 @@ return view.extend({
var moduleStats = this.getModuleStats(); var moduleStats = this.getModuleStats();
var alertsCount = (this.alertsData.alerts || []).length; var alertsCount = (this.alertsData.alerts || []).length;
var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0; var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0;
var version = status.version || _('Unknown');
var stats = [ var stats = [
{ label: _('Modules'), value: counts.total || moduleStats.total || 0 }, { icon: '🏷️', label: _('Version'), value: version },
{ label: _('Running'), value: counts.running || moduleStats.running || 0 }, { icon: '📦', label: _('Modules'), value: counts.total || moduleStats.total || 0 },
{ label: _('Alerts'), value: alertsCount }, { icon: '🟢', label: _('Running'), value: counts.running || moduleStats.running || 0, tone: (counts.running || moduleStats.running) > 0 ? 'success' : '' },
{ label: _('Health'), value: healthScore + '/100' } { icon: '⚠️', label: _('Alerts'), value: alertsCount, tone: alertsCount ? 'warn' : '' },
{ icon: '❤️', label: _('Health'), value: healthScore + '/100' }
]; ];
return E('div', { 'class': 'sh-page-header' }, [ return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [ E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [ E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🚀'), E('span', { 'class': 'sh-page-title-icon' }, '🚀'),
@ -89,14 +90,17 @@ return view.extend({
E('p', { 'class': 'sh-page-subtitle' }, E('p', { 'class': 'sh-page-subtitle' },
_('Security · Network · System automation')) _('Security · Network · System automation'))
]), ]),
E('div', { 'class': 'sh-stats-grid' }, stats.map(this.renderHeaderStat, this)) E('div', { 'class': 'sh-header-meta' }, stats.map(this.renderHeaderChip, this))
]); ]);
}, },
renderHeaderStat: function(stat) { renderHeaderChip: function(stat) {
return E('div', { 'class': 'sh-stat-badge' }, [ return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [
E('div', { 'class': 'sh-stat-value' }, stat.value.toString()), E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'),
E('div', { 'class': 'sh-stat-label' }, stat.label) E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, stat.label),
E('strong', {}, stat.value.toString())
])
]); ]);
}, },

View File

@ -3,12 +3,15 @@
'require ui'; 'require ui';
'require poll'; 'require poll';
'require rpc'; 'require rpc';
'require secubox/api as API';
'require secubox/nav as SecuNav';
/** /**
* SecuBox Development Status View (LuCI) * SecuBox Development Status View (LuCI)
* Real-time development progress tracker for LuCI interface * Real-time development progress tracker for LuCI interface
*/ */
return view.extend({ return view.extend({
statusData: {},
// Development milestones data // Development milestones data
milestones: { milestones: {
'modules-core': { 'modules-core': {
@ -267,7 +270,11 @@ return view.extend({
* Load view * Load view
*/ */
load: function() { load: function() {
return Promise.resolve(); var self = this;
return API.getStatus().then(function(status) {
self.statusData = status || {};
return status;
});
}, },
/** /**
@ -290,7 +297,7 @@ return view.extend({
); );
} }
var view = E([], [ var main = E('div', { 'class': 'secubox-dev-body' }, [
E('h2', { 'class': 'section-title' }, _('Development Status')), E('h2', { 'class': 'section-title' }, _('Development Status')),
E('div', { 'class': 'cbi-map-descr' }, E('div', { 'class': 'cbi-map-descr' },
_('Real-time project progress tracker showing SecuBox development milestones and achievements.')), _('Real-time project progress tracker showing SecuBox development milestones and achievements.')),
@ -528,7 +535,45 @@ return view.extend({
`) `)
]); ]);
return view; return E('div', { 'class': 'secubox-dev-status-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
SecuNav.renderTabs('dev-status'),
this.renderHeader(),
main
]);
},
renderHeader: function() {
var widget = this.getWidget();
var currentPhase = widget.getCurrentPhase() || {};
var status = this.statusData || {};
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🚀'),
_('Development Status')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('SecuBox roadmap, milestones, and release planning.'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderHeaderChip('🏷️', _('Version'), status.version || _('Unknown')),
this.renderHeaderChip('📈', _('Overall'), this.getOverallProgress() + '%'),
this.renderHeaderChip('📅', _('Current phase'),
currentPhase.phase ? currentPhase.phase + ' · ' + currentPhase.name : _('Not set'))
])
]);
},
renderHeaderChip: function(icon, label, value) {
return E('div', { 'class': 'sh-header-chip' }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value.toString())
])
]);
}, },
handleSaveApply: null, handleSaveApply: null,

View File

@ -4,6 +4,7 @@
'require secubox/api as API'; 'require secubox/api as API';
'require secubox/help as Help'; 'require secubox/help as Help';
'require secubox/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/nav as SecuNav';
// Ensure SecuBox theme variables are loaded for this view // Ensure SecuBox theme variables are loaded for this view
Theme.init(); Theme.init();
@ -30,6 +31,9 @@ return view.extend({
var helpPages = Help.getAllHelpPages(); var helpPages = Help.getAllHelpPages();
return E('div', { 'class': 'secubox-help-page' }, [ return E('div', { 'class': 'secubox-help-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
SecuNav.renderTabs('help'),
this.renderHeader(data),
this.renderHero(data), this.renderHero(data),
this.renderHelpCatalog(helpPages), this.renderHelpCatalog(helpPages),
this.renderSupportSection(), this.renderSupportSection(),
@ -198,6 +202,20 @@ return view.extend({
return c.toUpperCase(); return c.toUpperCase();
}); });
return titles[key] || { title: fallbackTitle, icon: '📦' }; return titles[key] || {
title: fallbackTitle,
icon: '📄',
description: _('Documentation officielle')
};
},
renderHeaderChip: function(icon, label, value) {
return E('div', { 'class': 'sh-header-chip' }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value.toString())
])
]);
} }
}); });

View File

@ -2,8 +2,9 @@
'require view'; 'require view';
'require rpc'; 'require rpc';
'require secubox/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/api as API';
'require secubox/nav as SecuNav';
// Initialize theme
Theme.init(); Theme.init();
var callModules = rpc.declare({ var callModules = rpc.declare({
@ -13,54 +14,90 @@ var callModules = rpc.declare({
}); });
return view.extend({ return view.extend({
load: function() { statusData: {},
console.log('=== MODULES DEBUG: load() called ===');
return callModules().then(function(result) {
console.log('=== MODULES DEBUG: RPC result ===');
console.log('Type:', typeof result);
console.log('Is Array:', Array.isArray(result));
console.log('Length:', result ? result.length : 'null/undefined');
console.log('Full result:', JSON.stringify(result, null, 2));
return result;
}).catch(function(err) {
console.error('=== MODULES DEBUG: RPC ERROR ===');
console.error(err);
return [];
});
},
render: function(data) {
console.log('=== MODULES DEBUG: render() called ===');
console.log('Data type:', typeof data);
console.log('Is Array:', Array.isArray(data));
console.log('Data:', data);
var modules = data || []; load: function() {
console.log('Modules array length:', modules.length); return Promise.all([
API.getStatus(),
callModules()
]).then(L.bind(function(res) {
this.statusData = res[0] || {};
return res[1] || [];
}, this)).catch(function(err) {
console.error('=== MODULES DEBUG: RPC ERROR ===', err);
return [];
});
},
if (modules.length === 0) { render: function(modules) {
return E('div', {class:'cbi-map'}, [ modules = modules || [];
E('h2', {}, '📦 SecuBox Modules'), var running = modules.filter(function(m) { return m.running; }).length;
E('div', {style:'color:red;padding:20px;background:#fee;border:1px solid red;border-radius:8px'}, [ var installed = modules.filter(function(m) { return m.installed; }).length;
E('p', {}, 'DEBUG: No modules found!'),
E('p', {}, 'Data type: ' + typeof data),
E('p', {}, 'Is Array: ' + Array.isArray(data)),
E('p', {}, 'Length: ' + (data ? (data.length || 'no length property') : 'null/undefined')),
E('p', {}, 'JSON: ' + JSON.stringify(data))
])
]);
}
return E('div', {class:'cbi-map'}, [ return E('div', { 'class': 'secubox-modules-debug' }, [
E('h2', {}, '📦 SecuBox Modules (' + modules.length + ' found)'), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('div', {style:'display:grid;gap:12px'}, modules.map(function(m) { E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
console.log('Rendering module:', m.name); SecuNav.renderTabs('modules'),
return E('div', {style:'background:#1e293b;padding:16px;border-radius:8px;border-left:4px solid '+m.color}, [ this.renderHeader(modules.length, running, installed),
E('div', {style:'font-weight:bold;color:#f1f5f9'}, m.name), modules.length ? this.renderModuleGrid(modules) : this.renderEmptyState()
E('div', {style:'color:#94a3b8;font-size:14px'}, m.description), ]);
E('span', {style:'display:inline-block;margin-top:8px;padding:2px 8px;border-radius:4px;font-size:12px;background:'+(m.running?'#22c55e20;color:#22c55e':m.installed?'#f59e0b20;color:#f59e0b':'#64748b20;color:#64748b')}, },
m.running ? 'Running' : m.installed ? 'Stopped' : 'Not Installed')
]); renderHeader: function(total, running, installed) {
})) var status = this.statusData || {};
]);
} return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🧪'),
_('Modules Debug Console')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Inspect raw module data returned by the SecuBox RPC backend.'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderHeaderChip('🏷️', _('Version'), status.version || _('Unknown')),
this.renderHeaderChip('📦', _('Total'), total),
this.renderHeaderChip('🟢', _('Running'), running, running ? 'success' : ''),
this.renderHeaderChip('💾', _('Installed'), installed)
])
]);
},
renderModuleGrid: function(modules) {
return E('div', { 'class': 'cbi-map' }, [
E('div', { 'class': 'secubox-debug-grid' }, modules.map(function(m) {
return E('div', { 'class': 'secubox-debug-card' }, [
E('div', { 'class': 'secubox-debug-card-title' }, m.name || _('Unnamed module')),
E('div', { 'class': 'secubox-debug-card-desc' }, m.description || _('No description provided.')),
E('div', { 'class': 'secubox-debug-card-meta' }, [
E('span', { 'class': 'secubox-debug-pill ' + (m.running ? 'running' : m.installed ? 'installed' : 'missing') },
m.running ? _('Running') : m.installed ? _('Installed') : _('Not Installed')),
m.category ? E('span', { 'class': 'secubox-debug-pill neutral' }, m.category) : ''
]),
E('pre', { 'class': 'secubox-debug-json' }, JSON.stringify(m, null, 2))
]);
}))
]);
},
renderEmptyState: function() {
return E('div', { 'class': 'cbi-map' }, [
E('div', { 'class': 'secubox-empty-state' }, [
E('div', { 'class': 'secubox-empty-icon' }, '📭'),
E('div', { 'class': 'secubox-empty-title' }, _('No modules found')),
E('p', { 'class': 'secubox-empty-text' }, _('RPC returned an empty list. Verify luci.secubox modules API.'))
])
]);
},
renderHeaderChip: function(icon, label, value, tone) {
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value.toString())
])
]);
}
}); });

View File

@ -1,22 +1,60 @@
'use strict'; 'use strict';
'require view'; 'require view';
'require secubox/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/api as API';
'require secubox/nav as SecuNav';
// Initialize theme
Theme.init(); Theme.init();
return view.extend({ return view.extend({
load: function() { statusData: {},
return Promise.resolve(null);
}, load: function() {
render: function() { return API.getStatus().then(L.bind(function(status) {
return E('div', {'class': 'cbi-map'}, [ this.statusData = status || {};
E('h2', {}, 'Test: SecuBox Modules'), return status;
E('div', {'style': 'padding:20px;background:#d4edda;border:2px solid green'}, [ }, this));
E('p', {}, '✅ If you see this, the JavaScript file is loading correctly!'), },
E('p', {}, 'File: /www/luci-static/resources/view/secubox/modules.js'),
E('p', {}, 'This is a minimal test without RPC calls.') render: function() {
]) var status = this.statusData || {};
]);
} return E('div', { 'class': 'secubox-modules-minimal' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
SecuNav.renderTabs('modules'),
E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '🧪'),
_('Minimal Modules Test')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Simple view confirming that the modules UI assets load correctly.'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderHeaderChip('🏷️', _('Version'), status.version || _('Unknown')),
this.renderHeaderChip('🧩', _('Modules'), status.modules_count || '—')
])
]),
E('div', { 'class': 'cbi-map' }, [
E('div', {
'class': 'secubox-success-box'
}, [
E('h3', {}, _('✅ JavaScript Loaded')),
E('p', {}, _('modules.js assets are accessible and the SecuBox theme is active.'))
])
])
]);
},
renderHeaderChip: function(icon, label, value) {
return E('div', { 'class': 'sh-header-chip' }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value.toString())
])
]);
}
}); });

View File

@ -3,7 +3,7 @@
'require ui'; 'require ui';
'require dom'; 'require dom';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox-theme/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/nav as SecuNav'; 'require secubox/nav as SecuNav';
'require poll'; 'require poll';
@ -46,6 +46,7 @@ return view.extend({
var container = E('div', { 'class': 'secubox-modules-page' }, [ var container = E('div', { 'class': 'secubox-modules-page' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
SecuNav.renderTabs('modules'), SecuNav.renderTabs('modules'),
this.renderHeader(modules), this.renderHeader(modules),
this.renderFilterTabs(), this.renderFilterTabs(),
@ -66,34 +67,23 @@ return view.extend({
}, },
renderHeader: function(modules) { renderHeader: function(modules) {
var total = modules.length; var stats = this.getModuleStats(modules);
var installed = modules.filter(function(m) { return m.installed; }).length;
var enabled = modules.filter(function(m) { return m.enabled; }).length;
var disabled = installed - enabled;
return E('div', { 'class': 'secubox-page-header' }, [ return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [ E('div', {}, [
E('h2', {}, '📦 SecuBox Modules'), E('h2', { 'class': 'sh-page-title' }, [
E('p', { 'class': 'secubox-page-subtitle' }, E('span', { 'class': 'sh-page-title-icon' }, '📦'),
'Manage and monitor all SecuBox modules') _('SecuBox Modules')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Manage and monitor all SecuBox modules'))
]), ]),
E('div', { 'class': 'secubox-modules-stats' }, [ E('div', { 'class': 'sh-header-meta' }, [
E('div', { 'class': 'secubox-stat-badge' }, [ this.renderHeaderChip('total', '🏷️', _('Total'), stats.total),
E('span', { 'class': 'secubox-stat-value' }, total), this.renderHeaderChip('installed', '💾', _('Installed'), stats.installed),
E('span', { 'class': 'secubox-stat-label' }, 'Total') this.renderHeaderChip('active', '🟢', _('Active'), stats.enabled, stats.enabled ? 'success' : ''),
]), this.renderHeaderChip('inactive', '⚪', _('Disabled'), stats.disabled, stats.disabled ? 'warn' : ''),
E('div', { 'class': 'secubox-stat-badge secubox-stat-success' }, [ this.renderHeaderChip('available', '📦', _('Available'), stats.available)
E('span', { 'class': 'secubox-stat-value' }, enabled),
E('span', { 'class': 'secubox-stat-label' }, 'Activés')
]),
E('div', { 'class': 'secubox-stat-badge secubox-stat-warning' }, [
E('span', { 'class': 'secubox-stat-value' }, disabled),
E('span', { 'class': 'secubox-stat-label' }, 'Désactivés')
]),
E('div', { 'class': 'secubox-stat-badge secubox-stat-muted' }, [
E('span', { 'class': 'secubox-stat-value' }, total - installed),
E('span', { 'class': 'secubox-stat-label' }, 'Available')
])
]) ])
]); ]);
}, },
@ -139,7 +129,7 @@ return view.extend({
return E('div', { 'class': 'secubox-filter-tabs cyber-tablist cyber-tablist--filters' }, filterButtons); return E('div', { 'class': 'secubox-filter-tabs cyber-tablist cyber-tablist--filters' }, filterButtons);
}, },
renderModuleCards: function(modules, filter) { renderModuleCards: function(modules, filter) {
var self = this; var self = this;
var filtered = filter === 'all' ? modules : var filtered = filter === 'all' ? modules :
@ -394,17 +384,59 @@ return view.extend({
} }
}, },
getModuleStats: function(modules) {
var list = modules || [];
var installed = list.filter(function(m) { return m.installed; }).length;
var enabled = list.filter(function(m) { return m.enabled; }).length;
var disabled = Math.max(installed - enabled, 0);
var available = Math.max(list.length - installed, 0);
return {
total: list.length,
installed: installed,
enabled: enabled,
disabled: disabled,
available: available
};
},
renderHeaderChip: function(id, icon, label, value, tone) {
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', { 'id': 'secubox-modules-chip-' + id }, value.toString())
])
]);
},
updateHeaderStats: function() {
var stats = this.getModuleStats(this.modulesData);
this.setHeaderChipValue('total', stats.total);
this.setHeaderChipValue('installed', stats.installed);
this.setHeaderChipValue('active', stats.enabled, stats.enabled ? 'success' : '');
this.setHeaderChipValue('inactive', stats.disabled, stats.disabled ? 'warn' : '');
this.setHeaderChipValue('available', stats.available);
},
setHeaderChipValue: function(id, value, tone) {
var target = document.getElementById('secubox-modules-chip-' + id);
if (target)
target.textContent = value.toString();
var chip = target && target.closest('.sh-header-chip');
if (chip) {
chip.classList.remove('success', 'warn');
if (tone)
chip.classList.add(tone);
}
},
updateModulesGrid: function() { updateModulesGrid: function() {
var activeTab = document.querySelector('.secubox-filter-tabs .cyber-tab.is-active[data-filter]'); var activeTab = document.querySelector('.secubox-filter-tabs .cyber-tab.is-active[data-filter]');
var filter = activeTab ? activeTab.getAttribute('data-filter') : 'all'; var filter = activeTab ? activeTab.getAttribute('data-filter') : 'all';
this.filterModules(filter); this.filterModules(filter);
this.updateHeaderStats();
// Update header stats
var header = document.querySelector('.secubox-modules-stats');
if (header && header.parentNode) {
var newHeader = this.renderHeader(this.modulesData);
header.parentNode.replaceChild(newHeader.querySelector('.secubox-modules-stats'), header);
}
}, },
handleSaveApply: null, handleSaveApply: null,

View File

@ -4,7 +4,7 @@
'require dom'; 'require dom';
'require poll'; 'require poll';
'require secubox/api as API'; 'require secubox/api as API';
'require secubox-theme/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/nav as SecuNav'; 'require secubox/nav as SecuNav';
// Respect LuCI language/theme preferences // Respect LuCI language/theme preferences
@ -59,6 +59,7 @@ return view.extend({
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/monitoring.css') }),
SecuNav.renderTabs('monitoring'), SecuNav.renderTabs('monitoring'),
this.renderHeader(),
this.renderHero(), this.renderHero(),
this.renderChartsGrid(), this.renderChartsGrid(),
this.renderCurrentStatsCard() this.renderCurrentStatsCard()
@ -75,9 +76,44 @@ return view.extend({
}); });
}, 5); }, 5);
this.hideLegacyTabMenu();
return container; return container;
}, },
renderHeader: function() {
var snapshot = this.getLatestSnapshot();
var rates = this.getNetworkRateSummary();
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '📡'),
_('SecuBox Monitoring')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Live telemetry for CPU, memory, storage, and network throughput'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderHeaderChip('cpu', '🔥', _('CPU'), snapshot.cpu.value.toFixed(1) + '%', snapshot.cpu.value),
this.renderHeaderChip('memory', '💾', _('Memory'), snapshot.memory.value.toFixed(1) + '%', snapshot.memory.value),
this.renderHeaderChip('disk', '💿', _('Disk'), snapshot.disk.value.toFixed(1) + '%', snapshot.disk.value),
this.renderHeaderChip('uptime', '⏱', _('Uptime'), API.formatUptime(snapshot.uptime || 0)),
this.renderHeaderChip('net', '🌐', _('Network'), rates.summary)
])
]);
},
renderHeaderChip: function(id, icon, label, value, percent) {
return E('div', { 'class': 'sh-header-chip' }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', { 'id': 'secubox-monitoring-chip-' + id, 'style': (typeof percent === 'number') ? ('color:' + this.getColorForValue(percent)) : '' }, value)
])
]);
},
renderHero: function() { renderHero: function() {
var snapshot = this.getLatestSnapshot(); var snapshot = this.getLatestSnapshot();
@ -277,7 +313,9 @@ return view.extend({
if (statsContainer) if (statsContainer)
dom.content(statsContainer, this.renderStatsTable()); dom.content(statsContainer, this.renderStatsTable());
this.updateHeroBadges(this.getLatestSnapshot()); var snapshot = this.getLatestSnapshot();
this.updateHeroBadges(snapshot);
this.updateHeaderChips(snapshot);
}, },
updateHeroBadges: function(snapshot) { updateHeroBadges: function(snapshot) {
@ -304,6 +342,36 @@ return view.extend({
}, this); }, this);
}, },
updateHeaderChips: function(snapshot) {
this.setHeaderChipValue('cpu', snapshot.cpu.value.toFixed(1) + '%', snapshot.cpu.value);
this.setHeaderChipValue('memory', snapshot.memory.value.toFixed(1) + '%', snapshot.memory.value);
this.setHeaderChipValue('disk', snapshot.disk.value.toFixed(1) + '%', snapshot.disk.value);
this.setHeaderChipValue('uptime', API.formatUptime(snapshot.uptime || 0));
var rates = this.getNetworkRateSummary();
this.setHeaderChipValue('net', rates.summary);
},
setHeaderChipValue: function(id, text, percent) {
var target = document.getElementById('secubox-monitoring-chip-' + id);
if (!target)
return;
target.textContent = text;
if (typeof percent === 'number')
target.style.color = this.getColorForValue(percent);
else
target.style.color = '';
},
hideLegacyTabMenu: function() {
window.requestAnimationFrame(function() {
var menus = document.querySelectorAll('.main > .tabmenu, .main > .cbi-tabmenu');
menus.forEach(function(menu) {
menu.style.display = 'none';
});
});
},
getLatestSnapshot: function() { getLatestSnapshot: function() {
return { return {
cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 },

View File

@ -7,6 +7,11 @@
'require secubox/theme as Theme'; 'require secubox/theme as Theme';
'require secubox/nav as SecuNav'; 'require secubox/nav as SecuNav';
var secuLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: secuLang });
return view.extend({ return view.extend({
load: function() { load: function() {
return Promise.all([ return Promise.all([
@ -45,17 +50,6 @@ return view.extend({
]) ])
]); ]);
renderHeaderChip: function(icon, label, value, tone) {
var display = (value == null ? '—' : value).toString();
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, display)
])
]);
},
// Create form // Create form
m = new form.Map('secubox', null, null); m = new form.Map('secubox', null, null);
@ -248,5 +242,16 @@ return view.extend({
container.appendChild(formElement); container.appendChild(formElement);
return container; return container;
}, this)); }, this));
},
renderHeaderChip: function(icon, label, value, tone) {
var display = (value == null ? '—' : value).toString();
return E('div', { 'class': 'sh-header-chip' + (tone ? ' ' + tone : '') }, [
E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, display)
])
]);
} }
}); });

View File

@ -7,6 +7,17 @@
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/info/luci-app-secubox.control"
if [ -f "$ctrl" ]; then
awk -F': ' '/^Version/ { print $2; exit }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
# Module registry - auto-detected from /usr/libexec/rpcd/ # Module registry - auto-detected from /usr/libexec/rpcd/
detect_modules() { detect_modules() {
local modules="" local modules=""
@ -125,7 +136,7 @@ get_status() {
local running=0 local running=0
json_init json_init
json_add_string "version" "0.1.1" json_add_string "version" "$PKG_VERSION"
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
@ -841,7 +852,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.1" json_add_string "version" "$PKG_VERSION"
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"

View File

@ -32,13 +32,8 @@
"admin/secubox/monitoring": { "admin/secubox/monitoring": {
"title": "Monitoring & Analytics", "title": "Monitoring & Analytics",
"order": 35, "order": 35,
"action": {"type": "firstchild"}
},
"admin/secubox/monitoring/overview": {
"title": "System Monitoring",
"order": 10,
"action": {"type": "view", "path": "secubox/monitoring"} "action": {"type": "view", "path": "secubox/monitoring"}
}, },
"admin/secubox/network": { "admin/secubox/network": {
"title": "Network & Connectivity", "title": "Network & Connectivity",
"order": 40, "order": 40,

View File

@ -226,8 +226,8 @@ Returns backup data in base64 format with size and filename.
# Encode backup file to base64 # Encode backup file to base64
DATA=$(base64 < backup.tar.gz | tr -d '\n') DATA=$(base64 < backup.tar.gz | tr -d '\n')
# Restore # Restore (include original file name for logging/validation)
ubus call luci.system-hub restore_config "{\"data\":\"$DATA\"}" ubus call luci.system-hub restore_config "{\"file_name\":\"backup.tar.gz\",\"data\":\"$DATA\"}"
``` ```
#### Reboot System #### Reboot System
@ -409,11 +409,12 @@ Create system configuration backup.
} }
``` ```
### restore_config(data) ### restore_config(file_name, data)
Restore system configuration from backup. Restore system configuration from backup.
**Parameters:** **Parameters:**
- `file_name`: Original archive name (used for logging/validation, optional but recommended)
- `data`: Base64-encoded backup data - `data`: Base64-encoded backup data
**Returns:** **Returns:**

View File

@ -59,7 +59,7 @@ var callBackupConfig = rpc.declare({
var callRestoreConfig = rpc.declare({ var callRestoreConfig = rpc.declare({
object: 'luci.system-hub', object: 'luci.system-hub',
method: 'restore_config', method: 'restore_config',
params: ['data'], params: ['file_name', 'data'],
expect: {} expect: {}
}); });
@ -192,7 +192,15 @@ return baseclass.extend({
serviceAction: callServiceAction, serviceAction: callServiceAction,
getLogs: callGetLogs, getLogs: callGetLogs,
backupConfig: callBackupConfig, backupConfig: callBackupConfig,
restoreConfig: callRestoreConfig, restoreConfig: function(fileName, data) {
if (typeof fileName === 'object')
return callRestoreConfig(fileName);
return callRestoreConfig({
file_name: fileName,
data: data
});
},
reboot: callReboot, reboot: callReboot,
getStorage: callGetStorage, getStorage: callGetStorage,
getSettings: callGetSettings, getSettings: callGetSettings,

View File

@ -8,15 +8,21 @@
Theme.init(); Theme.init();
return view.extend({ return view.extend({
statusData: {},
load: function() { load: function() {
return Promise.resolve(); return API.getSystemInfo().then(L.bind(function(info) {
this.statusData = info || {};
return info;
}, this));
}, },
render: function() { render: function() {
var container = E('div', { 'class': 'system-hub-dashboard sh-backup-view' }, [ return E('div', { 'class': 'system-hub-dashboard sh-backup-view' }, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/dashboard.css') }),
E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/backup.css') }), E('link', { 'rel': 'stylesheet', 'href': L.resource('system-hub/backup.css') }),
HubNav.renderTabs('backup'), HubNav.renderTabs('backup'),
this.renderHeader(),
this.renderHero(), this.renderHero(),
E('div', { 'class': 'sh-backup-grid' }, [ E('div', { 'class': 'sh-backup-grid' }, [
this.renderBackupCard(), this.renderBackupCard(),
@ -24,8 +30,25 @@ return view.extend({
this.renderMaintenanceCard() this.renderMaintenanceCard()
]) ])
]); ]);
},
return container; renderHeader: function() {
var info = this.statusData || {};
return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [
E('div', {}, [
E('h2', { 'class': 'sh-page-title' }, [
E('span', { 'class': 'sh-page-title-icon' }, '💾'),
_('Backup Control Center')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('Create encrypted snapshots and restore complete configurations safely.'))
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderChip('🏷️', _('Version'), info.version || _('Unknown')),
this.renderChip('🕒', _('Uptime'), info.uptime_formatted || _('0d 0h 0m')),
this.renderChip('🗂️', _('Configs'), _('etc + packages'))
])
]);
}, },
renderHero: function() { renderHero: function() {
@ -161,28 +184,17 @@ return view.extend({
return; return;
} }
if (!confirm(_('Restore configuration from backup? This will overwrite all settings.'))) {
return;
}
ui.showModal(_('Restoring backup…'), [
E('p', { 'class': 'spinning' }, _('Uploading archive and applying configuration...'))
]);
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(ev) { reader.onload = function() {
var arrayBuffer = ev.target.result; var base64Data = reader.result.split(',')[1];
var bytes = new Uint8Array(arrayBuffer); ui.showModal(_('Restoring backup…'), [
var binary = ''; E('p', { 'class': 'spinning' }, _('Uploading archive and applying configuration...'))
for (var i = 0; i < bytes.length; i++) { ]);
binary += String.fromCharCode(bytes[i]);
}
var encoded = btoa(binary); API.restoreConfig(file.name, base64Data).then(function(result) {
API.restoreConfig(encoded).then(function(result) {
ui.hideModal(); ui.hideModal();
if (result && result.success) { if (result && result.success) {
ui.addNotification(null, E('p', {}, _('Backup restored. Please reboot to apply changes.')), 'info'); ui.addNotification(null, E('p', {}, _('Backup restored successfully. System reboot recommended.')), 'info');
} else { } else {
ui.addNotification(null, E('p', {}, (result && result.message) || _('Restore failed')), 'error'); ui.addNotification(null, E('p', {}, (result && result.message) || _('Restore failed')), 'error');
} }
@ -191,31 +203,34 @@ return view.extend({
ui.addNotification(null, E('p', {}, err.message || err), 'error'); ui.addNotification(null, E('p', {}, err.message || err), 'error');
}); });
}; };
reader.readAsDataURL(file);
reader.onerror = function() {
ui.hideModal();
ui.addNotification(null, E('p', {}, _('Could not read backup file')), 'error');
};
reader.readAsArrayBuffer(file);
}, },
rebootSystem: function() { rebootSystem: function() {
if (!confirm(_('Reboot the device now? All connections will be interrupted.'))) { ui.showModal(_('Reboot system?'), [
return; E('p', {}, _('Rebooting is recommended after restoring configurations. Continue?')),
} E('div', { 'class': 'right' }, [
E('button', { 'class': 'sh-btn sh-btn-secondary', 'click': ui.hideModal }, _('Cancel')),
ui.showModal(_('System rebooting'), [ E('button', {
E('p', {}, _('Device is restarting. The interface will be unreachable for ~60 seconds.')) 'class': 'sh-btn sh-btn-danger',
'click': function() {
ui.hideModal();
API.reboot().then(function() {
ui.addNotification(null, E('p', {}, _('System reboot initiated')), 'info');
});
}
}, _('Reboot'))
])
]); ]);
API.reboot().catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
}, },
handleSaveApply: null, renderChip: function(icon, label, value) {
handleSave: null, return E('div', { 'class': 'sh-header-chip' }, [
handleReset: null E('span', { 'class': 'sh-chip-icon' }, icon),
E('div', { 'class': 'sh-chip-text' }, [
E('span', { 'class': 'sh-chip-label' }, label),
E('strong', {}, value.toString())
])
]);
}
}); });

View File

@ -59,8 +59,10 @@ return view.extend({
var hostname = this.sysInfo.hostname || 'OpenWrt'; var hostname = this.sysInfo.hostname || 'OpenWrt';
var kernel = this.sysInfo.kernel || '-'; var kernel = this.sysInfo.kernel || '-';
var score = (this.healthData.score || 0); var score = (this.healthData.score || 0);
var version = this.sysInfo.version || _('Unknown');
var stats = [ var stats = [
{ label: _('Version'), value: version, icon: '🏷️' },
{ label: _('Uptime'), value: uptime, icon: '⏱' }, { label: _('Uptime'), value: uptime, icon: '⏱' },
{ label: _('Hostname'), value: hostname, icon: '🖥' }, { label: _('Hostname'), value: hostname, icon: '🖥' },
{ label: _('Kernel'), value: kernel, copy: kernel, icon: '🧬' }, { label: _('Kernel'), value: kernel, copy: kernel, icon: '🧬' },

View File

@ -6,6 +6,19 @@
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/status"
local pkg="luci-app-system-hub"
if [ -f "$ctrl" ]; then
awk -F': ' '/Package: '"$pkg"'/,/^Package/ { if ($1 == "Version") { print $2; exit } }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
DIAG_DIR="/tmp/system-hub/diagnostics" DIAG_DIR="/tmp/system-hub/diagnostics"
mkdir -p "$DIAG_DIR" mkdir -p "$DIAG_DIR"
@ -25,6 +38,7 @@ status() {
local uptime=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0) local uptime=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0)
local model=$(cat /tmp/sysinfo/model 2>/dev/null || cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unknown") local model=$(cat /tmp/sysinfo/model 2>/dev/null || cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unknown")
json_add_string "version" "$PKG_VERSION"
json_add_string "hostname" "$hostname" json_add_string "hostname" "$hostname"
json_add_string "model" "$model" json_add_string "model" "$model"
json_add_int "uptime" "$uptime" json_add_int "uptime" "$uptime"
@ -66,6 +80,7 @@ get_system_info() {
# Hostname # Hostname
local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "unknown") local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "unknown")
json_add_string "version" "$PKG_VERSION"
json_add_string "hostname" "$hostname" json_add_string "hostname" "$hostname"
# Model # Model
@ -494,8 +509,9 @@ restore_config() {
read -r input read -r input
json_load "$input" json_load "$input"
local backup_data local backup_data file_name
json_get_var backup_data data json_get_var backup_data data
json_get_var file_name file_name
json_cleanup json_cleanup
if [ -z "$backup_data" ]; then if [ -z "$backup_data" ]; then
@ -506,18 +522,31 @@ restore_config() {
return 1 return 1
fi fi
local backup_file="/tmp/restore-$(date +%s).tar.gz" local clean_name="$(basename "${file_name:-backup.tar.gz}")"
clean_name="$(echo "$clean_name" | tr -c 'A-Za-z0-9._-' '_')"
[ -z "$clean_name" ] && clean_name="backup.tar.gz"
local timestamp="$(date +%s)"
local backup_file="/tmp/${timestamp}-${clean_name}"
# Decode base64 # Decode base64
echo "$backup_data" | base64 -d > "$backup_file" 2>/dev/null if ! echo "$backup_data" | base64 -d > "$backup_file" 2>/dev/null; then
rm -f "$backup_file"
if [ ! -f "$backup_file" ]; then
json_init json_init
json_add_boolean "success" 0 json_add_boolean "success" 0
json_add_string "message" "Failed to decode backup data" json_add_string "message" "Failed to decode backup data"
json_dump json_dump
return 1 return 1
fi fi
if [ ! -s "$backup_file" ]; then
rm -f "$backup_file"
json_init
json_add_boolean "success" 0
json_add_string "message" "Decoded backup archive is empty"
json_dump
return 1
fi
# Restore # Restore
sysupgrade -r "$backup_file" >/dev/null 2>&1 sysupgrade -r "$backup_file" >/dev/null 2>&1
@ -711,7 +740,7 @@ download_diagnostic() {
json_add_string "error" "not_found" json_add_string "error" "not_found"
json_dump json_dump
return return
} fi
local data=$(base64 "$file" 2>/dev/null) local data=$(base64 "$file" 2>/dev/null)
json_add_boolean "success" 1 json_add_boolean "success" 1
@ -1226,7 +1255,7 @@ case "$1" in
"service_action": { "service": "string", "action": "string" }, "service_action": { "service": "string", "action": "string" },
"get_logs": { "lines": 100, "filter": "" }, "get_logs": { "lines": 100, "filter": "" },
"backup_config": {}, "backup_config": {},
"restore_config": { "data": "string" }, "restore_config": { "file_name": "string", "data": "string" },
"reboot": {}, "reboot": {},
"get_storage": {}, "get_storage": {},
"get_settings": {}, "get_settings": {},

View File

@ -5,6 +5,17 @@
. /lib/functions.sh . /lib/functions.sh
. /usr/share/libubox/jshn.sh . /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/info/luci-app-vhost-manager.control"
if [ -f "$ctrl" ]; then
awk -F': ' '/^Version/ { print $2; exit }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
NGINX_VHOST_DIR="/etc/nginx/conf.d" NGINX_VHOST_DIR="/etc/nginx/conf.d"
ACME_STATE_DIR="/etc/acme" ACME_STATE_DIR="/etc/acme"
VHOST_CONFIG="/etc/config/vhost_manager" VHOST_CONFIG="/etc/config/vhost_manager"
@ -187,7 +198,7 @@ case "$1" in
json_init json_init
json_add_boolean "enabled" 1 json_add_boolean "enabled" 1
json_add_string "module" "vhost-manager" json_add_string "module" "vhost-manager"
json_add_string "version" "0.4.1" json_add_string "version" "$PKG_VERSION"
# Check nginx status # Check nginx status
if pgrep -x nginx > /dev/null 2>&1; then if pgrep -x nginx > /dev/null 2>&1; then

5
rpc_reload.sh Normal file
View File

@ -0,0 +1,5 @@
for obj in secubox system-hub network-modes vhost-manager cdn-cache; do
scp luci-app-$obj/root/usr/libexec/rpcd/luci.$obj root@192.168.8.191:/usr/libexec/rpcd/ || exit 1
ssh root@192.168.8.191 "chmod 755 /usr/libexec/rpcd/luci.$obj" || exit 1
done
ssh root@192.168.8.191 "/etc/init.d/rpcd restart && /etc/init.d/uhttpd restart"