docs: Add comprehensive CyberMood global theme system documentation

- Created GLOBAL_THEME_SYSTEM.md with complete theme specification
- Added THEME_CONTEXT.md for quick AI assistant reference
- Defined CyberMood design language (metallic, glass, neon aesthetics)
- Provided ready-to-use templates (CSS variables, components, JS controller)
- Planned multi-language support (en, fr, de, es)
- Created 5-week implementation roadmap
- Added 5 ready-to-use prompts for theme implementation
- Updated network-modes module with travel mode support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2025-12-28 16:58:11 +01:00
parent 6314884f00
commit 798e2e0435
11 changed files with 2248 additions and 13 deletions

292
.claude/THEME_CONTEXT.md Normal file
View File

@ -0,0 +1,292 @@
# Claude Context: Global Theme Implementation
**FOR CLAUDE CODE AI ASSISTANT**
## 🎯 When Asked to Work on Theme/UI
If the user asks you to:
- "Create a global theme"
- "Unify the design"
- "Implement CyberMood theme"
- "Add multi-language support"
- "Make it look like the website"
**IMPORTANT**: Read `DOCS/GLOBAL_THEME_SYSTEM.md` first!
## 🎨 Quick Design Reference
### Color Palette
```css
/* Use these variables in all new code */
Primary: var(--cyber-accent-primary) /* #667eea */
Secondary: var(--cyber-accent-secondary) /* #06b6d4 */
Background: var(--cyber-bg-primary) /* #0a0e27 */
Surface: var(--cyber-surface) /* #252b4a */
Text: var(--cyber-text-primary) /* #e2e8f0 */
Success: var(--cyber-success) /* #10b981 */
Danger: var(--cyber-danger) /* #ef4444 */
```
### Typography
```css
Display/Headers: var(--cyber-font-display) /* Orbitron */
Body Text: var(--cyber-font-body) /* Inter */
Code/Metrics: var(--cyber-font-mono) /* JetBrains Mono */
```
### Component Classes
```html
<!-- Cards -->
<div class="cyber-card">
<div class="cyber-card-header">
<h3 class="cyber-card-title">Title</h3>
</div>
<div class="cyber-card-body">Content</div>
</div>
<!-- Buttons -->
<button class="cyber-btn cyber-btn--primary">Primary</button>
<button class="cyber-btn cyber-btn--secondary">Secondary</button>
<!-- Badges -->
<span class="cyber-badge cyber-badge--success">Active</span>
<!-- Forms -->
<input type="text" class="cyber-input" />
<select class="cyber-select"></select>
```
## 🌍 Multi-Language Support
### Usage Pattern
```javascript
'require cybermood/theme as Theme';
// Initialize theme
Theme.init();
// Set language
Theme.setLanguage('fr'); // en, fr, de, es
// Translate strings
var title = Theme.t('dashboard.title');
var welcome = Theme.t('dashboard.welcome', { name: 'SecuBox' });
```
### Translation Keys Structure
```
common.* - Common UI strings (loading, error, success, etc.)
dashboard.* - Dashboard-specific strings
modules.* - Module names
settings.* - Settings page strings
[module_name].* - Module-specific strings
```
## 🏗️ Creating New Components
### Always Use Theme System
```javascript
// ❌ DON'T: Create components manually
E('div', { style: 'background: #667eea; padding: 16px;' }, 'Content');
// ✅ DO: Use theme components
Theme.createCard({
title: Theme.t('card.title'),
icon: '🎯',
content: E('div', {}, 'Content'),
variant: 'primary'
});
```
### Component Template
```javascript
// Create a new themed component
renderMyComponent: function() {
return E('div', { 'class': 'cyber-container' }, [
// Always load theme CSS first
E('link', {
'rel': 'stylesheet',
'href': L.resource('cybermood/cybermood.css')
}),
// Use theme components
Theme.createCard({
title: Theme.t('component.title'),
icon: '⚡',
content: this.renderContent(),
variant: 'success'
})
]);
}
```
## 📋 Implementation Prompts
### Prompt 1: Create Global Theme Package
```
Create the luci-theme-cybermood package following the structure in
DOCS/GLOBAL_THEME_SYSTEM.md. Include:
1. Package structure with Makefile
2. CSS variable system (variables.css)
3. Core components (cards.css, buttons.css, forms.css)
4. Theme controller JavaScript (cybermood.js)
5. Default translations (en.json, fr.json)
Use the ready-to-use templates from the documentation.
Apply CyberMood design aesthetic (metallic, glass effects, neon accents).
Ensure dark theme as default with light and cyberpunk variants.
```
### Prompt 2: Migrate Module to Global Theme
```
Migrate luci-app-[MODULE-NAME] to use the global CyberMood theme:
1. Remove module-specific CSS files (keep only module-unique styles)
2. Import cybermood.css in all views
3. Update all components to use cyber-* classes
4. Replace E() calls with Theme.create*() methods where appropriate
5. Replace hardcoded strings with Theme.t() translations
6. Test dark/light theme switching
7. Verify responsive design
Reference GLOBAL_THEME_SYSTEM.md for component usage examples.
```
### Prompt 3: Add Multi-Language Support
```
Add multi-language support to [MODULE-NAME]:
1. Extract all user-facing strings to translation keys
2. Create translation files for en, fr, de, es
3. Use Theme.t() for all strings
4. Add language selector to settings
5. Test language switching
Follow the translation structure in GLOBAL_THEME_SYSTEM.md.
Use meaningful translation keys (e.g., 'dashboard.active_modules').
```
### Prompt 4: Create New Themed Component
```
Create a new [COMPONENT-TYPE] component following CyberMood design:
1. Use CSS variables for all colors (var(--cyber-*))
2. Apply glass effect with backdrop-filter
3. Add hover animations (transform, glow effects)
4. Support dark/light themes
5. Make it responsive
6. Add to cybermood/components/
Style should match: metallic gradients, neon accents, smooth animations.
Reference existing components in GLOBAL_THEME_SYSTEM.md.
```
### Prompt 5: Implement Responsive Dashboard
```
Create a responsive dashboard layout using CyberMood theme:
1. Use cyber-grid for layout (auto-responsive)
2. Create cards with Theme.createCard()
3. Add stats with animated counters
4. Include theme toggle button
5. Add language selector
6. Support mobile (320px) to desktop (1920px+)
Follow the dashboard example in GLOBAL_THEME_SYSTEM.md.
Use metallic gradients for stats, glass effects for cards.
```
## ⚠️ Critical Rules
1. **NEVER hardcode colors**: Always use CSS variables
```css
/* ❌ BAD */
background: #667eea;
/* ✅ GOOD */
background: var(--cyber-accent-primary);
```
2. **ALWAYS support dark/light themes**: Test both
```css
/* Automatically handled by data-theme attribute */
[data-theme="light"] { /* overrides */ }
```
3. **ALWAYS use translation keys**: No hardcoded strings
```javascript
/* ❌ BAD */
E('h1', {}, 'Dashboard');
/* ✅ GOOD */
E('h1', {}, Theme.t('dashboard.title'));
```
4. **ALWAYS load theme CSS**: First element in render
```javascript
E('link', { 'rel': 'stylesheet', 'href': L.resource('cybermood/cybermood.css') })
```
5. **PREFER theme components**: Over manual E() creation
```javascript
/* ❌ ACCEPTABLE but not preferred */
E('div', { 'class': 'card' }, content);
/* ✅ PREFERRED */
Theme.createCard({ content: content });
```
## 🔍 Before You Start
1. Read `DOCS/GLOBAL_THEME_SYSTEM.md`
2. Check if `luci-theme-cybermood` package exists
3. Review existing themed modules for patterns
4. Test on both dark and light themes
5. Verify responsive on mobile/desktop
## 📚 Related Documentation
- **Main Guide**: `DOCS/GLOBAL_THEME_SYSTEM.md`
- **Development Guidelines**: `DOCS/DEVELOPMENT-GUIDELINES.md`
- **Quick Start**: `DOCS/QUICK-START.md`
- **Website Reference**: `http://192.168.8.191/luci-static/secubox/` (deployed demo)
## 🎨 Visual References
Look at these for design inspiration:
- SecuBox website: Modern, metallic, glass effects
- System Hub module: Dashboard layout, stats cards
- Network Modes: Header design, mode badges
- Existing help.css: Button styles, animations
## ✅ Quality Checklist
Before marking theme work complete:
- [ ] Uses CSS variables (no hardcoded colors)
- [ ] Supports dark/light/cyberpunk themes
- [ ] All strings use Theme.t() translations
- [ ] Components use cyber-* classes
- [ ] Responsive (mobile to 4K)
- [ ] Glass effects applied (backdrop-filter)
- [ ] Hover animations work smoothly
- [ ] Accessibility: keyboard navigation works
- [ ] Performance: < 50KB CSS bundle
- [ ] Browser tested: Chrome, Firefox, Safari
---
**Remember**: The goal is a unified, beautiful, responsive, multi-language CyberMood aesthetic across ALL SecuBox modules. Think: Cyberpunk meets modern minimalism. 🎯

200
.codex/THEME_CONTEXT.md Normal file
View File

@ -0,0 +1,200 @@
# Codex Context: Global Theme Implementation
**FOR CODEX CODING AGENT**
## 🎯 When Asked to Work on Theme/UI
If the user asks you to:
- “Create a global theme”
- “Unify the design”
- “Implement CyberMood theme”
- “Add multi-language support”
- “Make it look like the website”
**IMPORTANT**: Read `DOCS/GLOBAL_THEME_SYSTEM.md` first!
## 🎨 Quick Design Reference
### Color Palette
```css
/* Use these variables in all new code */
Primary: var(--cyber-accent-primary) /* #667eea */
Secondary: var(--cyber-accent-secondary) /* #06b6d4 */
Background: var(--cyber-bg-primary) /* #0a0e27 */
Surface: var(--cyber-surface) /* #252b4a */
Text: var(--cyber-text-primary) /* #e2e8f0 */
Success: var(--cyber-success) /* #10b981 */
Danger: var(--cyber-danger) /* #ef4444 */
```
### Typography
```css
Display/Headers: var(--cyber-font-display) /* Orbitron */
Body Text: var(--cyber-font-body) /* Inter */
Code/Metrics: var(--cyber-font-mono) /* JetBrains Mono */
```
### Component Classes
```html
<!-- Cards -->
<div class="cyber-card">
<div class="cyber-card-header">
<h3 class="cyber-card-title">Title</h3>
</div>
<div class="cyber-card-body">Content</div>
</div>
<!-- Buttons -->
<button class="cyber-btn cyber-btn--primary">Primary</button>
<button class="cyber-btn cyber-btn--secondary">Secondary</button>
<!-- Badges -->
<span class="cyber-badge cyber-badge--success">Active</span>
<!-- Forms -->
<input type="text" class="cyber-input" />
<select class="cyber-select"></select>
```
## 🌍 Multi-Language Support
### Usage Pattern
```javascript
'require cybermood/theme as Theme';
// Initialize theme
Theme.init();
// Set language
Theme.setLanguage('fr'); // en, fr, de, es
// Translate strings
var title = Theme.t('dashboard.title');
var welcome = Theme.t('dashboard.welcome', { name: 'SecuBox' });
```
### Translation Keys Structure
```
common.* - Common UI strings (loading, error, success, etc.)
dashboard.* - Dashboard-specific strings
modules.* - Module names
settings.* - Settings page strings
[module_name].* - Module-specific strings
```
## 🏗️ Creating New Components
### Always Use Theme System
```javascript
// ❌ DON'T: Create components manually
E('div', { style: 'background: #667eea; padding: 16px;' }, 'Content');
// ✅ DO: Use theme components
Theme.createCard({
title: Theme.t('card.title'),
icon: '🎯',
content: E('div', {}, 'Content'),
variant: 'primary'
});
```
### Component Template
```javascript
// Create a new themed component
renderMyComponent: function() {
return E('div', { 'class': 'cyber-container' }, [
// Always load theme CSS first
E('link', {
'rel': 'stylesheet',
'href': L.resource('cybermood/cybermood.css')
}),
// Use theme components
Theme.createCard({
title: Theme.t('component.title'),
icon: '⚡',
content: this.renderContent(),
variant: 'success'
})
]);
}
```
## 📋 Implementation Prompts
### Prompt 1: Create Global Theme Package
```
Create the luci-theme-cybermood package following the structure in
DOCS/GLOBAL_THEME_SYSTEM.md. Include:
1. Package structure with Makefile
2. CSS variable system (variables.css)
3. Core components (cards.css, buttons.css, forms.css)
4. Theme controller JavaScript (cybermood.js)
5. Default translations (en.json, fr.json)
Use the ready-to-use templates from the documentation.
Apply CyberMood design aesthetic (metallic, glass effects, neon accents).
Ensure dark theme as default with light and cyberpunk variants.
```
### Prompt 2: Migrate Module to Global Theme
```
Migrate luci-app-[MODULE-NAME] to use the global CyberMood theme:
1. Remove module-specific CSS files (keep only module-unique styles)
2. Import cybermood.css in all views
3. Update all components to use cyber-* classes
4. Replace E() calls with Theme.create*() methods where appropriate
5. Replace hardcoded strings with Theme.t() translations
6. Test dark/light theme switching
7. Verify responsive design
```
### Prompt 3: Add Multi-Language Support
```
Add multi-language support to [MODULE-NAME]:
1. Extract all user-facing strings to translation keys
2. Create translation files for en, fr, de, es
3. Use Theme.t() for all strings
4. Add language selector to settings
5. Test language switching
```
### Prompt 4: Create New Themed Component
```
Create a new [COMPONENT-TYPE] component following CyberMood design:
1. Use CSS variables for all colors (var(--cyber-*))
2. Apply glass effect with backdrop-filter
3. Add hover animations (transform, glow effects)
4. Support dark/light themes
5. Make it responsive
6. Add to cybermood/components/
```
### Prompt 5: Implement Responsive Dashboard
```
Create a responsive dashboard layout using CyberMood theme:
1. Use Theme.createCard for each section
2. Add quick stats, charts placeholders, alerts, and actions
3. Support breakpoints at 1440px, 1024px, 768px, 480px
4. Use CSS grid + flex combos from GLOBAL_THEME_SYSTEM.md
5. Ensure all copy uses Theme.t()
```
> **Always align new work with `cybermood/theme.js`, `cybermood.css`, and the prompts above.**

1040
DOCS/GLOBAL_THEME_SYSTEM.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -95,6 +95,18 @@ var callRouterConfig = rpc.declare({
expect: { }
});
var callTravelConfig = rpc.declare({
object: 'luci.network-modes',
method: 'travel_config',
expect: { }
});
var callTravelScan = rpc.declare({
object: 'luci.network-modes',
method: 'travel_scan_networks',
expect: { networks: [] }
});
var callUpdateSettings = rpc.declare({
object: 'luci.network-modes',
method: 'update_settings'
@ -237,6 +249,18 @@ return baseclass.extend({
'Signal amplification'
]
},
travel: {
id: 'travel',
name: 'Travel Router',
icon: '✈️',
description: 'Portable router for hotels and conferences. Clones WAN MAC and creates a secure personal hotspot.',
features: [
'Hotel WiFi client + scan wizard',
'MAC clone to bypass captive portals',
'Private WPA3 hotspot for your devices',
'Isolated NAT + DHCP sandbox'
]
},
sniffer: {
id: 'sniffer',
name: 'Sniffer Mode',
@ -275,6 +299,8 @@ return baseclass.extend({
getApConfig: callApConfig,
getRelayConfig: callRelayConfig,
getRouterConfig: callRouterConfig,
getTravelConfig: callTravelConfig,
scanTravelNetworks: callTravelScan,
updateSettings: function(mode, settings) {
var payload = Object.assign({}, settings || {}, { mode: mode });

View File

@ -132,6 +132,12 @@
border-color: rgba(16, 185, 129, 0.3);
}
.nm-mode-badge.travel {
background: rgba(251, 191, 36, 0.15);
color: var(--nm-accent-amber);
border-color: rgba(251, 191, 36, 0.35);
}
.nm-mode-badge.router {
background: rgba(249, 115, 22, 0.15);
color: var(--nm-router-color);
@ -200,6 +206,7 @@
.nm-mode-card.accesspoint { --mode-color: var(--nm-ap-color); }
.nm-mode-card.relay { --mode-color: var(--nm-relay-color); }
.nm-mode-card.router { --mode-color: var(--nm-router-color); }
.nm-mode-card.travel { --mode-color: var(--nm-accent-amber); }
.nm-mode-card:hover {
border-color: var(--mode-color);
@ -331,6 +338,12 @@
margin-bottom: 20px;
}
.nm-form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.nm-form-label {
display: block;
font-size: 13px;
@ -387,6 +400,48 @@
font-size: 13px;
}
.nm-scan-results {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-top: 16px;
}
.nm-scan-card {
border: 1px solid var(--nm-border);
border-radius: var(--nm-radius);
background: var(--nm-bg-tertiary);
padding: 12px;
text-align: left;
cursor: pointer;
transition: border-color 0.2s, transform 0.2s;
}
.nm-scan-card:hover {
border-color: var(--nm-accent-orange);
transform: translateY(-2px);
}
.nm-scan-ssid {
font-weight: 600;
font-size: 14px;
}
.nm-scan-meta {
display: flex;
gap: 8px;
font-size: 11px;
color: var(--nm-text-muted);
}
.nm-empty {
text-align: center;
padding: 16px;
color: var(--nm-text-muted);
border: 1px dashed var(--nm-border);
border-radius: var(--nm-radius);
}
/* Toggle Switch */
.nm-toggle {
display: flex;

View File

@ -54,10 +54,11 @@ return view.extend({
var currentMode = status.current_mode || 'router';
var modeInfos = {
sniffer: api.getModeInfo('sniffer'),
router: api.getModeInfo('router'),
accesspoint: api.getModeInfo('accesspoint'),
relay: api.getModeInfo('relay'),
router: api.getModeInfo('router')
travel: api.getModeInfo('travel'),
sniffer: api.getModeInfo('sniffer')
};
var currentModeInfo = modeInfos[currentMode];
@ -127,7 +128,7 @@ return view.extend({
E('th', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, '🌉 Bridge'),
E('th', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, '📡 Access Point'),
E('th', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, '🔁 Repeater'),
E('th', {}, '✈️ Travel Router')
E('th', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, '✈️ Travel Router')
])
]),
E('tbody', {}, [
@ -137,7 +138,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'L2 Forwarding'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, 'WiFi Hotspot'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'WiFi Extender'),
E('td', {}, 'Portable WiFi')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'Hotel / Travel kit')
]),
E('tr', {}, [
E('td', { 'class': 'feature-label' }, 'WAN Ports'),
@ -145,7 +146,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'All bridged'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, '1 uplink'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'WiFi'),
E('td', {}, 'WiFi/Ethernet')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'WiFi or USB')
]),
E('tr', {}, [
E('td', { 'class': 'feature-label' }, 'LAN Ports'),
@ -153,7 +154,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'All ports'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, 'All ports'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'All ports'),
E('td', {}, 'All ports')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'All ports')
]),
E('tr', {}, [
E('td', { 'class': 'feature-label' }, 'WiFi Role'),
@ -161,7 +162,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'Optional AP'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, 'AP only'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'Client + AP'),
E('td', {}, 'Client + AP')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'Client + AP')
]),
E('tr', {}, [
E('td', { 'class': 'feature-label' }, 'DHCP Server'),
@ -169,7 +170,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'No'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, 'No'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'Yes'),
E('td', {}, 'Yes')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'Yes')
]),
E('tr', {}, [
E('td', { 'class': 'feature-label' }, 'NAT'),
@ -177,7 +178,7 @@ return view.extend({
E('td', { 'class': currentMode === 'bridge' ? 'active-mode' : '' }, 'Disabled'),
E('td', { 'class': currentMode === 'accesspoint' ? 'active-mode' : '' }, 'Disabled'),
E('td', { 'class': currentMode === 'relay' ? 'active-mode' : '' }, 'Enabled'),
E('td', {}, 'Enabled')
E('td', { 'class': currentMode === 'travel' ? 'active-mode' : '' }, 'Enabled')
])
])
])

View File

@ -0,0 +1,275 @@
'use strict';
'require view';
'require dom';
'require ui';
'require network-modes.api as api';
'require network-modes.helpers as helpers';
'require secubox/help as Help';
return view.extend({
title: _('Travel Router Mode'),
load: function() {
return api.getTravelConfig();
},
render: function(data) {
var config = data || {};
var client = config.client || {};
var hotspot = config.hotspot || {};
var lan = config.lan || {};
var interfaces = config.available_interfaces || ['wlan0', 'wlan1'];
var radios = config.available_radios || ['radio0', 'radio1'];
var view = E('div', { 'class': 'network-modes-dashboard' }, [
E('div', { 'class': 'nm-header' }, [
E('div', { 'class': 'nm-logo' }, [
E('div', { 'class': 'nm-logo-icon', 'style': 'background: linear-gradient(135deg,#fbbf24,#f97316)' }, '✈️'),
E('div', { 'class': 'nm-logo-text' }, ['Travel ', E('span', { 'style': 'background: linear-gradient(135deg,#f97316,#fb923c); -webkit-background-clip:text; -webkit-text-fill-color:transparent;' }, 'Router')])
]),
Help.createHelpButton('network-modes', 'header', {
icon: '📘',
label: _('Travel help'),
modal: true
})
]),
E('div', { 'class': 'nm-alert nm-alert-info' }, [
E('span', { 'class': 'nm-alert-icon' }, '🌍'),
E('div', {}, [
E('div', { 'class': 'nm-alert-title' }, _('Portable security for hotels & events')),
E('div', { 'class': 'nm-alert-text' },
_('Connect the router as a WiFi client, clone the WAN MAC if needed, and broadcast your own encrypted hotspot for trusted devices.'))
])
]),
// Client WiFi
E('div', { 'class': 'nm-card' }, [
E('div', { 'class': 'nm-card-header' }, [
E('div', { 'class': 'nm-card-title' }, [
E('span', { 'class': 'nm-card-title-icon' }, '📡'),
_('Client WiFi Uplink')
]),
E('div', { 'class': 'nm-card-badge' }, client.ssid ? _('Connected to ') + client.ssid : _('No uplink configured'))
]),
E('div', { 'class': 'nm-card-body' }, [
E('div', { 'class': 'nm-form-grid' }, [
this.renderSelectField(_('Client interface'), 'travel-client-iface', interfaces, client.interface || 'wlan1'),
this.renderSelectField(_('Client radio'), 'travel-client-radio', radios, client.radio || 'radio1'),
this.renderSelectField(_('Encryption'), 'travel-encryption', [
'sae-mixed', 'sae', 'psk2', 'psk-mixed', 'none'
], client.encryption || 'sae-mixed')
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('SSID / BSSID')),
E('input', { 'class': 'nm-input', 'id': 'travel-client-ssid', 'value': client.ssid || '', 'placeholder': _('Hotel WiFi name') }),
E('div', { 'class': 'nm-form-hint' }, _('Click a scanned network below to autofill'))
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Password / captive portal token')),
E('input', { 'class': 'nm-input', 'type': 'password', 'id': 'travel-client-password', 'value': client.password || '' }),
E('div', { 'class': 'nm-form-hint' }, _('Leave empty for open WiFi or captive portal'))
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('WAN MAC clone')),
E('input', {
'class': 'nm-input',
'id': 'travel-mac-clone',
'value': client.clone_mac || '',
'placeholder': 'AA:BB:CC:DD:EE:FF'
}),
E('div', { 'class': 'nm-form-hint' }, _('Copy the MAC of the laptop/room card if the hotel locks access'))
]),
E('div', { 'class': 'nm-btn-group' }, [
E('button', {
'class': 'nm-btn',
'data-action': 'travel-scan',
'type': 'button'
}, [
E('span', {}, '🔍'),
_('Scan networks')
]),
E('span', { 'id': 'travel-scan-status', 'class': 'nm-text-muted' }, _('Last scan: never'))
]),
E('div', { 'class': 'nm-scan-results', 'id': 'travel-scan-results' }, [
E('div', { 'class': 'nm-empty' }, _('No scan results yet'))
])
])
]),
// Hotspot
E('div', { 'class': 'nm-card' }, [
E('div', { 'class': 'nm-card-header' }, [
E('div', { 'class': 'nm-card-title' }, [
E('span', { 'class': 'nm-card-title-icon' }, '🔥'),
_('Personal Hotspot')
]),
E('div', { 'class': 'nm-card-badge' }, _('WPA3 / WPA2 mixed'))
]),
E('div', { 'class': 'nm-card-body' }, [
E('div', { 'class': 'nm-form-grid' }, [
this.renderSelectField(_('Hotspot radio'), 'travel-hotspot-radio', radios, hotspot.radio || 'radio0')
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Hotspot SSID')),
E('input', { 'class': 'nm-input', 'id': 'travel-hotspot-ssid', 'value': hotspot.ssid || 'SecuBox-Travel' })
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('Hotspot password')),
E('input', { 'class': 'nm-input', 'type': 'text', 'id': 'travel-hotspot-password', 'value': hotspot.password || 'TravelSafe123!' })
])
])
]),
// LAN / DHCP
E('div', { 'class': 'nm-card' }, [
E('div', { 'class': 'nm-card-header' }, [
E('div', { 'class': 'nm-card-title' }, [
E('span', { 'class': 'nm-card-title-icon' }, '🛡️'),
_('LAN & DHCP Sandbox')
])
]),
E('div', { 'class': 'nm-card-body' }, [
E('div', { 'class': 'nm-form-grid' }, [
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('LAN Gateway IP')),
E('input', { 'class': 'nm-input', 'id': 'travel-lan-ip', 'value': lan.subnet || '10.77.0.1' })
]),
E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, _('LAN Netmask')),
E('input', { 'class': 'nm-input', 'id': 'travel-lan-mask', 'value': lan.netmask || '255.255.255.0' })
])
]),
E('div', { 'class': 'nm-form-hint' }, _('Each trip gets its own private /24 network to avoid overlapping hotel ranges.'))
])
]),
// Actions
E('div', { 'class': 'nm-btn-group' }, [
E('button', {
'class': 'nm-btn nm-btn-primary',
'type': 'button',
'data-action': 'travel-save'
}, [
E('span', {}, '💾'),
_('Save travel settings')
]),
E('button', {
'class': 'nm-btn',
'type': 'button',
'data-action': 'travel-preview'
}, [
E('span', {}, '📝'),
_('Preview configuration')
])
])
]);
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('network-modes/dashboard.css') });
document.head.appendChild(cssLink);
this.bindTravelActions(view);
return view;
},
renderSelectField: function(label, id, options, selected) {
return E('div', { 'class': 'nm-form-group' }, [
E('label', { 'class': 'nm-form-label' }, label),
E('select', { 'class': 'nm-select', 'id': id },
options.map(function(opt) {
return E('option', { 'value': opt, 'selected': opt === selected }, opt);
})
)
]);
},
bindTravelActions: function(container) {
var scanBtn = container.querySelector('[data-action="travel-scan"]');
if (scanBtn)
scanBtn.addEventListener('click', ui.createHandlerFn(this, 'scanNetworks', container));
var saveBtn = container.querySelector('[data-action="travel-save"]');
if (saveBtn)
saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveTravelSettings', container));
var previewBtn = container.querySelector('[data-action="travel-preview"]');
if (previewBtn)
previewBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'travel'));
},
scanNetworks: function(container) {
var statusEl = container.querySelector('#travel-scan-status');
if (statusEl)
statusEl.textContent = _('Scanning...');
return api.scanTravelNetworks().then(L.bind(function(result) {
if (statusEl)
statusEl.textContent = _('Last scan: ') + new Date().toLocaleTimeString();
this.populateScanResults(container, (result && result.networks) || []);
}, this)).catch(function(err) {
if (statusEl)
statusEl.textContent = _('Scan failed');
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
populateScanResults: function(container, networks) {
var list = container.querySelector('#travel-scan-results');
if (!list)
return;
list.innerHTML = '';
if (!networks.length) {
list.appendChild(E('div', { 'class': 'nm-empty' }, _('No networks detected')));
return;
}
networks.slice(0, 8).forEach(L.bind(function(net) {
var card = E('button', {
'class': 'nm-scan-card',
'type': 'button'
}, [
E('div', { 'class': 'nm-scan-ssid' }, net.ssid || _('Hidden SSID')),
E('div', { 'class': 'nm-scan-meta' }, [
E('span', {}, net.channel ? _('Ch. ') + net.channel : ''),
E('span', {}, net.signal || ''),
E('span', {}, net.encryption || '')
])
]);
card.addEventListener('click', ui.createHandlerFn(this, 'selectScannedNetwork', container, net));
list.appendChild(card);
}, this));
},
selectScannedNetwork: function(container, network) {
var ssidInput = container.querySelector('#travel-client-ssid');
if (ssidInput)
ssidInput.value = network.ssid || '';
if (network.encryption) {
var encSelect = container.querySelector('#travel-encryption');
if (encSelect && Array.prototype.some.call(encSelect.options, function(opt) { return opt.value === network.encryption; }))
encSelect.value = network.encryption;
}
},
saveTravelSettings: function(container) {
var payload = {
client_interface: container.querySelector('#travel-client-iface') ? container.querySelector('#travel-client-iface').value : '',
client_radio: container.querySelector('#travel-client-radio') ? container.querySelector('#travel-client-radio').value : '',
hotspot_radio: container.querySelector('#travel-hotspot-radio') ? container.querySelector('#travel-hotspot-radio').value : '',
ssid: container.querySelector('#travel-client-ssid') ? container.querySelector('#travel-client-ssid').value : '',
password: container.querySelector('#travel-client-password') ? container.querySelector('#travel-client-password').value : '',
encryption: container.querySelector('#travel-encryption') ? container.querySelector('#travel-encryption').value : 'sae-mixed',
hotspot_ssid: container.querySelector('#travel-hotspot-ssid') ? container.querySelector('#travel-hotspot-ssid').value : '',
hotspot_password: container.querySelector('#travel-hotspot-password') ? container.querySelector('#travel-hotspot-password').value : '',
clone_mac: container.querySelector('#travel-mac-clone') ? container.querySelector('#travel-mac-clone').value : '',
lan_subnet: container.querySelector('#travel-lan-ip') ? container.querySelector('#travel-lan-ip').value : '',
lan_netmask: container.querySelector('#travel-lan-mask') ? container.querySelector('#travel-lan-mask').value : ''
};
return helpers.persistSettings('travel', payload);
}
});

View File

@ -49,3 +49,20 @@ config mode 'router'
option https_frontend '0'
option frontend_type 'nginx'
list frontend_domains ''
config mode 'travel'
option name 'Travel Router'
option description 'Portable router with WiFi client uplink and personal hotspot'
option enabled '0'
option client_radio 'radio1'
option client_interface 'wlan1'
option hotspot_radio 'radio0'
option hotspot_interface 'wlan0'
option ssid ''
option password ''
option encryption 'sae-mixed'
option hotspot_ssid 'SecuBox-Travel'
option hotspot_password 'TravelSafe123!'
option clone_mac ''
option lan_subnet '10.77.0.1'
option lan_netmask '255.255.255.0'

View File

@ -131,6 +131,19 @@ get_modes() {
json_add_boolean "https_frontend" "$(uci -q get network-modes.router.https_frontend || echo 0)"
json_add_string "frontend_type" "$(uci -q get network-modes.router.frontend_type)"
json_close_object
# Travel mode
json_add_object
json_add_string "id" "travel"
json_add_string "name" "$(uci -q get network-modes.travel.name || echo 'Travel Router')"
json_add_string "description" "$(uci -q get network-modes.travel.description || echo 'Portable router with WiFi uplink and personal hotspot')"
json_add_string "icon" "✈️"
json_add_boolean "active" "$([ "$current_mode" = "travel" ] && echo 1 || echo 0)"
json_add_string "client_interface" "$(uci -q get network-modes.travel.client_interface || echo 'wlan1')"
json_add_string "hotspot_interface" "$(uci -q get network-modes.travel.hotspot_interface || echo 'wlan0')"
json_add_boolean "mac_clone_enabled" "$([ -n "$(uci -q get network-modes.travel.clone_mac)" ] && echo 1 || echo 0)"
json_add_string "hotspot_ssid" "$(uci -q get network-modes.travel.hotspot_ssid || echo 'SecuBox-Travel')"
json_close_object
json_close_array
json_dump
@ -360,6 +373,135 @@ get_router_config() {
json_dump
}
# Get travel router configuration
get_travel_config() {
json_init
local client_iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local hotspot_iface=$(uci -q get network-modes.travel.hotspot_interface || echo "wlan0")
local client_radio=$(uci -q get network-modes.travel.client_radio || echo "radio1")
local hotspot_radio=$(uci -q get network-modes.travel.hotspot_radio || echo "radio0")
local clone_mac=$(uci -q get network-modes.travel.clone_mac || echo "")
json_add_string "mode" "travel"
json_add_string "name" "$(uci -q get network-modes.travel.name || echo 'Travel Router')"
json_add_string "description" "$(uci -q get network-modes.travel.description || echo 'Portable WiFi router for hotels and coworking spaces')"
json_add_object "client"
json_add_string "interface" "$client_iface"
json_add_string "radio" "$client_radio"
json_add_string "ssid" "$(uci -q get network-modes.travel.ssid || echo '')"
json_add_string "encryption" "$(uci -q get network-modes.travel.encryption || echo 'sae-mixed')"
json_add_string "password" "$(uci -q get network-modes.travel.password || echo '')"
json_add_string "clone_mac" "$clone_mac"
json_add_boolean "mac_clone_enabled" "$([ -n "$clone_mac" ] && echo 1 || echo 0)"
json_close_object
json_add_object "hotspot"
json_add_string "interface" "$hotspot_iface"
json_add_string "radio" "$hotspot_radio"
json_add_string "ssid" "$(uci -q get network-modes.travel.hotspot_ssid || echo 'SecuBox-Travel')"
json_add_string "password" "$(uci -q get network-modes.travel.hotspot_password || echo 'TravelSafe123!')"
json_add_string "band" "$(uci -q get network-modes.travel.hotspot_band || echo 'dual')"
json_close_object
json_add_object "lan"
json_add_string "subnet" "$(uci -q get network-modes.travel.lan_subnet || echo '10.77.0.1')"
json_add_string "netmask" "$(uci -q get network-modes.travel.lan_netmask || echo '255.255.255.0')"
json_close_object
json_add_array "available_interfaces"
for iface in $(ls /sys/class/net/ 2>/dev/null | grep -E '^wl|^wlan' || true); do
json_add_string "" "$iface"
done
json_close_array
json_add_array "available_radios"
for radio in $(uci show wireless 2>/dev/null | grep "=wifi-device" | cut -d'.' -f2 | cut -d'=' -f1); do
json_add_string "" "$radio"
done
json_close_array
json_dump
}
# Scan nearby WiFi networks for travel mode
travel_scan_networks() {
json_init
json_add_array "networks"
local iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local scan_output
scan_output="$(iwinfo "$iface" scan 2>/dev/null || true)"
if [ -z "$scan_output" ]; then
json_close_array
json_dump
return
fi
local bssid=""
local ssid=""
local channel=""
local signal=""
local encryption=""
local quality=""
while IFS= read -r line; do
case "$line" in
Cell*)
if [ -n "$bssid" ]; then
json_add_object
json_add_string "ssid" "${ssid:-Unknown}"
json_add_string "bssid" "$bssid"
json_add_string "channel" "${channel:-?}"
json_add_string "signal" "${signal:-N/A}"
json_add_string "quality" "${quality:-N/A}"
json_add_string "encryption" "${encryption:-Unknown}"
json_close_object
fi
bssid=$(printf '%s\n' "$line" | awk '{print $5}')
ssid=""
channel=""
signal=""
encryption=""
quality=""
;;
*"ESSID:"*)
ssid=$(printf '%s\n' "$line" | sed -n 's/.*ESSID: "\(.*\)".*/\1/p')
;;
*"Channel:"*)
channel=$(printf '%s\n' "$line" | awk -F'Channel:' '{print $2}' | awk '{print $1}')
;;
*"Signal:"*)
signal=$(printf '%s\n' "$line" | awk -F'Signal:' '{print $2}' | awk '{print $1" "$2}')
if echo "$line" | grep -q 'Quality'; then
quality=$(printf '%s\n' "$line" | sed -n 's/.*Quality: \([0-9\/]*\).*/\1/p')
fi
;;
*"Encryption:"*)
encryption=$(printf '%s\n' "$line" | sed -n 's/.*Encryption: //p')
;;
esac
done <<EOF
$scan_output
EOF
if [ -n "$bssid" ]; then
json_add_object
json_add_string "ssid" "${ssid:-Unknown}"
json_add_string "bssid" "$bssid"
json_add_string "channel" "${channel:-?}"
json_add_string "signal" "${signal:-N/A}"
json_add_string "quality" "${quality:-N/A}"
json_add_string "encryption" "${encryption:-Unknown}"
json_close_object
fi
json_close_array
json_dump
}
# Apply mode change (with actual network reconfiguration and rollback timer)
apply_mode() {
json_init
@ -426,8 +568,82 @@ apply_mode() {
apply_transparent_proxy_rules
deploy_nginx_vhosts
;;
travel)
local client_iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local client_radio=$(uci -q get network-modes.travel.client_radio || echo "radio1")
local hotspot_radio=$(uci -q get network-modes.travel.hotspot_radio || echo "radio0")
local travel_ssid=$(uci -q get network-modes.travel.ssid || echo "")
local travel_password=$(uci -q get network-modes.travel.password || echo "")
local travel_encryption=$(uci -q get network-modes.travel.encryption || echo "sae-mixed")
local hotspot_ssid=$(uci -q get network-modes.travel.hotspot_ssid || echo "SecuBox-Travel")
local hotspot_password=$(uci -q get network-modes.travel.hotspot_password || echo "TravelSafe123!")
local clone_mac=$(uci -q get network-modes.travel.clone_mac || echo "")
local lan_ip=$(uci -q get network-modes.travel.lan_subnet || echo "10.77.0.1")
local lan_netmask=$(uci -q get network-modes.travel.lan_netmask || echo "255.255.255.0")
accesspoint)
uci delete network.wan 2>/dev/null
uci set network.wan=interface
uci set network.wan.proto='dhcp'
uci set network.wan.device="$client_iface"
uci set network.lan=interface
uci set network.lan.proto='static'
uci set network.lan.device='br-lan'
uci set network.lan.ipaddr="$lan_ip"
uci set network.lan.netmask="$lan_netmask"
uci set dhcp.lan=dhcp
uci set dhcp.lan.interface='lan'
uci set dhcp.lan.start='60'
uci set dhcp.lan.limit='80'
uci set dhcp.lan.leasetime='6h'
uci set firewall.@zone[0]=zone
uci set firewall.@zone[0].name='lan'
uci set firewall.@zone[0].input='ACCEPT'
uci set firewall.@zone[0].output='ACCEPT'
uci set firewall.@zone[0].forward='ACCEPT'
uci set firewall.@zone[1]=zone
uci set firewall.@zone[1].name='wan'
uci set firewall.@zone[1].network='wan'
uci set firewall.@zone[1].input='REJECT'
uci set firewall.@zone[1].output='ACCEPT'
uci set firewall.@zone[1].forward='REJECT'
uci set firewall.@zone[1].masq='1'
uci set firewall.@zone[1].mtu_fix='1'
uci delete wireless.travel_sta 2>/dev/null
uci set wireless.travel_sta=wifi-iface
uci set wireless.travel_sta.device="$client_radio"
uci set wireless.travel_sta.mode='sta'
uci set wireless.travel_sta.network='wan'
[ -n "$travel_ssid" ] && uci set wireless.travel_sta.ssid="$travel_ssid" || uci delete wireless.travel_sta.ssid 2>/dev/null
uci set wireless.travel_sta.encryption="$travel_encryption"
if [ -n "$travel_password" ]; then
uci set wireless.travel_sta.key="$travel_password"
else
uci delete wireless.travel_sta.key 2>/dev/null
fi
if [ -n "$clone_mac" ]; then
uci set wireless.travel_sta.macaddr="$clone_mac"
else
uci delete wireless.travel_sta.macaddr 2>/dev/null
fi
uci delete wireless.travel_ap 2>/dev/null
uci set wireless.travel_ap=wifi-iface
uci set wireless.travel_ap.device="$hotspot_radio"
uci set wireless.travel_ap.mode='ap'
uci set wireless.travel_ap.network='lan'
uci set wireless.travel_ap.ssid="$hotspot_ssid"
uci set wireless.travel_ap.encryption='sae-mixed'
uci set wireless.travel_ap.key="$hotspot_password"
uci set wireless.travel_ap.ieee80211w='1'
uci set wireless.travel_ap.hidden='0'
;;
accesspoint)
# Access Point mode: Bridge, no NAT, DHCP client
# Delete WAN
uci delete network.wan 2>/dev/null
@ -668,6 +884,31 @@ update_settings() {
[ -n "$dns_over_https" ] && uci set network-modes.router.dns_over_https="$dns_over_https"
[ -n "$letsencrypt" ] && uci set network-modes.router.letsencrypt="$letsencrypt"
;;
travel)
json_get_var client_interface client_interface
json_get_var client_radio client_radio
json_get_var hotspot_radio hotspot_radio
json_get_var ssid ssid
json_get_var password password
json_get_var encryption encryption
json_get_var hotspot_ssid hotspot_ssid
json_get_var hotspot_password hotspot_password
json_get_var clone_mac clone_mac
json_get_var lan_subnet lan_subnet
json_get_var lan_netmask lan_netmask
[ -n "$client_interface" ] && uci set network-modes.travel.client_interface="$client_interface"
[ -n "$client_radio" ] && uci set network-modes.travel.client_radio="$client_radio"
[ -n "$hotspot_radio" ] && uci set network-modes.travel.hotspot_radio="$hotspot_radio"
[ -n "$ssid" ] && uci set network-modes.travel.ssid="$ssid"
[ -n "$password" ] && uci set network-modes.travel.password="$password"
[ -n "$encryption" ] && uci set network-modes.travel.encryption="$encryption"
[ -n "$hotspot_ssid" ] && uci set network-modes.travel.hotspot_ssid="$hotspot_ssid"
[ -n "$hotspot_password" ] && uci set network-modes.travel.hotspot_password="$hotspot_password"
[ -n "$clone_mac" ] && uci set network-modes.travel.clone_mac="$clone_mac"
[ -n "$lan_subnet" ] && uci set network-modes.travel.lan_subnet="$lan_subnet"
[ -n "$lan_netmask" ] && uci set network-modes.travel.lan_netmask="$lan_netmask"
;;
*)
json_add_boolean "success" 0
json_add_string "error" "Invalid mode"
@ -1415,6 +1656,49 @@ config forwarding
option src 'lan'
option dest 'wan'"
;;
travel)
local travel_ssid=$(uci -q get network-modes.travel.ssid || echo "HotelWiFi")
local hotspot_ssid=$(uci -q get network-modes.travel.hotspot_ssid || echo "SecuBox-Travel")
local lan_ip=$(uci -q get network-modes.travel.lan_subnet || echo "10.77.0.1")
config="# Travel Router Mode
# /etc/config/network
config interface 'wan'
option proto 'dhcp'
option device 'wlan-sta'
config interface 'lan'
option proto 'static'
option ipaddr '$lan_ip'
option netmask '255.255.255.0'
option device 'br-lan'
# /etc/config/wireless
config wifi-iface 'travel_sta'
option device 'radio1'
option mode 'sta'
option network 'wan'
option ssid '$travel_ssid'
option encryption 'sae-mixed'
option key 'hotel-password'
config wifi-iface 'travel_ap'
option device 'radio0'
option mode 'ap'
option network 'lan'
option ssid '$hotspot_ssid'
option encryption 'sae-mixed'
option key 'TravelSafe123!'
# /etc/config/firewall
config zone
option name 'wan'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'"
;;
esac
json_add_string "config" "$config"
@ -1503,6 +1787,21 @@ get_available_modes() {
json_close_array
json_close_object
# Travel router mode
json_add_object
json_add_string "id" "travel"
json_add_string "name" "Travel Router"
json_add_string "description" "Portable hotspot WiFi depuis l'ethernet ou WiFi hôtel"
json_add_string "icon" "✈️"
json_add_boolean "current" "$([ "$current_mode" = "travel" ] && echo 1 || echo 0)"
json_add_array "features"
json_add_string "" "Client WiFi + scan SSID"
json_add_string "" "Clone MAC WAN"
json_add_string "" "Hotspot privé WPA3"
json_add_string "" "NAT + DHCP isolé"
json_close_array
json_close_object
# Bridge mode
json_add_object
json_add_string "id" "bridge"
@ -1532,7 +1831,7 @@ set_mode() {
# Validate mode
case "$target_mode" in
router|accesspoint|relay|bridge)
router|accesspoint|relay|bridge|sniffer|travel)
;;
*)
json_add_boolean "success" 0
@ -1735,7 +2034,7 @@ rollback() {
# Main dispatcher
case "$1" in
list)
echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"},"validate_pcap_filter":{"filter":"str"},"cleanup_old_pcaps":{}}'
echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"travel_config":{},"travel_scan_networks":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"},"validate_pcap_filter":{"filter":"str"},"cleanup_old_pcaps":{}}'
;;
call)
case "$2" in
@ -1778,6 +2077,12 @@ case "$1" in
router_config)
get_router_config
;;
travel_config)
get_travel_config
;;
travel_scan_networks)
travel_scan_networks
;;
update_settings)
update_settings
;;

View File

@ -49,6 +49,14 @@
"path": "network-modes/relay"
}
},
"admin/secubox/network-modes/travel": {
"title": "Travel Mode",
"order": 55,
"action": {
"type": "view",
"path": "network-modes/travel"
}
},
"admin/secubox/network-modes/sniffer": {
"title": "Sniffer Mode",
"order": 60,
@ -65,4 +73,4 @@
"path": "network-modes/settings"
}
}
}
}

View File

@ -0,0 +1,16 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-theme-secubox
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=LuCI - SecuBox CyberMood Theme
LUCI_DESCRIPTION:=Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards.
LUCI_DEPENDS:=+luci-base
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot