feat(vhosts-checker): Dark theme UI with emoji status and fixed route detection

- Fix jshn boolean handling (use 1/0 instead of "true"/"false")
- Rework UI with dark theme compatible styling
- Add emoji-based status indicators (🔗🔒🛡️)
- Simplify interface with async Load More pagination
- Update README.md to v0.18.0 with 86 modules

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-04 11:37:08 +01:00
parent 686fe113c5
commit 5b8c4cd52c
3 changed files with 419 additions and 365 deletions

390
README.md
View File

@ -1,194 +1,211 @@
# SecuBox - Security Suite for OpenWrt
**Version:** 0.17.0 🎉 **First Public Release**
**Last Updated:** 2026-01-31
**Status:** Production Ready
**Modules:** 38 LuCI Applications
**Version:** 0.18.0
**Last Updated:** 2026-03-04
**Status:** Production Ready
**Modules:** 86 LuCI Applications
[![Build OpenWrt Packages](https://github.com/CyberMind-FR/secubox-openwrt/actions/workflows/build-openwrt-packages.yml/badge.svg)](https://github.com/CyberMind-FR/secubox-openwrt/actions/workflows/build-openwrt-packages.yml)
[![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](LICENSE)
[![Release](https://img.shields.io/github/v/release/CyberMind-FR/secubox-openwrt?include_prereleases&label=release)](https://github.com/CyberMind-FR/secubox-openwrt/releases)
## 🎉 First Public Release
SecuBox v0.17.0 marks the **First Public Release** of the project. All core features are now stable and ready for production deployment.
### What's Ready
- ✅ **38 LuCI modules** — Complete security and network management suite
- ✅ **Three-Loop Security Architecture** — Operational, Tactical, and Strategic defense layers
- ✅ **CrowdSec Integration** — Real-time threat intelligence and automated blocking
- ✅ **Deep Packet Inspection** — netifyd/nDPId protocol analysis
- ✅ **WireGuard VPN** — Encrypted mesh connectivity
- ✅ **Multi-architecture support** — ARM64, ARM32, MIPS, x86
### Coming Next (v0.18+)
- 🔵 **P2P Hub** — Decentralized threat intelligence sharing
- 🔵 **did:plc Identity** — Self-sovereign node identity for trust networks
---
## Overview
SecuBox is a comprehensive security and network management suite for OpenWrt, providing a unified ecosystem of 38 specialized dashboards and tools. All modules are compiled automatically for multiple OpenWrt architectures via GitHub Actions.
SecuBox is a comprehensive security and network management suite for OpenWrt, providing a unified ecosystem of 86 specialized dashboards and tools. The platform implements a **Four-Layer Architecture** for defense in depth, featuring AI-powered threat analysis, P2P mesh networking, and multi-channel service exposure.
**Website:** [secubox.maegia.tv](https://secubox.maegia.tv)
**Website:** [secubox.maegia.tv](https://secubox.maegia.tv)
**Publisher:** [CyberMind.fr](https://cybermind.fr)
---
## Three-Loop Security Architecture
SecuBox implements a **Three-Loop Security Model** for defense in depth:
## Four-Layer Architecture
```
┌────────────────────────────────────────────────────────┐
│ LOOP 3: STRATEGIC │
│ (Hours → Days) │
│ ┌────────────────────────────────────────────────┐ │
│ │ LOOP 2: TACTICAL │ │
│ │ (Minutes → Hours) │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ LOOP 1: OPERATIONAL │ │ │
│ │ │ (Milliseconds → Seconds) │ │ │
│ │ │ DETECT → DECIDE → BLOCK │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ │ CORRELATE → ANALYZE → ADAPT │ │
│ └────────────────────────────────────────────────┘ │
│ AGGREGATE → ANTICIPATE → EVOLVE │
└────────────────────────────────────────────────────────┘
+============================================================+
| LAYER 4: MESH NETWORKING |
| MirrorNet / P2P Hub / Services Mirrors |
| +--------------------------------------------------------+ |
| | LAYER 3: AI GATEWAY | |
| | MCP Server / Threat Analyst / DNS Guard | |
| | +----------------------------------------------------+ | |
| | | LAYER 2: TACTICAL | | |
| | | CrowdSec / WAF / Scenarios | | |
| | | +------------------------------------------------+ | | |
| | | | LAYER 1: OPERATIONAL | | | |
| | | | fw4 / DPI / Bouncer / HAProxy | | | |
| | | +------------------------------------------------+ | | |
| | +----------------------------------------------------+ | |
| +--------------------------------------------------------+ |
+============================================================+
```
| Loop | Function | SecuBox Modules |
|------|----------|-----------------|
| **Loop 1** | Real-time blocking | nftables/fw4, netifyd DPI, CrowdSec Bouncer |
| **Loop 2** | Pattern correlation | CrowdSec Agent/LAPI, Scenarios, Netdata |
| **Loop 3** | Threat intelligence | CrowdSec CAPI, Blocklists, P2P Hub (v0.18+) |
See [DOCS/THREE-LOOP-ARCHITECTURE.md](DOCS/THREE-LOOP-ARCHITECTURE.md) for detailed analysis.
| Layer | Function | Time Scale | SecuBox Components |
|-------|----------|------------|-------------------|
| **Layer 1** | Real-time blocking | ms → seconds | nftables/fw4, netifyd DPI, CrowdSec Bouncer |
| **Layer 2** | Pattern correlation | minutes → hours | CrowdSec Agent/LAPI, mitmproxy WAF, Scenarios |
| **Layer 3** | AI analysis | minutes → hours | MCP Server, Threat Analyst, DNS Guard |
| **Layer 4** | Mesh networking | continuous | P2P Hub, MirrorBox, Services Registry |
---
## SecuBox Modules
## Key Features
### SecuBox Core (5 modules)
### Security
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-secubox** | 0.7.1 | Central dashboard/Hub for all SecuBox modules |
| **luci-app-secubox-portal** | 0.7.0 | Unified entry point with tabbed navigation |
| **luci-app-secubox-admin** | 1.0.0 | Admin control center with appstore and monitoring |
| **luci-app-secubox-bonus** | 0.2.0 | Documentation, local repo, and app store |
| **luci-app-system-hub** | 0.5.1 | Central system control with logs and backup |
- **CrowdSec Integration** — Real-time threat intelligence, CAPI enrollment, auto-banning
- **mitmproxy WAF** — HTTPS inspection with CVE detection, sensitivity-based auto-ban
- **Deep Packet Inspection** — netifyd/nDPId protocol analysis
- **MAC Guardian** — WiFi MAC spoofing detection with CrowdSec integration
- **DNS Guard** — AI-powered DGA, tunneling, and anomaly detection
### Security & Threat Management (9 modules)
### AI Gateway
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-crowdsec-dashboard** | 0.7.0 | Real-time CrowdSec security monitoring |
| **luci-app-secubox-security-threats** | 1.0.0 | Unified netifyd DPI + CrowdSec intelligence |
| **luci-app-client-guardian** | 0.4.0 | Network access, captive portal, parental controls |
| **luci-app-auth-guardian** | 0.4.0 | OAuth2/OIDC authentication, voucher system |
| **luci-app-exposure** | 1.0.0 | Service exposure manager |
| **luci-app-tor-shield** | 1.0.0 | Tor anonymization dashboard |
| **luci-app-mitmproxy** | 0.4.0 | HTTPS traffic inspection |
| **luci-app-cyberfeed** | 0.1.1 | Cyberpunk RSS feed aggregator |
| **luci-app-ksm-manager** | 0.4.0 | Cryptographic key/HSM management |
- **MCP Server** — Model Context Protocol for Claude Desktop integration
- **Threat Analyst** — Autonomous AI agent for threat analysis and rule generation
- **LocalAI** — Self-hosted LLM with model management
### Deep Packet Inspection (2 modules)
### Mesh Networking
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-ndpid** | 1.1.2 | nDPId deep packet inspection dashboard |
| **luci-app-secubox-netifyd** | 1.2.1 | netifyd DPI with real-time flow monitoring |
- **P2P Hub** — Decentralized peer discovery with globe visualization
- **MirrorBox** — Distributed service catalog with auto-sync
- **App Store** — P2P package distribution across mesh peers
- **Master Link** — Secure mesh onboarding with dynamic IPK generation
### Network & Connectivity (8 modules)
### Service Exposure
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-vhost-manager** | 0.5.0 | Nginx reverse proxy with Let's Encrypt SSL |
| **luci-app-haproxy** | 1.0.0 | Load balancer with vhosts and SSL |
| **luci-app-wireguard-dashboard** | 0.7.0 | WireGuard VPN monitoring |
| **luci-app-network-modes** | 0.5.0 | Sniffer, AP, Relay, Router modes |
| **luci-app-network-tweaks** | 1.0.0 | Auto Proxy DNS & Hosts from vhosts |
| **luci-app-mqtt-bridge** | 0.4.0 | USB-to-MQTT IoT hub |
| **luci-app-cdn-cache** | 0.5.0 | Content delivery optimization |
| **luci-app-media-flow** | 0.6.4 | Streaming detection (Netflix, YouTube, Spotify) |
- **Punk Exposure** — Multi-channel service emancipation (Tor + DNS/SSL + Mesh)
- **HAProxy** — Load balancer with webroot ACME, auto-SSL
- **Tor Shield** — .onion hidden services with split-routing
### Bandwidth & Traffic Management (2 modules)
### Media & Content
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-bandwidth-manager** | 0.5.0 | QoS rules, client quotas, SQM integration |
| **luci-app-traffic-shaper** | 0.4.0 | TC/CAKE traffic shaping |
- **Jellyfin** — LXC media server with setup wizard
- **Lyrion** — Music server with CIFS integration
- **Zigbee2MQTT** — LXC Alpine container for IoT
- **Domoticz** — Home automation with MQTT bridge
### Content & Web Platforms (5 modules)
---
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-gitea** | 1.0.0 | Gitea Platform management |
| **luci-app-hexojs** | 1.0.0 | Hexo static site generator |
| **luci-app-metabolizer** | 1.0.0 | Metabolizer CMS support |
| **luci-app-magicmirror2** | 0.4.0 | MagicMirror2 smart display |
| **luci-app-mmpm** | 0.2.0 | MagicMirror Package Manager |
## SecuBox Modules (86 Total)
### AI/LLM & Analytics (4 modules)
### Core (6 modules)
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-localai** | 0.1.0 | LocalAI LLM management |
| **luci-app-ollama** | 0.1.0 | Ollama LLM management |
| **luci-app-glances** | 1.0.0 | Glances system monitoring |
| **luci-app-netdata-dashboard** | 0.5.0 | Real-time Netdata monitoring |
| Module | Description |
|--------|-------------|
| luci-app-secubox | Central dashboard/Hub |
| luci-app-secubox-portal | Unified entry point with tabs |
| luci-app-secubox-admin | Admin control center |
| secubox-app-bonus | App store and documentation |
| luci-app-system-hub | System control with backup |
| luci-theme-secubox | KISS UI theme |
### Streaming & Data Processing (2 modules)
### Security (15 modules)
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-streamlit** | 1.0.0 | Streamlit Platform management |
| **luci-app-picobrew** | 1.0.0 | PicoBrew Server management |
| Module | Description |
|--------|-------------|
| luci-app-crowdsec-dashboard | CrowdSec monitoring |
| luci-app-security-threats | Unified netifyd + CrowdSec |
| luci-app-client-guardian | Captive portal, parental controls |
| luci-app-auth-guardian | OAuth2/OIDC, vouchers |
| luci-app-exposure | Service exposure manager |
| luci-app-tor-shield | Tor anonymization |
| luci-app-mitmproxy | HTTPS inspection WAF |
| luci-app-mac-guardian | WiFi MAC security |
| luci-app-dns-guard | AI-powered DNS anomaly |
| luci-app-waf | Web Application Firewall |
| luci-app-threat-analyst | AI threat analysis |
| luci-app-ksm-manager | Key/HSM management |
| luci-app-master-link | Mesh onboarding |
| luci-app-routes-status | VHosts route checker |
| secubox-mcp-server | MCP protocol server |
### IoT & Smart Devices (1 module)
### Network (12 modules)
| Module | Version | Description |
|--------|---------|-------------|
| **luci-app-zigbee2mqtt** | 1.0.0 | Zigbee2MQTT docker management |
| Module | Description |
|--------|-------------|
| luci-app-haproxy | Load balancer with SSL |
| luci-app-wireguard-dashboard | WireGuard VPN |
| luci-app-vhost-manager | Nginx reverse proxy |
| luci-app-network-modes | Sniffer/AP/Relay/Router |
| luci-app-network-tweaks | DNS & proxy controls |
| luci-app-dns-provider | DNS provider API |
| luci-app-cdn-cache | CDN optimization |
| luci-app-bandwidth-manager | QoS and quotas |
| luci-app-traffic-shaper | TC/CAKE shaping |
| luci-app-mqtt-bridge | USB-to-MQTT IoT |
| luci-app-media-flow | Streaming detection |
| luci-app-netdiag | Network diagnostics |
### DPI (2 modules)
| Module | Description |
|--------|-------------|
| luci-app-ndpid | nDPId deep packet inspection |
| luci-app-netifyd | netifyd flow monitoring |
### P2P Mesh (4 modules)
| Module | Description |
|--------|-------------|
| luci-app-p2p | P2P Hub with MirrorBox |
| luci-app-service-registry | Service catalog |
| luci-app-device-intel | Device intelligence |
| secubox-content-pkg | Content distribution |
### AI/LLM (4 modules)
| Module | Description |
|--------|-------------|
| luci-app-localai | LocalAI v3.9.0 |
| luci-app-ollama | Ollama LLM |
| luci-app-glances | System monitoring |
| luci-app-netdata-dashboard | Netdata real-time |
### Media (7 modules)
| Module | Description |
|--------|-------------|
| luci-app-jellyfin | Media server (LXC) |
| luci-app-lyrion | Music server |
| luci-app-zigbee2mqtt | Zigbee gateway (LXC) |
| luci-app-domoticz | Home automation (LXC) |
| luci-app-ksmbd | SMB/CIFS shares |
| luci-app-smbfs | Remote mount manager |
| luci-app-magicmirror2 | Smart display |
### Content Platforms (6 modules)
| Module | Description |
|--------|-------------|
| luci-app-gitea | Git platform |
| luci-app-hexojs | Static site generator |
| luci-app-metablogizer | Metabolizer CMS |
| luci-app-streamlit | Streamlit apps |
| luci-app-picobrew | PicoBrew server |
| luci-app-jitsi | Video conferencing |
### Remote Access (3 modules)
| Module | Description |
|--------|-------------|
| luci-app-rustdesk | RustDesk relay |
| luci-app-guacamole | Clientless desktop |
| luci-app-simplex | SimpleX Chat |
### *Plus 27 additional supporting packages...*
---
## Supported Architectures
### ARM 64-bit (AArch64)
| Target | Devices |
|--------|---------|
| `aarch64-cortex-a53` | ESPRESSObin, BananaPi R64 |
| `aarch64-cortex-a72` | MOCHAbin, Raspberry Pi 4, NanoPi R4S |
| `mediatek-filogic` | GL.iNet MT3000, BananaPi R3 |
| `rockchip-armv8` | NanoPi R4S/R5S, FriendlyARM |
| `bcm27xx-bcm2711` | Raspberry Pi 4, Compute Module 4 |
### ARM 32-bit
| Target | Devices |
|--------|---------|
| `arm-cortex-a7-neon` | Orange Pi, BananaPi, Allwinner |
| `arm-cortex-a9-neon` | Linksys WRT, Turris Omnia |
| `qualcomm-ipq40xx` | Google WiFi, Zyxel NBG6617 |
### MIPS
| Target | Devices |
|--------|---------|
| `mips-24kc` | TP-Link Archer, Ubiquiti |
| `mipsel-24kc` | Xiaomi, GL.iNet, Netgear |
### x86
| Target | Devices |
|--------|---------|
| `x86-64` | PC, VMs, Docker, Proxmox |
| Architecture | Targets | Example Devices |
|--------------|---------|-----------------|
| **ARM64** | aarch64-cortex-a53/a72, mediatek-filogic, rockchip-armv8 | MOCHAbin, NanoPi R4S/R5S, GL.iNet MT3000, Raspberry Pi 4 |
| **ARM32** | arm-cortex-a7/a9-neon, qualcomm-ipq40xx | Turris Omnia, Google WiFi |
| **MIPS** | mips-24kc, mipsel-24kc | TP-Link Archer, Xiaomi |
| **x86** | x86-64 | PC, VMs, Docker, Proxmox |
---
@ -196,12 +213,9 @@ See [DOCS/THREE-LOOP-ARCHITECTURE.md](DOCS/THREE-LOOP-ARCHITECTURE.md) for detai
### From Pre-built Packages
Download from [GitHub Releases](https://github.com/CyberMind-FR/secubox-openwrt/releases):
```bash
opkg update
opkg install luci-app-secubox-portal_*.ipk
opkg install luci-app-system-hub_*.ipk
opkg install luci-app-crowdsec-dashboard_*.ipk
```
@ -213,73 +227,53 @@ cd ~/openwrt-sdk/package/
git clone https://github.com/CyberMind-FR/secubox-openwrt.git secubox
# Build
cd ~/openwrt-sdk/
make package/secubox/luci-app-secubox-portal/compile V=s
```
### Add as OpenWrt Feed
Add to `feeds.conf.default`:
### Add as Feed
```
src-git secubox https://github.com/CyberMind-FR/secubox-openwrt.git
```
Then:
```bash
./scripts/feeds update secubox
./scripts/feeds install -a -p secubox
make menuconfig # Select modules under LuCI > Applications
make V=s
```
---
## Repository Structure
## MCP Integration (Claude Desktop)
```
secubox-openwrt/
├── package/secubox/ # All 38 SecuBox LuCI packages
├── secubox-tools/ # Build tools and local SDK
├── DOCS/ # Documentation
│ ├── THREE-LOOP-ARCHITECTURE.md # Security model analysis
│ ├── DEVELOPMENT-GUIDELINES.md
│ ├── QUICK-START.md
│ └── VALIDATION-GUIDE.md
└── .github/workflows/ # CI/CD
SecuBox includes an MCP server for AI integration:
```json
{
"mcpServers": {
"secubox": {
"command": "ssh",
"args": ["root@192.168.255.1", "/usr/bin/secubox-mcp"]
}
}
}
```
---
## OpenWrt Compatibility
| Version | Status | Package Format |
|---------|--------|----------------|
| 25.x | Testing | `.apk` |
| 24.10.x | **Recommended** | `.ipk` |
| 23.05.x | Supported | `.ipk` |
**Available tools:** `crowdsec.alerts`, `crowdsec.decisions`, `waf.logs`, `dns.queries`, `network.flows`, `system.metrics`, `wireguard.status`, `ai.analyze_threats`, `ai.cve_lookup`, `ai.suggest_waf_rules`
---
## Roadmap
| Phase | Version | Status | Focus |
|-------|---------|--------|-------|
| **Core Mesh** | v0.17 | ✅ Released | Loops 1+2 complete |
| **Service Mesh** | v0.18 | 🔵 In Progress | P2P Hub foundation |
| **Intelligence Mesh** | v0.19 | ⚪ Planned | Full P2P intelligence |
| **AI Mesh** | v0.20 | ⚪ Planned | ML in Loop 2 |
| **Certification** | v1.0 | ⚪ Planned | ANSSI certification |
| Version | Status | Focus |
|---------|--------|-------|
| **v0.17** | Released | Core Mesh, 38 modules |
| **v0.18** | Current | P2P Hub, AI Gateway, 86 modules |
| **v0.19** | Planned | Full P2P intelligence |
| **v1.0** | Planned | ANSSI certification |
---
## Links
* **Website**: [secubox.maegia.tv](https://secubox.maegia.tv)
* **GitHub**: [github.com/CyberMind-FR/secubox-openwrt](https://github.com/CyberMind-FR/secubox-openwrt)
* **Publisher**: [CyberMind.fr](https://cybermind.fr)
* **Issues**: [GitHub Issues](https://github.com/CyberMind-FR/secubox-openwrt/issues)
- **Website**: [secubox.maegia.tv](https://secubox.maegia.tv)
- **GitHub**: [github.com/CyberMind-FR/secubox-openwrt](https://github.com/CyberMind-FR/secubox-openwrt)
- **Publisher**: [CyberMind.fr](https://cybermind.fr)
- **Issues**: [GitHub Issues](https://github.com/CyberMind-FR/secubox-openwrt/issues)
---
@ -289,20 +283,10 @@ Apache-2.0 © 2024-2026 CyberMind.fr
---
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
---
## Author
**Gandalf** - [CyberMind.fr](https://cybermind.fr)
**Ex Tenebris, Lux Securitas**
🇫🇷 Made with love in France
Made in France

View File

@ -8,6 +8,7 @@
var callStatus = rpc.declare({
object: 'luci.routes-status',
method: 'status',
params: ['offset', 'limit'],
expect: { }
});
@ -25,46 +26,36 @@ var callAddRoute = rpc.declare({
});
return view.extend({
allVhosts: [],
currentOffset: 0,
pageSize: 50,
totalVhosts: 0,
statusData: null,
load: function() {
return callStatus();
return callStatus(0, 50);
},
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)
])
]);
},
renderPill: function(text, type) {
var colors = {
success: '#4CAF50',
warning: '#ff9800',
danger: '#f44336',
info: '#2196F3',
muted: '#9e9e9e'
};
// Emoji-based status pill
pill: function(emoji, label, ok) {
return E('span', {
'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:4px;color:#fff;background:' + (colors[type] || colors.muted) + ';font-size:0.8em;font-weight:500;'
}, text);
'class': 'vhost-pill' + (ok ? ' ok' : ' warn'),
'title': label
}, emoji);
},
handleSync: function() {
ui.showModal(_('Syncing Routes...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
ui.showModal(_('Syncing...'), [
E('p', { 'class': 'spinning' }, _('Syncing routes from HAProxy...'))
]);
callSyncRoutes().then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.addNotification(null, E('p', {}, _('Routes synchronized successfully')), 'success');
ui.addNotification(null, E('p', {}, '✅ ' + _('Routes synchronized')), 'success');
location.reload();
} else {
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
ui.addNotification(null, E('p', {}, '❌ ' + (res.error || 'Sync failed')), 'error');
}
});
},
@ -74,11 +65,11 @@ return view.extend({
ui.showModal(_('Add Route'), [
E('div', { 'class': 'cbi-section' }, [
E('p', {}, _('Add mitmproxy route for: %s').format(domain)),
E('p', {}, _('Add mitmproxy route for: ') + domain),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Backend Port')),
E('label', { 'class': 'cbi-value-title' }, _('Port')),
E('div', { 'class': 'cbi-value-field' }, [
E('input', { 'type': 'number', 'id': 'route-port', 'value': port || '443', 'style': 'width:100px;' })
E('input', { 'type': 'number', 'id': 'route-port', 'value': port || '443', 'style': 'width:80px;' })
])
])
]),
@ -93,61 +84,102 @@ return view.extend({
self.doAddRoute(domain, p);
}
},
'style': 'margin-left:10px;'
}, _('Add Route'))
'style': 'margin-left:8px;'
}, _('Add'))
])
]);
},
doAddRoute: function(domain, port) {
ui.showModal(_('Adding Route...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
ui.showModal(_('Adding...'), [
E('p', { 'class': 'spinning' }, _('Adding route...'))
]);
callAddRoute(domain, port).then(function(res) {
ui.hideModal();
if (res && res.success) {
ui.addNotification(null, E('p', {}, _('Route added successfully')), 'success');
ui.addNotification(null, E('p', {}, '✅ ' + _('Route added')), 'success');
location.reload();
} else {
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
ui.addNotification(null, E('p', {}, '❌ ' + (res.error || 'Failed')), 'error');
}
});
},
renderVhostRow: function(vhost) {
handleLoadMore: function() {
var self = this;
var missingRoutes = !vhost.has_route_out || !vhost.has_route_in;
var btn = document.getElementById('load-more-btn');
var spinner = document.getElementById('load-spinner');
return E('tr', {}, [
if (btn) btn.style.display = 'none';
if (spinner) spinner.style.display = 'block';
this.currentOffset += this.pageSize;
callStatus(this.currentOffset, this.pageSize).then(function(data) {
if (spinner) spinner.style.display = 'none';
var newVhosts = data.vhosts || [];
if (newVhosts.length > 0) {
self.allVhosts = self.allVhosts.concat(newVhosts);
var tbody = document.getElementById('vhosts-tbody');
if (tbody) {
newVhosts.forEach(function(v) {
tbody.appendChild(self.renderRow(v));
});
}
var counter = document.getElementById('vhosts-count');
if (counter) {
counter.textContent = self.allVhosts.length + '/' + self.totalVhosts;
}
}
if (self.allVhosts.length < self.totalVhosts && btn) {
btn.style.display = 'inline-block';
}
});
},
renderRow: function(v) {
var self = this;
var needsRoute = !v.has_route_out || !v.has_route_in;
return E('tr', { 'class': 'vhost-row' }, [
// Domain
E('td', {}, [
E('a', {
'href': 'https://' + vhost.domain,
'href': 'https://' + v.domain,
'target': '_blank',
'style': 'color:#1976D2;text-decoration:none;font-weight:500;'
}, vhost.domain)
'class': 'vhost-domain'
}, v.domain)
]),
E('td', {}, vhost.backend || '-'),
E('td', {}, [
vhost.has_route_out ? this.renderPill('OUT', 'success') : this.renderPill('OUT', 'warning'),
vhost.has_route_in ? this.renderPill('IN', 'success') : this.renderPill('IN', 'warning')
// Status indicators (emoji-based)
E('td', { 'class': 'vhost-status' }, [
// Routes
this.pill(v.has_route_out && v.has_route_in ? '🔗' : '⚠️',
'Routes: ' + (v.has_route_out ? 'OUT✓' : 'OUT✗') + ' ' + (v.has_route_in ? 'IN✓' : 'IN✗'),
v.has_route_out && v.has_route_in),
// SSL
this.pill(v.ssl_status === 'valid' ? '🔒' : v.ssl_status === 'expiring' ? '⏰' : '🔓',
'SSL: ' + v.ssl_status,
v.ssl_status === 'valid'),
// WAF
this.pill(v.waf_bypass ? '🚫' : '🛡️',
v.waf_bypass ? 'WAF Bypass' : 'WAF Active',
!v.waf_bypass),
// Active
this.pill(v.active ? '✅' : '⏸️',
v.active ? 'Active' : 'Inactive',
v.active)
]),
// Action
E('td', {}, [
vhost.ssl_status === 'valid' ? this.renderPill('SSL', 'success') :
vhost.ssl_status === 'expiring' ? this.renderPill('Expiring', 'warning') :
vhost.ssl_status === 'expired' ? this.renderPill('Expired', 'danger') :
this.renderPill('No SSL', 'muted')
]),
E('td', {}, [
vhost.waf_bypass ? this.renderPill('Bypass', 'danger') : this.renderPill('WAF', 'info')
]),
E('td', {}, [
vhost.active ? this.renderPill('Active', 'success') : this.renderPill('Inactive', 'muted'),
missingRoutes ? E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() { self.handleAddRoute(vhost.domain, vhost.backend_port); },
'style': 'margin-left:8px;padding:2px 8px;font-size:0.8em;'
}, _('+ Route')) : null
needsRoute ? E('button', {
'class': 'btn btn-sm',
'click': function() { self.handleAddRoute(v.domain, v.backend_port || 443); }
}, '') : null
])
]);
},
@ -155,98 +187,100 @@ return view.extend({
render: function(data) {
var self = this;
var vhosts = data.vhosts || [];
this.allVhosts = vhosts;
this.totalVhosts = data.total || vhosts.length;
this.currentOffset = data.offset || 0;
this.statusData = data;
// Sort by domain
vhosts.sort(function(a, b) {
return a.domain.localeCompare(b.domain);
});
// Quick stats from first page
var stats = {
active: vhosts.filter(function(v) { return v.active; }).length,
missing: vhosts.filter(function(v) { return !v.has_route_out || !v.has_route_in; }).length,
bypass: vhosts.filter(function(v) { return v.waf_bypass; }).length,
ssl: vhosts.filter(function(v) { return v.ssl_status === 'valid'; }).length
};
// Stats
var totalVhosts = vhosts.length;
var activeVhosts = vhosts.filter(function(v) { return v.active; }).length;
var missingRoutes = vhosts.filter(function(v) { return !v.has_route_out || !v.has_route_in; }).length;
var wafBypassed = vhosts.filter(function(v) { return v.waf_bypass; }).length;
var sslValid = vhosts.filter(function(v) { return v.ssl_status === 'valid'; }).length;
var content = E('div', { 'class': 'vhosts-checker' }, [
// Inline styles for dark theme compatibility
E('style', {}, [
'.vhosts-checker { font-family: system-ui, sans-serif; }',
'.vhosts-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 12px; }',
'.vhosts-title { font-size: 1.4em; font-weight: 600; display: flex; align-items: center; gap: 8px; }',
'.vhosts-stats { display: flex; gap: 16px; font-size: 0.9em; opacity: 0.8; }',
'.vhosts-stat { display: flex; align-items: center; gap: 4px; }',
'.services { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }',
'.service-badge { padding: 6px 12px; border-radius: 6px; display: flex; align-items: center; gap: 6px; font-size: 0.85em; background: rgba(255,255,255,0.1); }',
'.service-badge.ok { background: rgba(76, 175, 80, 0.2); }',
'.service-badge.err { background: rgba(244, 67, 54, 0.2); }',
'.vhosts-table { width: 100%; border-collapse: collapse; }',
'.vhosts-table th { text-align: left; padding: 8px; opacity: 0.7; font-weight: 500; border-bottom: 1px solid rgba(255,255,255,0.1); }',
'.vhost-row td { padding: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); }',
'.vhost-domain { color: #64b5f6; text-decoration: none; }',
'.vhost-domain:hover { text-decoration: underline; }',
'.vhost-status { display: flex; gap: 6px; }',
'.vhost-pill { font-size: 1.1em; cursor: help; opacity: 0.9; }',
'.vhost-pill.warn { opacity: 0.6; }',
'.load-more { text-align: center; padding: 16px; }',
'.btn-sm { padding: 4px 8px; font-size: 0.9em; }'
].join('\n')),
var vhostRows = vhosts.map(function(v) { return self.renderVhostRow(v); });
var content = E('div', { 'class': 'routes-status-page' }, [
// KISS Header
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' }, '🔀'),
_('Routes Status')
]),
E('p', { 'class': 'sh-page-subtitle' },
_('HAProxy vhosts and mitmproxy route configuration overview.'))
// Header
E('div', { 'class': 'vhosts-header' }, [
E('div', { 'class': 'vhosts-title' }, [
'🔀 ', _('VHosts Checker')
]),
E('div', { 'class': 'sh-header-meta' }, [
this.renderHeaderChip('🌐', _('Vhosts'), totalVhosts),
this.renderHeaderChip('✅', _('Active'), activeVhosts),
this.renderHeaderChip('⚠️', _('Missing Routes'), missingRoutes, missingRoutes > 0 ? 'warn' : ''),
this.renderHeaderChip('🛡️', _('WAF Bypass'), wafBypassed, wafBypassed > 0 ? 'warn' : ''),
this.renderHeaderChip('🔒', _('SSL Valid'), sslValid)
E('div', { 'class': 'vhosts-stats' }, [
E('span', { 'class': 'vhosts-stat' }, ['📊 ', this.totalVhosts, ' ', _('total')]),
E('span', { 'class': 'vhosts-stat' }, ['✅ ', stats.active, '+', ' ', _('active')]),
stats.missing > 0 ? E('span', { 'class': 'vhosts-stat' }, ['⚠️ ', stats.missing, ' ', _('missing routes')]) : null,
stats.bypass > 0 ? E('span', { 'class': 'vhosts-stat' }, ['🚫 ', stats.bypass, ' ', _('WAF bypass')]) : null
])
]),
// Service Status Cards
E('div', { 'class': 'sh-card-grid', 'style': 'display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin:20px 0;' }, [
E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [
E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [
E('span', { 'style': 'font-size:1.5em;' }, '⚖️'),
E('strong', {}, 'HAProxy')
]),
data.haproxy_running ?
this.renderPill('Running', 'success') :
this.renderPill('Stopped', 'danger')
// Service status badges
E('div', { 'class': 'services' }, [
E('span', { 'class': 'service-badge' + (data.haproxy_running ? ' ok' : ' err') }, [
data.haproxy_running ? '✅' : '❌', ' HAProxy'
]),
E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [
E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [
E('span', { 'style': 'font-size:1.5em;' }, '🔍'),
E('strong', {}, 'mitmproxy')
]),
data.mitmproxy_running ?
this.renderPill('Running', 'success') :
this.renderPill('Stopped', 'danger')
E('span', { 'class': 'service-badge' + (data.mitmproxy_running ? ' ok' : ' err') }, [
data.mitmproxy_running ? '✅' : '❌', ' mitmproxy'
]),
E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);' }, [
E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [
E('span', { 'style': 'font-size:1.5em;' }, '🖥️'),
E('strong', {}, _('Host IP'))
]),
E('code', { 'style': 'background:#f5f5f5;padding:4px 8px;border-radius:4px;' }, data.host_ip || '192.168.255.1')
])
]),
// Actions
E('div', { 'style': 'margin:20px 0;' }, [
E('span', { 'class': 'service-badge' }, ['🖥️ ', data.host_ip || '192.168.255.1']),
E('button', {
'class': 'cbi-button cbi-button-action',
'class': 'btn cbi-button-action',
'click': function() { self.handleSync(); },
'style': 'margin-right:10px;'
}, '🔄 ' + _('Sync Routes from HAProxy'))
'style': 'margin-left: auto;'
}, '🔄 ' + _('Sync'))
]),
// Vhosts Table
E('div', { 'class': 'sh-card', 'style': 'background:#fff;border-radius:8px;padding:16px;box-shadow:0 1px 3px rgba(0,0,0,0.1);overflow-x:auto;' }, [
E('h3', { 'style': 'margin:0 0 16px 0;' }, '🌐 ' + _('Virtual Hosts (%d)').format(totalVhosts)),
vhosts.length > 0 ?
E('table', { 'class': 'table', 'style': 'width:100%;border-collapse:collapse;' }, [
E('thead', {}, [
E('tr', { 'style': 'background:#f5f5f5;' }, [
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Domain')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Backend')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Routes')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('SSL')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('WAF')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Status'))
])
]),
E('tbody', {}, vhostRows)
]) :
E('p', { 'style': 'color:#666;text-align:center;padding:20px;' }, _('No virtual hosts configured.'))
])
// Table
vhosts.length > 0 ?
E('table', { 'class': 'vhosts-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Domain')),
E('th', {}, _('Status')),
E('th', { 'style': 'width: 50px;' }, '')
])
]),
E('tbody', { 'id': 'vhosts-tbody' }, vhosts.map(function(v) { return self.renderRow(v); }))
]) :
E('p', { 'style': 'text-align: center; opacity: 0.6; padding: 20px;' }, _('No vhosts found.')),
// Load more
this.totalVhosts > vhosts.length ? E('div', { 'class': 'load-more' }, [
E('button', {
'id': 'load-more-btn',
'class': 'btn cbi-button-neutral',
'click': function() { self.handleLoadMore(); }
}, [
'📥 ', _('Load More'),
' (',
E('span', { 'id': 'vhosts-count' }, vhosts.length + '/' + this.totalVhosts),
')'
]),
E('p', { 'id': 'load-spinner', 'class': 'spinning', 'style': 'display: none;' }, _('Loading...'))
]) : null
]);
return KissTheme.wrap([content], 'admin/status/vhosts-checker');

View File

@ -1,6 +1,7 @@
#!/bin/sh
# RPCD handler for Routes Status dashboard
# Shows HAProxy vhosts and mitmproxy route configuration status
# Optimized with pagination
. /usr/share/libubox/jshn.sh
@ -13,11 +14,24 @@ get_host_ip() {
uci -q get network.lan.ipaddr || echo "192.168.255.1"
}
# Main status method - optimized to fetch data once
# Main status method - optimized with pagination
method_status() {
local offset=0
local limit=50
# Read JSON input for pagination params
read -r input 2>/dev/null
if [ -n "$input" ]; then
json_load "$input" 2>/dev/null
json_get_var offset offset 2>/dev/null
json_get_var limit limit 2>/dev/null
fi
[ -z "$offset" ] && offset=0
[ -z "$limit" ] && limit=50
local host_ip=$(get_host_ip)
local haproxy_running=$(pgrep haproxy >/dev/null 2>&1 && echo "true" || echo "false")
local mitmproxy_running=$(pgrep -f mitmproxy >/dev/null 2>&1 && echo "true" || echo "false")
local haproxy_running=$(pgrep haproxy >/dev/null 2>&1 && echo "1" || echo "0")
local mitmproxy_running=$(pgrep -f mitmproxy >/dev/null 2>&1 && echo "1" || echo "0")
# Fetch vhost list once to temp file
local vhost_tmp="/tmp/vhosts_$$"
@ -27,41 +41,60 @@ method_status() {
touch "$vhost_tmp"
fi
# Count total vhosts
local total=$(wc -l < "$vhost_tmp" | tr -d ' ')
json_init
json_add_boolean haproxy_running "$haproxy_running"
json_add_boolean mitmproxy_running "$mitmproxy_running"
json_add_string host_ip "$host_ip"
json_add_int total "$total"
json_add_int offset "$offset"
json_add_int limit "$limit"
json_add_array vhosts
# Process vhost data line by line (using file redirection to avoid subshell)
# Process vhost data with pagination
local count=0
local processed=0
while IFS= read -r line; do
[ -z "$line" ] && continue
# Skip until offset
if [ "$count" -lt "$offset" ]; then
count=$((count + 1))
continue
fi
# Stop after limit
if [ "$processed" -ge "$limit" ]; then
break
fi
# Parse line: " domain.com -> backend_name [enabled] SSL ..."
local domain=$(echo "$line" | awk '{print $1}')
local backend=$(echo "$line" | awk '{print $3}')
local enabled=$(echo "$line" | grep -qF '[enabled]' && echo "true" || echo "false")
local enabled=$(echo "$line" | grep -qF '[enabled]' && echo "1" || echo "0")
[ -z "$domain" ] && continue
# Check mitmproxy routes (grep for domain in JSON)
local has_route_out="false"
local has_route_in="false"
if [ -f "$MITMPROXY_ROUTES" ] && grep -qF "\"$domain\"" "$MITMPROXY_ROUTES" 2>/dev/null; then
has_route_out="true"
# Check mitmproxy routes (use 1/0 for jshn booleans)
local has_route_out="0"
local has_route_in="0"
if [ -f "$MITMPROXY_ROUTES" ] && grep "$domain" "$MITMPROXY_ROUTES" >/dev/null 2>&1; then
has_route_out="1"
fi
if [ -f "$MITMPROXY_IN_ROUTES" ] && grep -qF "\"$domain\"" "$MITMPROXY_IN_ROUTES" 2>/dev/null; then
has_route_in="true"
if [ -f "$MITMPROXY_IN_ROUTES" ] && grep "$domain" "$MITMPROXY_IN_ROUTES" >/dev/null 2>&1; then
has_route_in="1"
fi
# Check SSL cert
local ssl_status="missing"
[ -f "$HAPROXY_CERTS/${domain}.pem" ] && ssl_status="valid"
# WAF bypass check
local waf_bypass="false"
[ "$backend" != "mitmproxy_inspector" ] && waf_bypass="true"
# WAF bypass check (1=bypassed, 0=waf active)
local waf_bypass="0"
[ "$backend" != "mitmproxy_inspector" ] && waf_bypass="1"
json_add_object ""
json_add_string domain "$domain"
@ -75,6 +108,9 @@ method_status() {
json_add_string route_target_in ""
json_add_boolean waf_bypass "$waf_bypass"
json_close_object
count=$((count + 1))
processed=$((processed + 1))
done < "$vhost_tmp"
json_close_array
@ -90,12 +126,12 @@ method_sync_routes() {
if [ -x /usr/sbin/mitmproxyctl ]; then
result=$(/usr/sbin/mitmproxyctl sync-routes 2>&1)
json_init
json_add_boolean success true
json_add_boolean success 1
json_add_string output "$result"
json_dump
else
json_init
json_add_boolean success false
json_add_boolean success 0
json_add_string error "mitmproxyctl not found"
json_dump
fi
@ -113,7 +149,7 @@ method_add_route() {
if [ -z "$domain" ] || [ -z "$port" ]; then
json_init
json_add_boolean success false
json_add_boolean success 0
json_add_string error "Missing domain or port parameter"
json_dump
return
@ -122,7 +158,7 @@ method_add_route() {
local host_ip=$(get_host_ip)
# Add route to both mitmproxy route files
local success="true"
local success="1"
local errors=""
for routes_file in "$MITMPROXY_ROUTES" "$MITMPROXY_IN_ROUTES"; do
@ -134,9 +170,7 @@ method_add_route() {
'. + {($d): [$h, $p]}' "$routes_file" > "$tmpfile" 2>/dev/null
else
# Fallback: manual JSON manipulation
# Remove trailing } and add new entry
sed 's/}$//' "$routes_file" > "$tmpfile"
# Check if file has content (not empty object)
if grep -q '": \[' "$routes_file"; then
printf ',\n "%s": ["%s", %s]\n}\n' "$domain" "$host_ip" "$port" >> "$tmpfile"
else
@ -148,14 +182,14 @@ method_add_route() {
mv "$tmpfile" "$routes_file"
else
rm -f "$tmpfile"
success="false"
success="0"
errors="$errors Failed to update $routes_file."
fi
fi
done
# Restart mitmproxy to apply changes
if [ "$success" = "true" ]; then
if [ "$success" = "1" ]; then
/etc/init.d/mitmproxy restart >/dev/null 2>&1
fi
@ -169,6 +203,8 @@ method_add_route() {
list_methods() {
json_init
json_add_object status
json_add_int offset 0
json_add_int limit 50
json_close_object
json_add_object sync_routes
json_close_object