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

386
README.md
View File

@ -1,194 +1,211 @@
# SecuBox - Security Suite for OpenWrt # SecuBox - Security Suite for OpenWrt
**Version:** 0.17.0 🎉 **First Public Release** **Version:** 0.18.0
**Last Updated:** 2026-01-31 **Last Updated:** 2026-03-04
**Status:** Production Ready **Status:** Production Ready
**Modules:** 38 LuCI Applications **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) [![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) [![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) [![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 ## 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) **Publisher:** [CyberMind.fr](https://cybermind.fr)
--- ---
## Three-Loop Security Architecture ## Four-Layer Architecture
SecuBox implements a **Three-Loop Security Model** for defense in depth:
``` ```
┌────────────────────────────────────────────────────────┐ +============================================================+
│ LOOP 3: STRATEGIC │ | LAYER 4: MESH NETWORKING |
│ (Hours → Days) │ | MirrorNet / P2P Hub / Services Mirrors |
│ ┌────────────────────────────────────────────────┐ │ | +--------------------------------------------------------+ |
│ │ LOOP 2: TACTICAL │ │ | | LAYER 3: AI GATEWAY | |
│ │ (Minutes → Hours) │ │ | | MCP Server / Threat Analyst / DNS Guard | |
│ │ ┌────────────────────────────────────────┐ │ │ | | +----------------------------------------------------+ | |
│ │ │ LOOP 1: OPERATIONAL │ │ │ | | | LAYER 2: TACTICAL | | |
│ │ │ (Milliseconds → Seconds) │ │ │ | | | CrowdSec / WAF / Scenarios | | |
│ │ │ DETECT → DECIDE → BLOCK │ │ │ | | | +------------------------------------------------+ | | |
│ │ └────────────────────────────────────────┘ │ │ | | | | LAYER 1: OPERATIONAL | | | |
│ │ CORRELATE → ANALYZE → ADAPT │ │ | | | | fw4 / DPI / Bouncer / HAProxy | | | |
│ └────────────────────────────────────────────────┘ │ | | | +------------------------------------------------+ | | |
│ AGGREGATE → ANTICIPATE → EVOLVE │ | | +----------------------------------------------------+ | |
└────────────────────────────────────────────────────────┘ | +--------------------------------------------------------+ |
+============================================================+
``` ```
| Loop | Function | SecuBox Modules | | Layer | Function | Time Scale | SecuBox Components |
|------|----------|-----------------| |-------|----------|------------|-------------------|
| **Loop 1** | Real-time blocking | nftables/fw4, netifyd DPI, CrowdSec Bouncer | | **Layer 1** | Real-time blocking | ms → seconds | nftables/fw4, netifyd DPI, CrowdSec Bouncer |
| **Loop 2** | Pattern correlation | CrowdSec Agent/LAPI, Scenarios, Netdata | | **Layer 2** | Pattern correlation | minutes → hours | CrowdSec Agent/LAPI, mitmproxy WAF, Scenarios |
| **Loop 3** | Threat intelligence | CrowdSec CAPI, Blocklists, P2P Hub (v0.18+) | | **Layer 3** | AI analysis | minutes → hours | MCP Server, Threat Analyst, DNS Guard |
| **Layer 4** | Mesh networking | continuous | P2P Hub, MirrorBox, Services Registry |
See [DOCS/THREE-LOOP-ARCHITECTURE.md](DOCS/THREE-LOOP-ARCHITECTURE.md) for detailed analysis.
--- ---
## SecuBox Modules ## Key Features
### SecuBox Core (5 modules) ### Security
| Module | Version | Description | - **CrowdSec Integration** — Real-time threat intelligence, CAPI enrollment, auto-banning
|--------|---------|-------------| - **mitmproxy WAF** — HTTPS inspection with CVE detection, sensitivity-based auto-ban
| **luci-app-secubox** | 0.7.1 | Central dashboard/Hub for all SecuBox modules | - **Deep Packet Inspection** — netifyd/nDPId protocol analysis
| **luci-app-secubox-portal** | 0.7.0 | Unified entry point with tabbed navigation | - **MAC Guardian** — WiFi MAC spoofing detection with CrowdSec integration
| **luci-app-secubox-admin** | 1.0.0 | Admin control center with appstore and monitoring | - **DNS Guard** — AI-powered DGA, tunneling, and anomaly detection
| **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 |
### Security & Threat Management (9 modules) ### AI Gateway
| Module | Version | Description | - **MCP Server** — Model Context Protocol for Claude Desktop integration
|--------|---------|-------------| - **Threat Analyst** — Autonomous AI agent for threat analysis and rule generation
| **luci-app-crowdsec-dashboard** | 0.7.0 | Real-time CrowdSec security monitoring | - **LocalAI** — Self-hosted LLM with model management
| **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 |
### Deep Packet Inspection (2 modules) ### Mesh Networking
| Module | Version | Description | - **P2P Hub** — Decentralized peer discovery with globe visualization
|--------|---------|-------------| - **MirrorBox** — Distributed service catalog with auto-sync
| **luci-app-ndpid** | 1.1.2 | nDPId deep packet inspection dashboard | - **App Store** — P2P package distribution across mesh peers
| **luci-app-secubox-netifyd** | 1.2.1 | netifyd DPI with real-time flow monitoring | - **Master Link** — Secure mesh onboarding with dynamic IPK generation
### Network & Connectivity (8 modules) ### Service Exposure
| Module | Version | Description | - **Punk Exposure** — Multi-channel service emancipation (Tor + DNS/SSL + Mesh)
|--------|---------|-------------| - **HAProxy** — Load balancer with webroot ACME, auto-SSL
| **luci-app-vhost-manager** | 0.5.0 | Nginx reverse proxy with Let's Encrypt SSL | - **Tor Shield** — .onion hidden services with split-routing
| **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) |
### Bandwidth & Traffic Management (2 modules) ### Media & Content
| Module | Version | Description | - **Jellyfin** — LXC media server with setup wizard
|--------|---------|-------------| - **Lyrion** — Music server with CIFS integration
| **luci-app-bandwidth-manager** | 0.5.0 | QoS rules, client quotas, SQM integration | - **Zigbee2MQTT** — LXC Alpine container for IoT
| **luci-app-traffic-shaper** | 0.4.0 | TC/CAKE traffic shaping | - **Domoticz** — Home automation with MQTT bridge
### Content & Web Platforms (5 modules) ---
| Module | Version | Description | ## SecuBox Modules (86 Total)
|--------|---------|-------------|
| **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 |
### AI/LLM & Analytics (4 modules) ### Core (6 modules)
| Module | Version | Description | | Module | Description |
|--------|---------|-------------| |--------|-------------|
| **luci-app-localai** | 0.1.0 | LocalAI LLM management | | luci-app-secubox | Central dashboard/Hub |
| **luci-app-ollama** | 0.1.0 | Ollama LLM management | | luci-app-secubox-portal | Unified entry point with tabs |
| **luci-app-glances** | 1.0.0 | Glances system monitoring | | luci-app-secubox-admin | Admin control center |
| **luci-app-netdata-dashboard** | 0.5.0 | Real-time Netdata monitoring | | 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 | | Module | Description |
|--------|---------|-------------| |--------|-------------|
| **luci-app-streamlit** | 1.0.0 | Streamlit Platform management | | luci-app-crowdsec-dashboard | CrowdSec monitoring |
| **luci-app-picobrew** | 1.0.0 | PicoBrew Server management | | 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 | | Module | Description |
|--------|---------|-------------| |--------|-------------|
| **luci-app-zigbee2mqtt** | 1.0.0 | Zigbee2MQTT docker management | | 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 ## Supported Architectures
### ARM 64-bit (AArch64) | Architecture | Targets | Example Devices |
|--------------|---------|-----------------|
| Target | 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 |
| `aarch64-cortex-a53` | ESPRESSObin, BananaPi R64 | | **MIPS** | mips-24kc, mipsel-24kc | TP-Link Archer, Xiaomi |
| `aarch64-cortex-a72` | MOCHAbin, Raspberry Pi 4, NanoPi R4S | | **x86** | x86-64 | PC, VMs, Docker, Proxmox |
| `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 |
--- ---
@ -196,12 +213,9 @@ See [DOCS/THREE-LOOP-ARCHITECTURE.md](DOCS/THREE-LOOP-ARCHITECTURE.md) for detai
### From Pre-built Packages ### From Pre-built Packages
Download from [GitHub Releases](https://github.com/CyberMind-FR/secubox-openwrt/releases):
```bash ```bash
opkg update opkg update
opkg install luci-app-secubox-portal_*.ipk opkg install luci-app-secubox-portal_*.ipk
opkg install luci-app-system-hub_*.ipk
opkg install luci-app-crowdsec-dashboard_*.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 git clone https://github.com/CyberMind-FR/secubox-openwrt.git secubox
# Build # Build
cd ~/openwrt-sdk/
make package/secubox/luci-app-secubox-portal/compile V=s make package/secubox/luci-app-secubox-portal/compile V=s
``` ```
### Add as OpenWrt Feed ### Add as Feed
Add to `feeds.conf.default`:
``` ```
src-git secubox https://github.com/CyberMind-FR/secubox-openwrt.git 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 includes an MCP server for AI integration:
secubox-openwrt/
├── package/secubox/ # All 38 SecuBox LuCI packages ```json
├── secubox-tools/ # Build tools and local SDK {
├── DOCS/ # Documentation "mcpServers": {
│ ├── THREE-LOOP-ARCHITECTURE.md # Security model analysis "secubox": {
│ ├── DEVELOPMENT-GUIDELINES.md "command": "ssh",
│ ├── QUICK-START.md "args": ["root@192.168.255.1", "/usr/bin/secubox-mcp"]
│ └── VALIDATION-GUIDE.md }
└── .github/workflows/ # CI/CD }
}
``` ```
--- **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`
## OpenWrt Compatibility
| Version | Status | Package Format |
|---------|--------|----------------|
| 25.x | Testing | `.apk` |
| 24.10.x | **Recommended** | `.ipk` |
| 23.05.x | Supported | `.ipk` |
--- ---
## Roadmap ## Roadmap
| Phase | Version | Status | Focus | | Version | Status | Focus |
|-------|---------|--------|-------| |---------|--------|-------|
| **Core Mesh** | v0.17 | ✅ Released | Loops 1+2 complete | | **v0.17** | Released | Core Mesh, 38 modules |
| **Service Mesh** | v0.18 | 🔵 In Progress | P2P Hub foundation | | **v0.18** | Current | P2P Hub, AI Gateway, 86 modules |
| **Intelligence Mesh** | v0.19 | ⚪ Planned | Full P2P intelligence | | **v0.19** | Planned | Full P2P intelligence |
| **AI Mesh** | v0.20 | ⚪ Planned | ML in Loop 2 | | **v1.0** | Planned | ANSSI certification |
| **Certification** | v1.0 | ⚪ Planned | ANSSI certification |
--- ---
## Links ## Links
* **Website**: [secubox.maegia.tv](https://secubox.maegia.tv) - **Website**: [secubox.maegia.tv](https://secubox.maegia.tv)
* **GitHub**: [github.com/CyberMind-FR/secubox-openwrt](https://github.com/CyberMind-FR/secubox-openwrt) - **GitHub**: [github.com/CyberMind-FR/secubox-openwrt](https://github.com/CyberMind-FR/secubox-openwrt)
* **Publisher**: [CyberMind.fr](https://cybermind.fr) - **Publisher**: [CyberMind.fr](https://cybermind.fr)
* **Issues**: [GitHub Issues](https://github.com/CyberMind-FR/secubox-openwrt/issues) - **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 ## Author
**Gandalf** - [CyberMind.fr](https://cybermind.fr) **Gandalf** - [CyberMind.fr](https://cybermind.fr)
**Ex Tenebris, Lux Securitas** **Ex Tenebris, Lux Securitas**
🇫🇷 Made with love in France Made in France

View File

@ -8,6 +8,7 @@
var callStatus = rpc.declare({ var callStatus = rpc.declare({
object: 'luci.routes-status', object: 'luci.routes-status',
method: 'status', method: 'status',
params: ['offset', 'limit'],
expect: { } expect: { }
}); });
@ -25,46 +26,36 @@ var callAddRoute = rpc.declare({
}); });
return view.extend({ return view.extend({
allVhosts: [],
currentOffset: 0,
pageSize: 50,
totalVhosts: 0,
statusData: null,
load: function() { load: function() {
return callStatus(); return callStatus(0, 50);
}, },
renderHeaderChip: function(icon, label, value, tone) { // Emoji-based status pill
var display = (value == null ? '—' : value).toString(); pill: function(emoji, label, ok) {
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'
};
return E('span', { 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;' 'class': 'vhost-pill' + (ok ? ' ok' : ' warn'),
}, text); 'title': label
}, emoji);
}, },
handleSync: function() { handleSync: function() {
ui.showModal(_('Syncing Routes...'), [ ui.showModal(_('Syncing...'), [
E('p', { 'class': 'spinning' }, _('Please wait...')) E('p', { 'class': 'spinning' }, _('Syncing routes from HAProxy...'))
]); ]);
callSyncRoutes().then(function(res) { callSyncRoutes().then(function(res) {
ui.hideModal(); ui.hideModal();
if (res && res.success) { if (res && res.success) {
ui.addNotification(null, E('p', {}, _('Routes synchronized successfully')), 'success'); ui.addNotification(null, E('p', {}, '✅ ' + _('Routes synchronized')), 'success');
location.reload(); location.reload();
} else { } 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'), [ ui.showModal(_('Add Route'), [
E('div', { 'class': 'cbi-section' }, [ 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('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('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); self.doAddRoute(domain, p);
} }
}, },
'style': 'margin-left:10px;' 'style': 'margin-left:8px;'
}, _('Add Route')) }, _('Add'))
]) ])
]); ]);
}, },
doAddRoute: function(domain, port) { doAddRoute: function(domain, port) {
ui.showModal(_('Adding Route...'), [ ui.showModal(_('Adding...'), [
E('p', { 'class': 'spinning' }, _('Please wait...')) E('p', { 'class': 'spinning' }, _('Adding route...'))
]); ]);
callAddRoute(domain, port).then(function(res) { callAddRoute(domain, port).then(function(res) {
ui.hideModal(); ui.hideModal();
if (res && res.success) { if (res && res.success) {
ui.addNotification(null, E('p', {}, _('Route added successfully')), 'success'); ui.addNotification(null, E('p', {}, '✅ ' + _('Route added')), 'success');
location.reload(); location.reload();
} else { } 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 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('td', {}, [
E('a', { E('a', {
'href': 'https://' + vhost.domain, 'href': 'https://' + v.domain,
'target': '_blank', 'target': '_blank',
'style': 'color:#1976D2;text-decoration:none;font-weight:500;' 'class': 'vhost-domain'
}, vhost.domain) }, v.domain)
]), ]),
E('td', {}, vhost.backend || '-'), // Status indicators (emoji-based)
E('td', {}, [ E('td', { 'class': 'vhost-status' }, [
vhost.has_route_out ? this.renderPill('OUT', 'success') : this.renderPill('OUT', 'warning'), // Routes
vhost.has_route_in ? this.renderPill('IN', 'success') : this.renderPill('IN', 'warning') 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', {}, [ E('td', {}, [
vhost.ssl_status === 'valid' ? this.renderPill('SSL', 'success') : needsRoute ? E('button', {
vhost.ssl_status === 'expiring' ? this.renderPill('Expiring', 'warning') : 'class': 'btn btn-sm',
vhost.ssl_status === 'expired' ? this.renderPill('Expired', 'danger') : 'click': function() { self.handleAddRoute(v.domain, v.backend_port || 443); }
this.renderPill('No SSL', 'muted') }, '') : null
]),
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
]) ])
]); ]);
}, },
@ -155,98 +187,100 @@ return view.extend({
render: function(data) { render: function(data) {
var self = this; var self = this;
var vhosts = data.vhosts || []; var vhosts = data.vhosts || [];
this.allVhosts = vhosts;
this.totalVhosts = data.total || vhosts.length;
this.currentOffset = data.offset || 0;
this.statusData = data;
// Sort by domain // Quick stats from first page
vhosts.sort(function(a, b) { var stats = {
return a.domain.localeCompare(b.domain); 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 content = E('div', { 'class': 'vhosts-checker' }, [
var totalVhosts = vhosts.length; // Inline styles for dark theme compatibility
var activeVhosts = vhosts.filter(function(v) { return v.active; }).length; E('style', {}, [
var missingRoutes = vhosts.filter(function(v) { return !v.has_route_out || !v.has_route_in; }).length; '.vhosts-checker { font-family: system-ui, sans-serif; }',
var wafBypassed = vhosts.filter(function(v) { return v.waf_bypass; }).length; '.vhosts-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 12px; }',
var sslValid = vhosts.filter(function(v) { return v.ssl_status === 'valid'; }).length; '.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); }); // Header
E('div', { 'class': 'vhosts-header' }, [
var content = E('div', { 'class': 'routes-status-page' }, [ E('div', { 'class': 'vhosts-title' }, [
// KISS Header '🔀 ', _('VHosts Checker')
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.'))
]), ]),
E('div', { 'class': 'sh-header-meta' }, [ E('div', { 'class': 'vhosts-stats' }, [
this.renderHeaderChip('🌐', _('Vhosts'), totalVhosts), E('span', { 'class': 'vhosts-stat' }, ['📊 ', this.totalVhosts, ' ', _('total')]),
this.renderHeaderChip('✅', _('Active'), activeVhosts), E('span', { 'class': 'vhosts-stat' }, ['✅ ', stats.active, '+', ' ', _('active')]),
this.renderHeaderChip('⚠️', _('Missing Routes'), missingRoutes, missingRoutes > 0 ? 'warn' : ''), stats.missing > 0 ? E('span', { 'class': 'vhosts-stat' }, ['⚠️ ', stats.missing, ' ', _('missing routes')]) : null,
this.renderHeaderChip('🛡️', _('WAF Bypass'), wafBypassed, wafBypassed > 0 ? 'warn' : ''), stats.bypass > 0 ? E('span', { 'class': 'vhosts-stat' }, ['🚫 ', stats.bypass, ' ', _('WAF bypass')]) : null
this.renderHeaderChip('🔒', _('SSL Valid'), sslValid)
]) ])
]), ]),
// Service Status Cards // Service status badges
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': 'services' }, [
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('span', { 'class': 'service-badge' + (data.haproxy_running ? ' ok' : ' err') }, [
E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [ data.haproxy_running ? '✅' : '❌', ' HAProxy'
E('span', { 'style': 'font-size:1.5em;' }, '⚖️'),
E('strong', {}, 'HAProxy')
]),
data.haproxy_running ?
this.renderPill('Running', 'success') :
this.renderPill('Stopped', 'danger')
]), ]),
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('span', { 'class': 'service-badge' + (data.mitmproxy_running ? ' ok' : ' err') }, [
E('div', { 'style': 'display:flex;align-items:center;gap:8px;margin-bottom:8px;' }, [ data.mitmproxy_running ? '✅' : '❌', ' mitmproxy'
E('span', { 'style': 'font-size:1.5em;' }, '🔍'),
E('strong', {}, 'mitmproxy')
]),
data.mitmproxy_running ?
this.renderPill('Running', 'success') :
this.renderPill('Stopped', 'danger')
]), ]),
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('span', { 'class': 'service-badge' }, ['🖥️ ', data.host_ip || '192.168.255.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('button', { E('button', {
'class': 'cbi-button cbi-button-action', 'class': 'btn cbi-button-action',
'click': function() { self.handleSync(); }, 'click': function() { self.handleSync(); },
'style': 'margin-right:10px;' 'style': 'margin-left: auto;'
}, '🔄 ' + _('Sync Routes from HAProxy')) }, '🔄 ' + _('Sync'))
]), ]),
// Vhosts Table // 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;' }, [ vhosts.length > 0 ?
E('h3', { 'style': 'margin:0 0 16px 0;' }, '🌐 ' + _('Virtual Hosts (%d)').format(totalVhosts)), E('table', { 'class': 'vhosts-table' }, [
vhosts.length > 0 ? E('thead', {}, [
E('table', { 'class': 'table', 'style': 'width:100%;border-collapse:collapse;' }, [ E('tr', {}, [
E('thead', {}, [ E('th', {}, _('Domain')),
E('tr', { 'style': 'background:#f5f5f5;' }, [ E('th', {}, _('Status')),
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Domain')), E('th', { 'style': 'width: 50px;' }, '')
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('tbody', { 'id': 'vhosts-tbody' }, vhosts.map(function(v) { return self.renderRow(v); }))
E('th', { 'style': 'padding:10px;text-align:left;' }, _('WAF')), ]) :
E('th', { 'style': 'padding:10px;text-align:left;' }, _('Status')) E('p', { 'style': 'text-align: center; opacity: 0.6; padding: 20px;' }, _('No vhosts found.')),
])
]), // Load more
E('tbody', {}, vhostRows) this.totalVhosts > vhosts.length ? E('div', { 'class': 'load-more' }, [
]) : E('button', {
E('p', { 'style': 'color:#666;text-align:center;padding:20px;' }, _('No virtual hosts configured.')) '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'); return KissTheme.wrap([content], 'admin/status/vhosts-checker');

View File

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