navbar
This commit is contained in:
parent
76955f48d0
commit
9b9becd0a8
25
.claude/HISTORY.md
Normal file
25
.claude/HISTORY.md
Normal 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
21
.claude/TODO.md
Normal 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
21
.claude/WIP.md
Normal 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
21
.codex/HISTORY.md
Normal 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
21
.codex/TODO.md
Normal 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
17
.codex/WIP.md
Normal 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.
|
||||
@ -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/<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:**
|
||||
```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:
|
||||
|
||||
@ -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() }
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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: '🏠' }
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'] }
|
||||
];
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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())
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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())
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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())
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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())
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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:**
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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())
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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: '🧬' },
|
||||
|
||||
@ -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": {},
|
||||
|
||||
@ -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
|
||||
|
||||
5
rpc_reload.sh
Normal file
5
rpc_reload.sh
Normal 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"
|
||||
Loading…
Reference in New Issue
Block a user