diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md new file mode 100644 index 0000000..1a30c53 --- /dev/null +++ b/.claude/HISTORY.md @@ -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. diff --git a/.claude/TODO.md b/.claude/TODO.md new file mode 100644 index 0000000..8fd2a66 --- /dev/null +++ b/.claude/TODO.md @@ -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=` to view URLs). diff --git a/.claude/WIP.md b/.claude/WIP.md new file mode 100644 index 0000000..1a05ec7 --- /dev/null +++ b/.claude/WIP.md @@ -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. diff --git a/.codex/HISTORY.md b/.codex/HISTORY.md new file mode 100644 index 0000000..43981c9 --- /dev/null +++ b/.codex/HISTORY.md @@ -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). diff --git a/.codex/TODO.md b/.codex/TODO.md new file mode 100644 index 0000000..defce11 --- /dev/null +++ b/.codex/TODO.md @@ -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. diff --git a/.codex/WIP.md b/.codex/WIP.md new file mode 100644 index 0000000..24126e2 --- /dev/null +++ b/.codex/WIP.md @@ -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. diff --git a/DOCS/DEVELOPMENT-GUIDELINES.md b/DOCS/DEVELOPMENT-GUIDELINES.md index c4ab4c5..6e93ef3 100644 --- a/DOCS/DEVELOPMENT-GUIDELINES.md +++ b/DOCS/DEVELOPMENT-GUIDELINES.md @@ -20,6 +20,7 @@ Ce document définit les standards, bonnes pratiques et validations obligatoires 7. [Common Errors & Solutions](#common-errors--solutions) 8. [Validation Checklist](#validation-checklist) 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 2‑3 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/.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:** ```javascript 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 Ce guide doit être consulté **AVANT** de: diff --git a/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js b/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js index 402097c..3d60228 100644 --- a/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js +++ b/luci-app-cdn-cache/htdocs/luci-static/resources/view/cdn-cache/overview.js @@ -82,6 +82,7 @@ return view.extend({ renderHeader: function(status) { var stats = [ + { icon: '🏷️', label: _('Version'), value: status.version || _('Unknown') }, { icon: '🟢', label: _('Service'), value: status.running ? _('Running') : _('Stopped'), tone: status.running ? 'success' : 'danger' }, { icon: '⏱', label: _('Uptime'), value: formatUptime(status.uptime || 0) }, { icon: '📁', label: _('Cache files'), value: (status.cache_files || 0).toLocaleString() } diff --git a/luci-app-cdn-cache/root/usr/libexec/rpcd/luci.cdn-cache b/luci-app-cdn-cache/root/usr/libexec/rpcd/luci.cdn-cache index f67111f..b2e1932 100755 --- a/luci-app-cdn-cache/root/usr/libexec/rpcd/luci.cdn-cache +++ b/luci-app-cdn-cache/root/usr/libexec/rpcd/luci.cdn-cache @@ -6,6 +6,17 @@ . /lib/functions.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") STATS_FILE="/var/run/cdn-cache-stats.json" 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") json_init + json_add_string "version" "$PKG_VERSION" json_add_boolean "enabled" "$enabled" json_add_boolean "running" "$running" json_add_string "pid" "$pid" diff --git a/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js index 8f87d69..2d41ea5 100644 --- a/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js +++ b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/overview.js @@ -364,9 +364,10 @@ return view.extend({ return view; }, - renderHeader: function(status, currentModeInfo) { + renderHeader: function(status, currentModeInfo) { var modeName = currentModeInfo ? currentModeInfo.name : (status.current_mode || 'router'); var stats = [ + { label: _('Version'), value: status.version || _('Unknown'), icon: '🏷️' }, { label: _('Mode'), value: modeName, icon: '🧭' }, { label: _('WAN IP'), value: status.wan_ip || _('Unknown'), icon: '🌍' }, { label: _('LAN IP'), value: status.lan_ip || _('Unknown'), icon: '🏠' } diff --git a/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes b/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes index 4af3d03..e850479 100755 --- a/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes +++ b/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes @@ -6,6 +6,17 @@ . /lib/functions.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" BACKUP_DIR="/etc/network-modes-backup" 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 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 "last_change" "$last_change" diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css b/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css index efeb269..5a5f269 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/common.css @@ -294,6 +294,47 @@ pre { 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 === */ .sh-filter-tabs { display: flex; @@ -443,14 +484,15 @@ pre { justify-content: space-between; align-items: center; gap: 12px; - flex-wrap: wrap; + flex-wrap: nowrap; + overflow-x: auto; box-shadow: 0 10px 24px rgba(15, 23, 42, 0.05); } .sh-header-meta { display: flex; gap: 10px; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; } @@ -507,3 +549,12 @@ pre { color: #b45309; } +@media (max-width: 900px) { + .sh-page-header-lite { + flex-wrap: wrap; + } + + .sh-header-meta { + flex-wrap: wrap; + } +} diff --git a/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js b/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js index 20157cd..b0c7f78 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js +++ b/luci-app-secubox/htdocs/luci-static/resources/secubox/nav.js @@ -4,7 +4,7 @@ var tabs = [ { id: 'dashboard', icon: '🚀', label: _('Dashboard'), path: ['admin', 'secubox', 'dashboard'] }, { 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: 'settings', icon: '⚙️', label: _('Settings'), path: ['admin', 'secubox', 'settings'] } ]; diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js index 7d1b3fe..4bbc09a 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/alerts.js @@ -44,8 +44,10 @@ return view.extend({ var self = this; 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/secubox.css') }), SecuNav.renderTabs('alerts'), this.renderHeader(), + this.renderHeaderActions(), this.renderControls(), this.renderStats(), this.renderAlertsList() @@ -62,33 +64,47 @@ return view.extend({ }, renderHeader: function() { - var self = this; - return E('div', { 'class': 'secubox-page-header' }, [ + var stats = this.getAlertStats(); + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ - E('h2', {}, '⚠️ System Alerts'), - E('p', { 'class': 'secubox-page-subtitle' }, - 'Monitor and manage system alerts and notifications') + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '⚠️'), + _('System Alerts') + ]), + E('p', { 'class': 'sh-page-subtitle' }, + _('Monitor and manage system alerts and notifications')) ]), - 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') + E('div', { 'class': 'sh-header-meta' }, [ + this.renderHeaderChip('total', '📊', _('Total'), stats.total), + this.renderHeaderChip('errors', '❌', _('Errors'), stats.errors, stats.errors ? 'danger' : ''), + this.renderHeaderChip('warnings', '⚠️', _('Warnings'), stats.warnings, stats.warnings ? 'warn' : ''), + this.renderHeaderChip('info', 'ℹ️', _('Info'), stats.info), + this.renderHeaderChip('ack', '🧹', _('Dismissed'), stats.dismissed || 0) ]) ]); }, + 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() { var self = this; @@ -144,17 +160,22 @@ return view.extend({ }, renderStats: function() { + return E('div', { 'id': 'secubox-alerts-stats', 'class': 'secubox-alerts-stats' }, + this.renderStatCards()); + }, + + renderStatCards: 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; - return E('div', { 'class': 'secubox-alerts-stats' }, [ + return [ this.renderStatCard('Total Alerts', alerts.length, '📊', '#6366f1'), this.renderStatCard('Errors', errorCount, '❌', '#ef4444'), this.renderStatCard('Warnings', warningCount, '⚠️', '#f59e0b'), this.renderStatCard('Info', infoCount, 'ℹ️', '#3b82f6') - ]); + ]; }, renderStatCard: function(label, value, icon, color) { @@ -285,6 +306,54 @@ return view.extend({ 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() { var container = document.getElementById('alerts-container'); if (container) { @@ -296,6 +365,7 @@ return view.extend({ // Update stats this.updateStats(); + this.updateHeaderStats(); }, updateModuleFilter: function() { @@ -323,7 +393,9 @@ return view.extend({ }, 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() { diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js index 21675f0..7c23c1a 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dashboard.js @@ -4,7 +4,7 @@ 'require dom'; 'require poll'; 'require secubox/api as API'; -'require secubox-theme/theme as Theme'; +'require secubox/theme as Theme'; 'require secubox/nav as SecuNav'; // Load theme resources once @@ -72,15 +72,16 @@ return view.extend({ var moduleStats = this.getModuleStats(); var alertsCount = (this.alertsData.alerts || []).length; var healthScore = (this.healthData.overall && this.healthData.overall.score) || 0; - + var version = status.version || _('Unknown'); var stats = [ - { label: _('Modules'), value: counts.total || moduleStats.total || 0 }, - { label: _('Running'), value: counts.running || moduleStats.running || 0 }, - { label: _('Alerts'), value: alertsCount }, - { label: _('Health'), value: healthScore + '/100' } + { icon: '🏷️', label: _('Version'), value: version }, + { icon: '📦', label: _('Modules'), value: counts.total || moduleStats.total || 0 }, + { icon: '🟢', label: _('Running'), value: counts.running || moduleStats.running || 0, tone: (counts.running || moduleStats.running) > 0 ? 'success' : '' }, + { 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('h2', { 'class': 'sh-page-title' }, [ E('span', { 'class': 'sh-page-title-icon' }, '🚀'), @@ -89,14 +90,17 @@ return view.extend({ E('p', { 'class': 'sh-page-subtitle' }, _('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) { - return E('div', { 'class': 'sh-stat-badge' }, [ - E('div', { 'class': 'sh-stat-value' }, stat.value.toString()), - E('div', { 'class': 'sh-stat-label' }, stat.label) + renderHeaderChip: function(stat) { + return E('div', { 'class': 'sh-header-chip' + (stat.tone ? ' ' + stat.tone : '') }, [ + E('span', { 'class': 'sh-chip-icon' }, stat.icon || '•'), + E('div', { 'class': 'sh-chip-text' }, [ + E('span', { 'class': 'sh-chip-label' }, stat.label), + E('strong', {}, stat.value.toString()) + ]) ]); }, diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dev-status.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dev-status.js index e0ab423..94e551d 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dev-status.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/dev-status.js @@ -3,12 +3,15 @@ 'require ui'; 'require poll'; 'require rpc'; +'require secubox/api as API'; +'require secubox/nav as SecuNav'; /** * SecuBox Development Status View (LuCI) * Real-time development progress tracker for LuCI interface */ return view.extend({ + statusData: {}, // Development milestones data milestones: { 'modules-core': { @@ -267,7 +270,11 @@ return view.extend({ * Load view */ 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('div', { 'class': 'cbi-map-descr' }, _('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, diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/help.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/help.js index b6250b8..d392ebf 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/help.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/help.js @@ -4,6 +4,7 @@ 'require secubox/api as API'; 'require secubox/help as Help'; 'require secubox/theme as Theme'; +'require secubox/nav as SecuNav'; // Ensure SecuBox theme variables are loaded for this view Theme.init(); @@ -30,6 +31,9 @@ return view.extend({ var helpPages = Help.getAllHelpPages(); 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.renderHelpCatalog(helpPages), this.renderSupportSection(), @@ -198,6 +202,20 @@ return view.extend({ 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()) + ]) + ]); } }); diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-debug.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-debug.js index 0210b67..88f1e77 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-debug.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-debug.js @@ -2,8 +2,9 @@ 'require view'; 'require rpc'; 'require secubox/theme as Theme'; +'require secubox/api as API'; +'require secubox/nav as SecuNav'; -// Initialize theme Theme.init(); var callModules = rpc.declare({ @@ -13,54 +14,90 @@ var callModules = rpc.declare({ }); return view.extend({ - load: function() { - 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); + statusData: {}, - var modules = data || []; - console.log('Modules array length:', modules.length); + load: function() { + 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) { - return E('div', {class:'cbi-map'}, [ - E('h2', {}, '📦 SecuBox Modules'), - E('div', {style:'color:red;padding:20px;background:#fee;border:1px solid red;border-radius:8px'}, [ - 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)) - ]) - ]); - } + render: function(modules) { + modules = modules || []; + var running = modules.filter(function(m) { return m.running; }).length; + var installed = modules.filter(function(m) { return m.installed; }).length; - return E('div', {class:'cbi-map'}, [ - E('h2', {}, '📦 SecuBox Modules (' + modules.length + ' found)'), - E('div', {style:'display:grid;gap:12px'}, modules.map(function(m) { - console.log('Rendering module:', m.name); - return E('div', {style:'background:#1e293b;padding:16px;border-radius:8px;border-left:4px solid '+m.color}, [ - E('div', {style:'font-weight:bold;color:#f1f5f9'}, m.name), - 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') - ]); - })) - ]); - } + return E('div', { 'class': 'secubox-modules-debug' }, [ + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/common.css') }), + E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox/secubox.css') }), + SecuNav.renderTabs('modules'), + this.renderHeader(modules.length, running, installed), + modules.length ? this.renderModuleGrid(modules) : this.renderEmptyState() + ]); + }, + + 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()) + ]) + ]); + } }); diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-minimal.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-minimal.js index a54f15d..286482e 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-minimal.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules-minimal.js @@ -1,22 +1,60 @@ 'use strict'; 'require view'; 'require secubox/theme as Theme'; +'require secubox/api as API'; +'require secubox/nav as SecuNav'; -// Initialize theme Theme.init(); return view.extend({ - load: function() { - return Promise.resolve(null); - }, - render: function() { - return E('div', {'class': 'cbi-map'}, [ - E('h2', {}, 'Test: SecuBox Modules'), - E('div', {'style': 'padding:20px;background:#d4edda;border:2px solid green'}, [ - 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.') - ]) - ]); - } + statusData: {}, + + load: function() { + return API.getStatus().then(L.bind(function(status) { + this.statusData = status || {}; + return status; + }, this)); + }, + + 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()) + ]) + ]); + } }); diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js index 5def706..021b514 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/modules.js @@ -3,7 +3,7 @@ 'require ui'; 'require dom'; 'require secubox/api as API'; -'require secubox-theme/theme as Theme'; +'require secubox/theme as Theme'; 'require secubox/nav as SecuNav'; 'require poll'; @@ -46,6 +46,7 @@ return view.extend({ 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/secubox.css') }), SecuNav.renderTabs('modules'), this.renderHeader(modules), this.renderFilterTabs(), @@ -66,34 +67,23 @@ return view.extend({ }, renderHeader: function(modules) { - var total = modules.length; - var installed = modules.filter(function(m) { return m.installed; }).length; - var enabled = modules.filter(function(m) { return m.enabled; }).length; - var disabled = installed - enabled; + var stats = this.getModuleStats(modules); - return E('div', { 'class': 'secubox-page-header' }, [ + return E('div', { 'class': 'sh-page-header sh-page-header-lite' }, [ E('div', {}, [ - E('h2', {}, '📦 SecuBox Modules'), - E('p', { 'class': 'secubox-page-subtitle' }, - 'Manage and monitor all SecuBox modules') + E('h2', { 'class': 'sh-page-title' }, [ + E('span', { 'class': 'sh-page-title-icon' }, '📦'), + _('SecuBox Modules') + ]), + E('p', { 'class': 'sh-page-subtitle' }, + _('Manage and monitor all SecuBox modules')) ]), - E('div', { 'class': 'secubox-modules-stats' }, [ - E('div', { 'class': 'secubox-stat-badge' }, [ - E('span', { 'class': 'secubox-stat-value' }, total), - E('span', { 'class': 'secubox-stat-label' }, 'Total') - ]), - E('div', { 'class': 'secubox-stat-badge secubox-stat-success' }, [ - 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') - ]) + E('div', { 'class': 'sh-header-meta' }, [ + this.renderHeaderChip('total', '🏷️', _('Total'), stats.total), + this.renderHeaderChip('installed', '💾', _('Installed'), stats.installed), + this.renderHeaderChip('active', '🟢', _('Active'), stats.enabled, stats.enabled ? 'success' : ''), + this.renderHeaderChip('inactive', '⚪', _('Disabled'), stats.disabled, stats.disabled ? 'warn' : ''), + this.renderHeaderChip('available', '📦', _('Available'), stats.available) ]) ]); }, @@ -139,7 +129,7 @@ return view.extend({ 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 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() { var activeTab = document.querySelector('.secubox-filter-tabs .cyber-tab.is-active[data-filter]'); var filter = activeTab ? activeTab.getAttribute('data-filter') : 'all'; this.filterModules(filter); - - // 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); - } + this.updateHeaderStats(); }, handleSaveApply: null, diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js index 9ca71f1..124b485 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/monitoring.js @@ -4,7 +4,7 @@ 'require dom'; 'require poll'; 'require secubox/api as API'; -'require secubox-theme/theme as Theme'; +'require secubox/theme as Theme'; 'require secubox/nav as SecuNav'; // 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/monitoring.css') }), SecuNav.renderTabs('monitoring'), + this.renderHeader(), this.renderHero(), this.renderChartsGrid(), this.renderCurrentStatsCard() @@ -75,9 +76,44 @@ return view.extend({ }); }, 5); + this.hideLegacyTabMenu(); + 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() { var snapshot = this.getLatestSnapshot(); @@ -277,7 +313,9 @@ return view.extend({ if (statsContainer) dom.content(statsContainer, this.renderStatsTable()); - this.updateHeroBadges(this.getLatestSnapshot()); + var snapshot = this.getLatestSnapshot(); + this.updateHeroBadges(snapshot); + this.updateHeaderChips(snapshot); }, updateHeroBadges: function(snapshot) { @@ -304,6 +342,36 @@ return view.extend({ }, 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() { return { cpu: this.cpuHistory[this.cpuHistory.length - 1] || { value: 0 }, diff --git a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js index 119b021..2931bd2 100644 --- a/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js +++ b/luci-app-secubox/htdocs/luci-static/resources/view/secubox/settings.js @@ -7,6 +7,11 @@ 'require secubox/theme as Theme'; '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({ load: function() { 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 m = new form.Map('secubox', null, null); @@ -248,5 +242,16 @@ return view.extend({ container.appendChild(formElement); return container; }, 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) + ]) + ]); } }); diff --git a/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox b/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox index 7c1fcef..29f7b5c 100755 --- a/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox +++ b/luci-app-secubox/root/usr/libexec/rpcd/luci.secubox @@ -7,6 +7,17 @@ . /lib/functions.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/ detect_modules() { local modules="" @@ -125,7 +136,7 @@ get_status() { local running=0 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')" # System info @@ -841,7 +852,7 @@ get_dashboard_data() { local mem_pct=$((mem_used * 100 / mem_total)) 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_int "uptime" "$uptime" json_add_string "load" "$load" diff --git a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json index 51d8293..636db20 100644 --- a/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json +++ b/luci-app-secubox/root/usr/share/luci/menu.d/luci-app-secubox.json @@ -32,13 +32,8 @@ "admin/secubox/monitoring": { "title": "Monitoring & Analytics", "order": 35, - "action": {"type": "firstchild"} - }, - "admin/secubox/monitoring/overview": { - "title": "System Monitoring", - "order": 10, "action": {"type": "view", "path": "secubox/monitoring"} - }, + }, "admin/secubox/network": { "title": "Network & Connectivity", "order": 40, diff --git a/luci-app-system-hub/README.md b/luci-app-system-hub/README.md index ea1dd43..0363f2d 100644 --- a/luci-app-system-hub/README.md +++ b/luci-app-system-hub/README.md @@ -226,8 +226,8 @@ Returns backup data in base64 format with size and filename. # Encode backup file to base64 DATA=$(base64 < backup.tar.gz | tr -d '\n') -# Restore -ubus call luci.system-hub restore_config "{\"data\":\"$DATA\"}" +# Restore (include original file name for logging/validation) +ubus call luci.system-hub restore_config "{\"file_name\":\"backup.tar.gz\",\"data\":\"$DATA\"}" ``` #### Reboot System @@ -409,11 +409,12 @@ Create system configuration backup. } ``` -### restore_config(data) +### restore_config(file_name, data) Restore system configuration from backup. **Parameters:** +- `file_name`: Original archive name (used for logging/validation, optional but recommended) - `data`: Base64-encoded backup data **Returns:** diff --git a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js index c3115e3..2b85fb0 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js @@ -59,7 +59,7 @@ var callBackupConfig = rpc.declare({ var callRestoreConfig = rpc.declare({ object: 'luci.system-hub', method: 'restore_config', - params: ['data'], + params: ['file_name', 'data'], expect: {} }); @@ -192,7 +192,15 @@ return baseclass.extend({ serviceAction: callServiceAction, getLogs: callGetLogs, backupConfig: callBackupConfig, - restoreConfig: callRestoreConfig, + restoreConfig: function(fileName, data) { + if (typeof fileName === 'object') + return callRestoreConfig(fileName); + + return callRestoreConfig({ + file_name: fileName, + data: data + }); + }, reboot: callReboot, getStorage: callGetStorage, getSettings: callGetSettings, diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/backup.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/backup.js index 2243bab..2d3074c 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/backup.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/backup.js @@ -8,15 +8,21 @@ Theme.init(); return view.extend({ + statusData: {}, + load: function() { - return Promise.resolve(); + return API.getSystemInfo().then(L.bind(function(info) { + this.statusData = info || {}; + return info; + }, this)); }, 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/backup.css') }), HubNav.renderTabs('backup'), + this.renderHeader(), this.renderHero(), E('div', { 'class': 'sh-backup-grid' }, [ this.renderBackupCard(), @@ -24,8 +30,25 @@ return view.extend({ 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() { @@ -161,28 +184,17 @@ return view.extend({ 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(); - reader.onload = function(ev) { - var arrayBuffer = ev.target.result; - var bytes = new Uint8Array(arrayBuffer); - var binary = ''; - for (var i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } + reader.onload = function() { + var base64Data = reader.result.split(',')[1]; + ui.showModal(_('Restoring backup…'), [ + E('p', { 'class': 'spinning' }, _('Uploading archive and applying configuration...')) + ]); - var encoded = btoa(binary); - API.restoreConfig(encoded).then(function(result) { + API.restoreConfig(file.name, base64Data).then(function(result) { ui.hideModal(); 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 { 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'); }); }; - - reader.onerror = function() { - ui.hideModal(); - ui.addNotification(null, E('p', {}, _('Could not read backup file')), 'error'); - }; - - reader.readAsArrayBuffer(file); + reader.readAsDataURL(file); }, rebootSystem: function() { - if (!confirm(_('Reboot the device now? All connections will be interrupted.'))) { - return; - } - - ui.showModal(_('System rebooting'), [ - E('p', {}, _('Device is restarting. The interface will be unreachable for ~60 seconds.')) + ui.showModal(_('Reboot system?'), [ + 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')), + E('button', { + '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, - handleSave: null, - handleReset: null + renderChip: 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()) + ]) + ]); + } }); diff --git a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js index 9974e7c..0cdd3af 100644 --- a/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js +++ b/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/overview.js @@ -59,8 +59,10 @@ return view.extend({ var hostname = this.sysInfo.hostname || 'OpenWrt'; var kernel = this.sysInfo.kernel || '-'; var score = (this.healthData.score || 0); + var version = this.sysInfo.version || _('Unknown'); var stats = [ + { label: _('Version'), value: version, icon: '🏷️' }, { label: _('Uptime'), value: uptime, icon: '⏱' }, { label: _('Hostname'), value: hostname, icon: '🖥' }, { label: _('Kernel'), value: kernel, copy: kernel, icon: '🧬' }, diff --git a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub index ecb0f77..e5b1574 100755 --- a/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub +++ b/luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub @@ -6,6 +6,19 @@ . /lib/functions.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" mkdir -p "$DIAG_DIR" @@ -25,6 +38,7 @@ status() { 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") + json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$hostname" json_add_string "model" "$model" json_add_int "uptime" "$uptime" @@ -66,6 +80,7 @@ get_system_info() { # Hostname local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "unknown") + json_add_string "version" "$PKG_VERSION" json_add_string "hostname" "$hostname" # Model @@ -494,8 +509,9 @@ restore_config() { read -r input json_load "$input" - local backup_data + local backup_data file_name json_get_var backup_data data + json_get_var file_name file_name json_cleanup if [ -z "$backup_data" ]; then @@ -506,18 +522,31 @@ restore_config() { return 1 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 - echo "$backup_data" | base64 -d > "$backup_file" 2>/dev/null - - if [ ! -f "$backup_file" ]; then + if ! echo "$backup_data" | base64 -d > "$backup_file" 2>/dev/null; then + rm -f "$backup_file" json_init json_add_boolean "success" 0 json_add_string "message" "Failed to decode backup data" json_dump return 1 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 sysupgrade -r "$backup_file" >/dev/null 2>&1 @@ -711,7 +740,7 @@ download_diagnostic() { json_add_string "error" "not_found" json_dump return - } + fi local data=$(base64 "$file" 2>/dev/null) json_add_boolean "success" 1 @@ -1226,7 +1255,7 @@ case "$1" in "service_action": { "service": "string", "action": "string" }, "get_logs": { "lines": 100, "filter": "" }, "backup_config": {}, - "restore_config": { "data": "string" }, + "restore_config": { "file_name": "string", "data": "string" }, "reboot": {}, "get_storage": {}, "get_settings": {}, diff --git a/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager index 2b55ee1..da756cf 100755 --- a/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager +++ b/luci-app-vhost-manager/root/usr/libexec/rpcd/luci.vhost-manager @@ -5,6 +5,17 @@ . /lib/functions.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" ACME_STATE_DIR="/etc/acme" VHOST_CONFIG="/etc/config/vhost_manager" @@ -187,7 +198,7 @@ case "$1" in json_init json_add_boolean "enabled" 1 json_add_string "module" "vhost-manager" - json_add_string "version" "0.4.1" + json_add_string "version" "$PKG_VERSION" # Check nginx status if pgrep -x nginx > /dev/null 2>&1; then diff --git a/rpc_reload.sh b/rpc_reload.sh new file mode 100644 index 0000000..09ccff9 --- /dev/null +++ b/rpc_reload.sh @@ -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"