feat(v0.23.0): Matrix homeserver, SaaS Relay CDN caching, Media Hub dashboard
Matrix Homeserver (Conduit): - E2EE mesh messaging using Conduit v0.10.12 in LXC container - matrixctl CLI: install/uninstall, user/room management, federation - luci-app-matrix: status cards, user form, emancipate, mesh publish - RPCD backend with 17 methods - Identity (DID) integration and P2P mesh publication SaaS Relay CDN Caching & Session Replay: - CDN cache profiles: minimal, gandalf (default), aggressive - Session replay modes: shared, per_user, master - saasctl cache/session commands for management - Enhanced mitmproxy addon (415 lines) with response caching Media Services Hub Dashboard: - Unified dashboard at /admin/services/media-hub - Category-organized cards (streaming, conferencing, apps, etc.) - Service status indicators with start/stop/restart controls - RPCD backend querying 8 media services Also includes: - HexoJS static upload workflow and multi-user auth - Jitsi config.js Promise handling fix - Feed package updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b6747c197e
commit
58220065b5
@ -1,6 +1,6 @@
|
||||
# SecuBox UI & Theme History
|
||||
|
||||
_Last updated: 2026-02-19_
|
||||
_Last updated: 2026-02-20_
|
||||
|
||||
1. **Unified Dashboard Refresh (2025-12-20)**
|
||||
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
|
||||
@ -2490,3 +2490,110 @@ git checkout HEAD -- index.html
|
||||
- UCI config section: `config recording 'recording'` with enabled/format/retention_days
|
||||
- Menu entry: Services → VoIP PBX → Recordings
|
||||
- Note: OVH SIP trunk registration requires correct password from OVH Manager
|
||||
|
||||
41. **Matrix Homeserver Integration (2026-02-19)**
|
||||
- Created `secubox-app-matrix` package for Conduit Matrix server:
|
||||
- Lightweight Rust-based homeserver (~15MB binary, ~500MB RAM)
|
||||
- LXC Debian Bookworm container with pre-built ARM64/x86_64 binaries
|
||||
- E2EE messaging with federation support
|
||||
- RocksDB database for performance
|
||||
- New `matrixctl` CLI commands:
|
||||
- `install`, `uninstall`, `update` - Container lifecycle
|
||||
- `start`, `stop`, `restart`, `status` - Service control
|
||||
- `user add/del/passwd/list` - User management
|
||||
- `room list/create/delete` - Room management
|
||||
- `federation test/status` - Federation testing
|
||||
- `configure-haproxy`, `emancipate <domain>` - Punk Exposure
|
||||
- `identity link/unlink` - DID integration
|
||||
- `mesh publish/unpublish` - P2P service registry
|
||||
- `backup`, `restore` - Data persistence
|
||||
- Created `luci-app-matrix` LuCI dashboard:
|
||||
- Install wizard for new deployments
|
||||
- Status card with running state, version, features
|
||||
- Service controls (Start/Stop/Update/Uninstall)
|
||||
- User management form
|
||||
- Emancipate form for public exposure
|
||||
- Identity integration section (DID linking)
|
||||
- Mesh publication toggle
|
||||
- Logs viewer with refresh
|
||||
- RPCD methods (18 total): status, logs, start, stop, install, uninstall, update,
|
||||
emancipate, configure_haproxy, user_add, user_del, federation_status,
|
||||
identity_status, identity_link, identity_unlink, mesh_status, mesh_publish, mesh_unpublish
|
||||
- UCI config sections: main, server, federation, admin, database, network, identity, mesh
|
||||
- v1.0.0 roadmap: Matrix integration complements VoIP/Jabber for full mesh communication stack
|
||||
- Files created:
|
||||
- `package/secubox/secubox-app-matrix/` (Makefile, UCI, init.d, matrixctl)
|
||||
- `package/secubox/luci-app-matrix/` (RPCD, ACL, menu, overview.js, api.js)
|
||||
|
||||
25. **HexoJS KISS Static Upload & Multi-User Authentication (2026-02-20)**
|
||||
- Added multi-user/multi-instance authentication:
|
||||
- HAProxy Basic Auth integration with apr1 password hashing
|
||||
- `hexoctl user add/del/passwd/list/grant/revoke` commands
|
||||
- `hexoctl auth enable/disable/status/haproxy` commands
|
||||
- UCI config sections for users and per-instance auth
|
||||
- KISS Static Upload workflow (no Hexo build process):
|
||||
- `hexoctl static create <name>` - Create static-only site
|
||||
- `hexoctl static upload <file> [inst]` - Upload HTML/CSS/JS directly
|
||||
- `hexoctl static publish [inst]` - Copy to /www/ for uhttpd serving
|
||||
- `hexoctl static quick <file> [inst]` - One-command upload + publish
|
||||
- `hexoctl static list [inst]` - List static files
|
||||
- `hexoctl static serve [inst]` - Python/busybox httpd server
|
||||
- `hexoctl static delete <name>` - Delete static instance
|
||||
- Goal: Fast publishing experiment (KISSS) for HTML files without Node.js/Hexo build
|
||||
- Tested and verified on router with immediate uhttpd serving
|
||||
|
||||
26. **SaaS Relay CDN Caching & Session Replay (2026-02-20)**
|
||||
- Enhanced `secubox-app-saas-relay` with CDN caching layer and multi-user session replay
|
||||
- CDN Cache features:
|
||||
- Configurable cache profiles: minimal, gandalf (default), aggressive
|
||||
- Profile-based caching rules (content types, TTL, max size, exclude patterns)
|
||||
- File-based cache storage with metadata for expiry tracking
|
||||
- Cache-Control header respect (max-age, no-store, private)
|
||||
- `X-SaaSRelay-Cache: HIT/MISS` header for debugging
|
||||
- Session Replay features:
|
||||
- Three modes: shared (default), per_user, master
|
||||
- Shared mode: All SecuBox users share same session cookies
|
||||
- Per-user mode: Each user gets their own session storage
|
||||
- Master mode: One user (admin) authenticates, others replay their session
|
||||
- New CLI commands:
|
||||
- `saasctl cache {status|clear|profile|enable|disable}` - Cache management
|
||||
- `saasctl session {status|mode|master|enable|disable}` - Session management
|
||||
- Enhanced mitmproxy addon (415 lines) with:
|
||||
- Response caching before network request
|
||||
- Cache key generation with SHA-256 URL hashing
|
||||
- Per-user session file storage with fallback to master
|
||||
- Activity logging with emoji indicators
|
||||
- UCI config sections added: cache, cache_profile (3), session_replay
|
||||
- Config JSON export for container: config.json + services.json
|
||||
|
||||
27. **Matrix Homeserver (Conduit) Integration (2026-02-20)**
|
||||
- E2EE mesh messaging using Conduit Matrix homeserver (v0.10.12)
|
||||
- `secubox-app-matrix` package with LXC container management:
|
||||
- Pre-built ARM64 Conduit binary from GitLab artifacts
|
||||
- Debian Bookworm base, RocksDB backend
|
||||
- 512MB RAM limit, persistent data in /srv/matrix
|
||||
- `matrixctl` CLI tool (1279 lines):
|
||||
- Container: install, uninstall, update, check, shell
|
||||
- Service: start, stop, restart, status, logs
|
||||
- Users: add, del, passwd, list
|
||||
- Rooms: list, create, delete
|
||||
- Federation: test, status
|
||||
- Exposure: configure-haproxy, emancipate
|
||||
- Identity: link, unlink, status (DID integration)
|
||||
- Mesh: publish, unpublish
|
||||
- Backup: backup, restore
|
||||
- `luci-app-matrix` dashboard:
|
||||
- Install wizard for first-time setup
|
||||
- Status cards with feature badges
|
||||
- Service controls
|
||||
- User management form
|
||||
- Emancipate (public exposure) form
|
||||
- Identity/DID linking section
|
||||
- P2P mesh publication toggle
|
||||
- Logs viewer with refresh
|
||||
- RPCD methods (17 total): status, logs, start, stop, install, uninstall, update,
|
||||
emancipate, configure_haproxy, user_add, user_del, federation_status,
|
||||
identity_status, identity_link, identity_unlink, mesh_status, mesh_publish, mesh_unpublish
|
||||
- UCI config sections: main, server, federation, admin, database, network, identity, mesh
|
||||
- Matrix API responding with v1.1-v1.12 support
|
||||
- Files: `package/secubox/secubox-app-matrix/`, `package/secubox/luci-app-matrix/`
|
||||
|
||||
@ -188,8 +188,8 @@ All cloud providers are **opt-in**. Offline resilience: local tier always active
|
||||
- [x] Config Advisor (ANSSI prep) — Done 2026-02-07
|
||||
- [ ] P2P Mesh Intelligence
|
||||
- [ ] Factory auto-provisioning
|
||||
- [ ] VoIP integration
|
||||
- [ ] Matrix integration
|
||||
- [x] VoIP integration — Done 2026-02-19
|
||||
- [x] Matrix integration — Done 2026-02-19
|
||||
|
||||
### v1.1+ — Extended Mesh
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Work In Progress (Claude)
|
||||
|
||||
_Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
|
||||
_Last updated: 2026-02-20 (v0.23.0 - Matrix + SaaS Relay + Media Hub)_
|
||||
|
||||
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
||||
|
||||
@ -62,6 +62,52 @@ _Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
|
||||
- Gossip-based exposure config sync via secubox-p2p
|
||||
- Created `luci-app-vortex-dns` dashboard
|
||||
|
||||
### Just Completed (2026-02-20)
|
||||
|
||||
- **Matrix Homeserver (Conduit)** — DONE (2026-02-20)
|
||||
- E2EE mesh messaging server using Conduit Matrix homeserver
|
||||
- LXC container with pre-built ARM64 Conduit binary (0.10.12)
|
||||
- `matrixctl` CLI (1279 lines): install/uninstall/update, user management, rooms, federation
|
||||
- `luci-app-matrix` dashboard with:
|
||||
- Install wizard, status cards, feature badges
|
||||
- Service controls (Start/Stop/Update/Uninstall)
|
||||
- User management form
|
||||
- Emancipate (public exposure) with HAProxy + SSL
|
||||
- Identity (DID) integration section
|
||||
- P2P mesh publication toggle
|
||||
- Logs viewer
|
||||
- RPCD backend with 17 methods
|
||||
- UCI config: main, server, federation, admin, database, network, identity, mesh
|
||||
- Tested and verified on router (all checks pass, API responding)
|
||||
|
||||
- **SaaS Relay CDN Caching & Session Replay** — DONE (2026-02-20)
|
||||
- CDN cache with configurable profiles: minimal, gandalf, aggressive
|
||||
- Session replay modes: shared (default), per_user, master
|
||||
- New CLI commands: `saasctl cache {status|clear|profile|enable|disable}`
|
||||
- New CLI commands: `saasctl session {status|mode|master|enable|disable}`
|
||||
- Enhanced mitmproxy addon (415 lines) with response caching
|
||||
- UCI config sections: cache, cache_profile (3), session_replay
|
||||
- Config JSON export: config.json + services.json
|
||||
|
||||
- **Media Services Hub Dashboard** — DONE (2026-02-20)
|
||||
- Unified dashboard for all SecuBox media services at `/admin/services/media-hub`
|
||||
- Category-organized cards: streaming, conferencing, apps, display, social, monitoring
|
||||
- Service cards with status indicators, start/stop/restart controls
|
||||
- RPCD backend querying 8 media services (Jellyfin, Lyrion, Jitsi, PeerTube, etc.)
|
||||
- Files: `luci-app-media-hub` package
|
||||
|
||||
- **HexoJS KISS Static Upload** — DONE (2026-02-20)
|
||||
- Multi-user/multi-instance authentication with HAProxy Basic Auth
|
||||
- UCI config for users, auth, and instances
|
||||
- `hexoctl user add/del/passwd/grant/revoke` commands
|
||||
- `hexoctl auth enable/disable/status/haproxy` commands
|
||||
- KISS static upload workflow (no Hexo build required):
|
||||
- `hexoctl static create <name>` - Create static-only site
|
||||
- `hexoctl static upload <file>` - Upload HTML/CSS/JS directly
|
||||
- `hexoctl static publish` - Copy to /www/ for immediate serving
|
||||
- `hexoctl static quick <file>` - One-command upload + publish
|
||||
- Tested and verified on router
|
||||
|
||||
### Just Completed (2026-02-19)
|
||||
|
||||
- **WAF VoIP/XMPP Security Filters** — DONE (2026-02-19)
|
||||
@ -94,6 +140,16 @@ _Last updated: 2026-02-19 (v0.22.0 - VoIP + Jabber Integration)_
|
||||
- Updated luci.jabber RPCD with 9 new VoIP methods
|
||||
- UCI config sections: jingle, sms, voicemail
|
||||
|
||||
- **Matrix Homeserver Integration** — DONE (2026-02-19)
|
||||
- Created `secubox-app-matrix` package with Conduit Matrix server in LXC
|
||||
- Pre-built ARM64/x86_64 binaries (~15MB), ~500MB RAM footprint
|
||||
- `matrixctl` CLI: install/start/stop, user management, federation, emancipate
|
||||
- HAProxy integration, identity linking (DID), P2P mesh publication
|
||||
- Created `luci-app-matrix` dashboard with KISS theme
|
||||
- Install wizard, status cards, user form, emancipate form, logs viewer
|
||||
- RPCD backend with 18 methods
|
||||
- Completes v1.0.0 roadmap: Matrix + VoIP + Jabber = full mesh communication stack
|
||||
|
||||
### Just Completed (2026-02-17)
|
||||
|
||||
- **PeerTube yt-dlp Video Import** — DONE (2026-02-17)
|
||||
|
||||
@ -353,7 +353,29 @@
|
||||
"Bash(gh repo view:*)",
|
||||
"Bash(gh repo edit:*)",
|
||||
"Bash(gh auth:*)",
|
||||
"WebFetch(domain:cloud.gk2.secubox.in)"
|
||||
"WebFetch(domain:cloud.gk2.secubox.in)",
|
||||
"WebFetch(domain:docs.conduit.rs)",
|
||||
"Bash(' -f1\\)\n local nc_ok=$\\(echo \"\"$nc_stats\"\" | cut -d')",
|
||||
"Bash(' -f2\\)\n local nc_fail=$\\(echo \"\"$nc_stats\"\" | cut -d')",
|
||||
"Bash(' -f3\\)\n \n local pt_last=$\\(echo \"\"$pt_stats\"\" | cut -d')",
|
||||
"Bash(' -f1\\)\n local pt_ok=$\\(echo \"\"$pt_stats\"\" | cut -d')",
|
||||
"Bash(' -f2\\)\n local pt_fail=$\\(echo \"\"$pt_stats\"\" | cut -d')",
|
||||
"Bash(' -f3\\)\n \n local jb_last=$\\(echo \"\"$jb_stats\"\" | cut -d')",
|
||||
"Bash(' -f1\\)\n local jb_ok=$\\(echo \"\"$jb_stats\"\" | cut -d')",
|
||||
"Bash(' -f2\\)\n local jb_fail=$\\(echo \"\"$jb_stats\"\" | cut -d')",
|
||||
"Bash(' -f3\\)\n \n local mx_last=$\\(echo \"\"$mx_stats\"\" | cut -d')",
|
||||
"Bash(' -f1\\)\n local mx_ok=$\\(echo \"\"$mx_stats\"\" | cut -d')",
|
||||
"Bash(' -f2\\)\n local mx_fail=$\\(echo \"\"$mx_stats\"\" | cut -d')",
|
||||
"Bash(' -f3\\)\n \n local em_last=$\\(echo \"\"$em_stats\"\" | cut -d')",
|
||||
"Bash(' -f1\\)\n local em_ok=$\\(echo \"\"$em_stats\"\" | cut -d')",
|
||||
"Bash(' -f2\\)\n local em_fail=$\\(echo \"\"$em_stats\"\" | cut -d')",
|
||||
"Bash(' -f3\\)\n \n # Calculate totals\n local total_success=$\\(\\(${nc_ok:-0} + ${pt_ok:-0} + ${jb_ok:-0} + ${mx_ok:-0} + ${em_ok:-0}\\)\\)\n local total_failure=$\\(\\(${nc_fail:-0} + ${pt_fail:-0} + ${jb_fail:-0} + ${mx_fail:-0} + ${em_fail:-0}\\)\\)\n \n # Find most recent login\n local last_login=\"\"\"\"\n for ts in \"\"$nc_last\"\" \"\"$pt_last\"\" \"\"$jb_last\"\" \"\"$mx_last\"\" \"\"$em_last\"\"; do\n [ -n \"\"$ts\"\" ] && last_login=\"\"$ts\"\"\n done\n \n # Output JSON\n cat << EOFSTATS\n{\n \"\"last_login\"\": \"\"${last_login:-never}\"\",\n \"\"total_success\"\": $total_success,\n \"\"total_failure\"\": $total_failure,\n \"\"services\"\": {\n \"\"nextcloud\"\": {\"\"last\"\": \"\"${nc_last:-}\"\", \"\"success\"\": ${nc_ok:-0}, \"\"failure\"\": ${nc_fail:-0}},\n \"\"peertube\"\": {\"\"last\"\": \"\"${pt_last:-}\"\", \"\"success\"\": ${pt_ok:-0}, \"\"failure\"\": ${pt_fail:-0}},\n \"\"jabber\"\": {\"\"last\"\": \"\"${jb_last:-}\"\", \"\"success\"\": ${jb_ok:-0}, \"\"failure\"\": ${jb_fail:-0}},\n \"\"matrix\"\": {\"\"last\"\": \"\"${mx_last:-}\"\", \"\"success\"\": ${mx_ok:-0}, \"\"failure\"\": ${mx_fail:-0}},\n \"\"email\"\": {\"\"last\"\": \"\"${em_last:-}\"\", \"\"success\"\": ${em_ok:-0}, \"\"failure\"\": ${em_fail:-0}}\n }\n}\nEOFSTATS\n}\nEOF\nchmod +x /usr/lib/secubox/users-login-stats.sh\necho \"\"Login stats library created\"\"')",
|
||||
"WebFetch(domain:cdnjs.com)",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(npm run build:*)",
|
||||
"Bash(npx gulp browserify:*)",
|
||||
"Bash(npx terser:*)",
|
||||
"Bash(read)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
18
CLAUDE.md
18
CLAUDE.md
@ -13,6 +13,24 @@
|
||||
|
||||
**When the user says "continue" or "next"**, consult WIP.md "Next Up" and TODO.md "Open" to pick the next task. When completing work, update these files to keep them current. New features and fixes must be appended to HISTORY.md with the date.
|
||||
|
||||
## Security Policies
|
||||
|
||||
### WAF Bypass Prohibition
|
||||
- **NEVER set `waf_bypass='1'` on any HAProxy vhost** — All traffic MUST pass through the mitmproxy WAF for inspection
|
||||
- When adding new services/domains to HAProxy, route through `mitmproxy_inspector` backend and add the upstream route to `/srv/mitmproxy/haproxy-routes.json`
|
||||
- If a service needs special handling (WebSocket, long connections), configure mitmproxy to properly forward the traffic rather than bypassing WAF
|
||||
- Use `mitmproxyctl sync-routes` to regenerate routes from HAProxy backends, then manually add any missing routes for backends that don't have standard server entries
|
||||
|
||||
### Mitmproxy Route Configuration
|
||||
When adding a new service that routes through HAProxy → mitmproxy:
|
||||
1. Add vhost: `haproxyctl vhost add <domain>`
|
||||
2. Backend will default to `mitmproxy_inspector` (correct)
|
||||
3. Add route to mitmproxy: Edit `/srv/mitmproxy/haproxy-routes.json` and `/srv/mitmproxy-in/haproxy-routes.json`:
|
||||
```json
|
||||
"domain.example.com": ["127.0.0.1", PORT]
|
||||
```
|
||||
4. Restart mitmproxy: `/etc/init.d/mitmproxy restart`
|
||||
|
||||
## OpenWrt Shell Scripting Guidelines
|
||||
|
||||
### Process Detection
|
||||
|
||||
24
luci-app-secubox-users/Makefile
Normal file
24
luci-app-secubox-users/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-secubox-users
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI SecuBox User Management
|
||||
LUCI_DEPENDS:=+secubox-core-users
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/luci-app-secubox-users/install
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-users $(1)/usr/libexec/rpcd/luci.secubox-users
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-secubox-users.json $(1)/usr/share/rpcd/acl.d/luci-app-secubox-users.json
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-secubox-users.json $(1)/usr/share/luci/menu.d/luci-app-secubox-users.json
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/secubox-users
|
||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-users/overview.js $(1)/www/luci-static/resources/view/secubox-users/overview.js
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-secubox-users))
|
||||
Binary file not shown.
@ -0,0 +1,284 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require ui';
|
||||
'require rpc';
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.secubox-users',
|
||||
method: 'status',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callUsers = rpc.declare({
|
||||
object: 'luci.secubox-users',
|
||||
method: 'users',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callAddUser = rpc.declare({
|
||||
object: 'luci.secubox-users',
|
||||
method: 'add',
|
||||
params: ['username', 'password', 'services'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callDeleteUser = rpc.declare({
|
||||
object: 'luci.secubox-users',
|
||||
method: 'delete',
|
||||
params: ['username'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
var callPasswd = rpc.declare({
|
||||
object: 'luci.secubox-users',
|
||||
method: 'passwd',
|
||||
params: ['username', 'password'],
|
||||
expect: { }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callStatus(),
|
||||
callUsers()
|
||||
]);
|
||||
},
|
||||
|
||||
renderServiceBadge: function(name, running) {
|
||||
var color = running ? '#4CAF50' : '#9e9e9e';
|
||||
return E('span', {
|
||||
'style': 'display:inline-block;padding:2px 8px;margin:2px;border-radius:3px;color:#fff;background:' + color + ';font-size:0.85em;'
|
||||
}, name);
|
||||
},
|
||||
|
||||
renderUserRow: function(user) {
|
||||
var self = this;
|
||||
var services = (user.services || []).map(function(s) {
|
||||
return E('span', {
|
||||
'style': 'display:inline-block;padding:1px 6px;margin:1px;border-radius:3px;background:#e3f2fd;font-size:0.8em;'
|
||||
}, s);
|
||||
});
|
||||
|
||||
return E('tr', {}, [
|
||||
E('td', {}, user.username),
|
||||
E('td', {}, user.email),
|
||||
E('td', {}, services),
|
||||
E('td', {}, user.enabled === '1' ? 'Yes' : 'No'),
|
||||
E('td', {}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button',
|
||||
'click': function() { self.handlePasswd(user.username); },
|
||||
'style': 'margin-right:5px;'
|
||||
}, _('Password')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'click': function() { self.handleDelete(user.username); }
|
||||
}, _('Delete'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleAdd: function() {
|
||||
var self = this;
|
||||
|
||||
ui.showModal(_('Add User'), [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Username')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'text', 'id': 'new-username', 'style': 'width:200px;' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Password')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'password', 'id': 'new-password', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Services')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('label', {}, [E('input', { 'type': 'checkbox', 'id': 'svc-nextcloud', 'checked': true }), ' Nextcloud']),
|
||||
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-peertube', 'checked': true }), ' PeerTube']),
|
||||
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-jabber', 'checked': true }), ' Jabber']),
|
||||
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-matrix', 'checked': true }), ' Matrix']),
|
||||
E('label', { 'style': 'margin-left:10px;' }, [E('input', { 'type': 'checkbox', 'id': 'svc-email', 'checked': true }), ' Email'])
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.hideModal
|
||||
}, _('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': function() {
|
||||
var username = document.getElementById('new-username').value;
|
||||
var password = document.getElementById('new-password').value;
|
||||
var services = [];
|
||||
if (document.getElementById('svc-nextcloud').checked) services.push('nextcloud');
|
||||
if (document.getElementById('svc-peertube').checked) services.push('peertube');
|
||||
if (document.getElementById('svc-jabber').checked) services.push('jabber');
|
||||
if (document.getElementById('svc-matrix').checked) services.push('matrix');
|
||||
if (document.getElementById('svc-email').checked) services.push('email');
|
||||
|
||||
if (!username) {
|
||||
ui.addNotification(null, E('p', {}, _('Username required')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Creating User...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
||||
]);
|
||||
|
||||
callAddUser(username, password, services.join(',')).then(function(res) {
|
||||
ui.hideModal();
|
||||
if (res && res.success) {
|
||||
ui.showModal(_('User Created'), [
|
||||
E('div', { 'style': 'padding:20px;' }, [
|
||||
E('p', {}, _('User created successfully!')),
|
||||
E('table', { 'style': 'margin:15px 0;' }, [
|
||||
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Username:')), E('td', { 'style': 'padding:5px;' }, res.username)]),
|
||||
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Password:')), E('td', { 'style': 'padding:5px;font-family:monospace;background:#f5f5f5;' }, res.password)]),
|
||||
E('tr', {}, [E('td', { 'style': 'padding:5px;font-weight:bold;' }, _('Email:')), E('td', { 'style': 'padding:5px;' }, res.email)])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'btn cbi-button-positive', 'click': function() { ui.hideModal(); location.reload(); } }, _('OK'))
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
'style': 'margin-left:10px;'
|
||||
}, _('Create User'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleDelete: function(username) {
|
||||
var self = this;
|
||||
|
||||
if (!confirm(_('Delete user "%s" from all services?').format(username))) {
|
||||
return;
|
||||
}
|
||||
|
||||
ui.showModal(_('Deleting...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
||||
]);
|
||||
|
||||
callDeleteUser(username).then(function(res) {
|
||||
ui.hideModal();
|
||||
if (res && res.success) {
|
||||
ui.addNotification(null, E('p', {}, _('User deleted')), 'success');
|
||||
location.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Error: ') + (res.error || 'Unknown error')), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handlePasswd: function(username) {
|
||||
var self = this;
|
||||
|
||||
ui.showModal(_('Change Password'), [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('p', {}, _('Change password for: %s').format(username)),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('New Password')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('input', { 'type': 'password', 'id': 'new-passwd', 'placeholder': _('Leave empty to generate'), 'style': 'width:200px;' })
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': function() {
|
||||
var password = document.getElementById('new-passwd').value;
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Updating...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
||||
]);
|
||||
|
||||
callPasswd(username, password).then(function(res) {
|
||||
ui.hideModal();
|
||||
if (res && res.success) {
|
||||
ui.showModal(_('Password Updated'), [
|
||||
E('div', { 'style': 'padding:20px;' }, [
|
||||
E('p', {}, _('Password updated for all services!')),
|
||||
E('p', { 'style': 'font-family:monospace;background:#f5f5f5;padding:10px;margin:10px 0;' }, res.password)
|
||||
]),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'btn cbi-button-positive', 'click': ui.hideModal }, _('OK'))
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, _('Error updating password')), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
'style': 'margin-left:10px;'
|
||||
}, _('Update'))
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var status = data[0] || {};
|
||||
var usersData = data[1] || {};
|
||||
var users = usersData.users || [];
|
||||
var services = status.services || {};
|
||||
|
||||
var content = [];
|
||||
|
||||
// Header
|
||||
content.push(E('h2', {}, _('SecuBox User Management')));
|
||||
|
||||
// Status Section
|
||||
content.push(E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Services')),
|
||||
E('div', { 'style': 'margin:10px 0;' }, [
|
||||
this.renderServiceBadge('Nextcloud', services.nextcloud),
|
||||
this.renderServiceBadge('PeerTube', services.peertube),
|
||||
this.renderServiceBadge('Matrix', services.matrix),
|
||||
this.renderServiceBadge('Jabber', services.jabber),
|
||||
this.renderServiceBadge('Email', services.email)
|
||||
]),
|
||||
E('p', { 'style': 'color:#666;' }, _('Domain: %s | Users: %d').format(status.domain, status.user_count))
|
||||
]));
|
||||
|
||||
// Users Table
|
||||
var userRows = users.map(function(u) { return self.renderUserRow(u); });
|
||||
|
||||
content.push(E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Users')),
|
||||
E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom:15px;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': function() { self.handleAdd(); }
|
||||
}, _('Add User'))
|
||||
]),
|
||||
users.length > 0 ?
|
||||
E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Username')),
|
||||
E('th', { 'class': 'th' }, _('Email')),
|
||||
E('th', { 'class': 'th' }, _('Services')),
|
||||
E('th', { 'class': 'th' }, _('Enabled')),
|
||||
E('th', { 'class': 'th' }, _('Actions'))
|
||||
])
|
||||
].concat(userRows)) :
|
||||
E('p', { 'style': 'color:#666;' }, _('No users configured. Click "Add User" to create one.'))
|
||||
]));
|
||||
|
||||
return E('div', { 'class': 'cbi-map' }, content);
|
||||
}
|
||||
});
|
||||
2917
luci-app-secubox-users/local-build.sh
Executable file
2917
luci-app-secubox-users/local-build.sh
Executable file
File diff suppressed because it is too large
Load Diff
190
luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users
Normal file
190
luci-app-secubox-users/root/usr/libexec/rpcd/luci.secubox-users
Normal file
@ -0,0 +1,190 @@
|
||||
#!/bin/sh
|
||||
# RPCD handler for SecuBox User Management
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
CONFIG="secubox-users"
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
|
||||
# Check if service is running
|
||||
check_service() {
|
||||
local service="$1"
|
||||
case "$service" in
|
||||
nextcloud) [ -x /usr/sbin/nextcloudctl ] && lxc-info -n nextcloud 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
|
||||
peertube) [ -x /usr/sbin/peertubectl ] && lxc-info -n peertube 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
|
||||
matrix) [ -x /usr/sbin/matrixctl ] && lxc-info -n matrix 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
|
||||
jabber) [ -x /usr/sbin/jabberctl ] && lxc-info -n jabber 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
|
||||
email) [ -x /usr/sbin/mailserverctl ] && lxc-info -n mailserver 2>/dev/null | grep -q "RUNNING" && echo "1" || echo "0" ;;
|
||||
*) echo "0" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_status() {
|
||||
local domain=$(uci_get main.domain || echo "secubox.in")
|
||||
local matrix_server=$(uci_get main.matrix_server || echo "matrix.local")
|
||||
local user_count=$(uci show ${CONFIG} 2>/dev/null | grep -c "=user$" || echo 0)
|
||||
|
||||
local nc_running=$(check_service nextcloud)
|
||||
local pt_running=$(check_service peertube)
|
||||
local mx_running=$(check_service matrix)
|
||||
local jb_running=$(check_service jabber)
|
||||
local em_running=$(check_service email)
|
||||
|
||||
cat <<EOFJ
|
||||
{
|
||||
"domain": "$domain",
|
||||
"matrix_server": "$matrix_server",
|
||||
"user_count": $user_count,
|
||||
"services": {
|
||||
"nextcloud": $nc_running,
|
||||
"peertube": $pt_running,
|
||||
"matrix": $mx_running,
|
||||
"jabber": $jb_running,
|
||||
"email": $em_running
|
||||
}
|
||||
}
|
||||
EOFJ
|
||||
}
|
||||
|
||||
get_users() {
|
||||
local users=$(uci show ${CONFIG} 2>/dev/null | grep "=user$" | cut -d'.' -f2 | cut -d'=' -f1)
|
||||
|
||||
json_init
|
||||
json_add_array "users"
|
||||
|
||||
for user in $users; do
|
||||
json_add_object
|
||||
json_add_string "username" "$user"
|
||||
json_add_string "email" "$(uci_get ${user}.email)"
|
||||
json_add_string "enabled" "$(uci_get ${user}.enabled)"
|
||||
json_add_string "created" "$(uci_get ${user}.created)"
|
||||
|
||||
# Get services as array
|
||||
local services=$(uci -q get ${CONFIG}.${user}.services 2>/dev/null)
|
||||
json_add_array "services"
|
||||
for svc in $services; do
|
||||
json_add_string "" "$svc"
|
||||
done
|
||||
json_close_array
|
||||
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
add_user() {
|
||||
read -r input
|
||||
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
|
||||
local password=$(echo "$input" | jsonfilter -e '@.password' 2>/dev/null)
|
||||
local services=$(echo "$input" | jsonfilter -e '@.services' 2>/dev/null)
|
||||
|
||||
if [ -z "$username" ]; then
|
||||
echo '{"success":false,"error":"Username required"}'
|
||||
return
|
||||
fi
|
||||
|
||||
# Generate password if not provided
|
||||
if [ -z "$password" ]; then
|
||||
password=$(head -c 12 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 16)
|
||||
fi
|
||||
|
||||
# Run secubox-users add
|
||||
local output
|
||||
if [ -n "$services" ]; then
|
||||
output=$(secubox-users add "$username" "$password" "$services" 2>&1)
|
||||
else
|
||||
output=$(secubox-users add "$username" "$password" 2>&1)
|
||||
fi
|
||||
|
||||
if echo "$output" | grep -q "USER CREDENTIALS"; then
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "username" "$username"
|
||||
json_add_string "password" "$password"
|
||||
json_add_string "email" "${username}@$(uci_get main.domain)"
|
||||
json_dump
|
||||
else
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to create user"
|
||||
json_add_string "output" "$output"
|
||||
json_dump
|
||||
fi
|
||||
}
|
||||
|
||||
delete_user() {
|
||||
read -r input
|
||||
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
|
||||
|
||||
if [ -z "$username" ]; then
|
||||
echo '{"success":false,"error":"Username required"}'
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$(secubox-users del "$username" 2>&1)
|
||||
|
||||
if echo "$output" | grep -q "deleted"; then
|
||||
echo '{"success":true}'
|
||||
else
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to delete user"
|
||||
json_add_string "output" "$output"
|
||||
json_dump
|
||||
fi
|
||||
}
|
||||
|
||||
update_password() {
|
||||
read -r input
|
||||
local username=$(echo "$input" | jsonfilter -e '@.username' 2>/dev/null)
|
||||
local password=$(echo "$input" | jsonfilter -e '@.password' 2>/dev/null)
|
||||
|
||||
if [ -z "$username" ]; then
|
||||
echo '{"success":false,"error":"Username required"}'
|
||||
return
|
||||
fi
|
||||
|
||||
local output
|
||||
if [ -n "$password" ]; then
|
||||
output=$(secubox-users passwd "$username" "$password" 2>&1)
|
||||
else
|
||||
output=$(secubox-users passwd "$username" 2>&1)
|
||||
password=$(echo "$output" | grep "Generated password:" | cut -d: -f2 | xargs)
|
||||
fi
|
||||
|
||||
if echo "$output" | grep -q "Password updated"; then
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "password" "$password"
|
||||
json_dump
|
||||
else
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to update password"
|
||||
json_dump
|
||||
fi
|
||||
}
|
||||
|
||||
list_methods() {
|
||||
cat <<'EOFM'
|
||||
{"status":{},"users":{},"add":{"username":"str","password":"str","services":"str"},"delete":{"username":"str"},"passwd":{"username":"str","password":"str"}}
|
||||
EOFM
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list) list_methods ;;
|
||||
call)
|
||||
case "$2" in
|
||||
status) get_status ;;
|
||||
users) get_users ;;
|
||||
add) add_user ;;
|
||||
delete) delete_user ;;
|
||||
passwd) update_password ;;
|
||||
*) echo '{"error":"Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
*) echo '{"error":"Unknown command"}' ;;
|
||||
esac
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/system/secubox-users": {
|
||||
"title": "SecuBox Users",
|
||||
"order": 85,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "secubox-users/overview"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-secubox-users"],
|
||||
"uci": {"secubox-users": true}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"luci-app-secubox-users": {
|
||||
"description": "Grant access to SecuBox User Management",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.secubox-users": ["status", "users"]
|
||||
},
|
||||
"uci": ["secubox-users"]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.secubox-users": ["add", "delete", "passwd"]
|
||||
},
|
||||
"uci": ["secubox-users"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,396 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
'require form';
|
||||
'require fs';
|
||||
|
||||
var callStaticList = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_list',
|
||||
params: ['instance'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticUpload = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_upload',
|
||||
params: ['instance', 'filename', 'content'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticCreate = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_create',
|
||||
params: ['name', 'domain'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticDelete = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_delete',
|
||||
params: ['name'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticPublish = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_publish',
|
||||
params: ['instance'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticDeleteFile = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_delete_file',
|
||||
params: ['instance', 'filename'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStaticConfigureAuth = rpc.declare({
|
||||
object: 'luci.hexojs',
|
||||
method: 'static_configure_auth',
|
||||
params: ['instance', 'enabled', 'domain'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
var k = 1024;
|
||||
var sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
if (!timestamp) return '-';
|
||||
var d = new Date(timestamp * 1000);
|
||||
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
selectedInstance: null,
|
||||
|
||||
load: function() {
|
||||
return callStaticList({});
|
||||
},
|
||||
|
||||
renderInstanceCard: function(instance) {
|
||||
var self = this;
|
||||
var card = E('div', { 'class': 'cbi-section', 'style': 'margin-bottom: 10px; padding: 15px; border: 1px solid #ccc; border-radius: 8px;' }, [
|
||||
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;' }, [
|
||||
E('div', {}, [
|
||||
E('strong', { 'style': 'font-size: 1.2em;' }, instance.name),
|
||||
instance.domain ? E('span', { 'style': 'margin-left: 10px; color: #666;' }, instance.domain) : '',
|
||||
instance.auth_enabled ? E('span', { 'class': 'badge', 'style': 'margin-left: 10px; background: #28a745; color: white; padding: 2px 8px; border-radius: 4px; font-size: 0.8em;' }, 'Auth') : ''
|
||||
]),
|
||||
E('div', {}, [
|
||||
E('span', { 'style': 'margin-right: 15px; color: #666;' }, instance.file_count + ' files'),
|
||||
instance.port > 0 ? E('span', { 'style': 'color: #666;' }, 'Port ' + instance.port) : ''
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; gap: 10px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() { self.showFiles(instance.name); }
|
||||
}, 'Manage Files'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': function() { self.publishSite(instance.name); }
|
||||
}, 'Publish'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { self.configureAuth(instance.name, instance.domain); }
|
||||
}, 'Auth'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'click': function() { self.deleteSite(instance.name); }
|
||||
}, 'Delete')
|
||||
])
|
||||
]);
|
||||
return card;
|
||||
},
|
||||
|
||||
showFiles: function(instanceName) {
|
||||
var self = this;
|
||||
|
||||
callStaticList({ instance: instanceName }).then(function(result) {
|
||||
if (!result.success) {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to list files'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
self.selectedInstance = instanceName;
|
||||
var container = document.getElementById('file-list-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Header
|
||||
container.appendChild(E('h3', {}, 'Files in "' + instanceName + '"'));
|
||||
|
||||
// Upload section
|
||||
var uploadSection = E('div', { 'class': 'cbi-section', 'style': 'padding: 15px; background: #f9f9f9; border-radius: 8px; margin-bottom: 15px;' }, [
|
||||
E('h4', {}, 'Upload Files'),
|
||||
E('input', {
|
||||
'type': 'file',
|
||||
'id': 'file-upload-input',
|
||||
'multiple': true,
|
||||
'style': 'margin-right: 10px;'
|
||||
}),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() { self.uploadFiles(instanceName); }
|
||||
}, 'Upload Selected')
|
||||
]);
|
||||
container.appendChild(uploadSection);
|
||||
|
||||
// File list table
|
||||
var table = E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, 'Filename'),
|
||||
E('th', { 'class': 'th' }, 'Size'),
|
||||
E('th', { 'class': 'th' }, 'Modified'),
|
||||
E('th', { 'class': 'th' }, 'Actions')
|
||||
])
|
||||
]);
|
||||
|
||||
var files = result.files || [];
|
||||
if (files.length === 0) {
|
||||
table.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td', 'colspan': '4', 'style': 'text-align: center; color: #666;' }, 'No files yet. Upload some files above.')
|
||||
]));
|
||||
} else {
|
||||
files.forEach(function(file) {
|
||||
table.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, file.name),
|
||||
E('td', { 'class': 'td' }, formatBytes(file.size)),
|
||||
E('td', { 'class': 'td' }, formatDate(file.modified)),
|
||||
E('td', { 'class': 'td' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'style': 'padding: 2px 8px; font-size: 0.9em;',
|
||||
'click': function() { self.deleteFile(instanceName, file.name); }
|
||||
}, 'Delete')
|
||||
])
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(table);
|
||||
|
||||
// Back button
|
||||
container.appendChild(E('button', {
|
||||
'class': 'cbi-button',
|
||||
'style': 'margin-top: 15px;',
|
||||
'click': function() { self.refreshView(); }
|
||||
}, 'Back to Sites'));
|
||||
});
|
||||
},
|
||||
|
||||
uploadFiles: function(instanceName) {
|
||||
var self = this;
|
||||
var input = document.getElementById('file-upload-input');
|
||||
var files = input.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
ui.addNotification(null, E('p', 'Please select files to upload'), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
var uploadPromises = [];
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
(function(file) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
// Convert to base64
|
||||
var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(e.target.result)));
|
||||
|
||||
callStaticUpload({
|
||||
instance: instanceName,
|
||||
filename: file.name,
|
||||
content: base64
|
||||
}).then(function(result) {
|
||||
if (result.success) {
|
||||
resolve(file.name);
|
||||
} else {
|
||||
reject(result.error || 'Upload failed for ' + file.name);
|
||||
}
|
||||
}).catch(reject);
|
||||
};
|
||||
reader.onerror = function() {
|
||||
reject('Failed to read file: ' + file.name);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
uploadPromises.push(promise);
|
||||
})(files[i]);
|
||||
}
|
||||
|
||||
Promise.all(uploadPromises).then(function(uploaded) {
|
||||
ui.addNotification(null, E('p', 'Uploaded ' + uploaded.length + ' file(s)'), 'success');
|
||||
self.showFiles(instanceName);
|
||||
}).catch(function(err) {
|
||||
ui.addNotification(null, E('p', 'Upload error: ' + err), 'error');
|
||||
self.showFiles(instanceName);
|
||||
});
|
||||
},
|
||||
|
||||
deleteFile: function(instanceName, filename) {
|
||||
var self = this;
|
||||
|
||||
if (!confirm('Delete file "' + filename + '"?')) return;
|
||||
|
||||
callStaticDeleteFile({ instance: instanceName, filename: filename }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', 'File deleted'), 'success');
|
||||
self.showFiles(instanceName);
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to delete file'), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
publishSite: function(instanceName) {
|
||||
callStaticPublish({ instance: instanceName }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', 'Published to ' + result.url), 'success');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to publish'), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
configureAuth: function(instanceName, currentDomain) {
|
||||
var self = this;
|
||||
|
||||
var domain = prompt('Enter domain for HAProxy auth (e.g., site.example.com):', currentDomain || '');
|
||||
if (domain === null) return;
|
||||
|
||||
if (!domain) {
|
||||
ui.addNotification(null, E('p', 'Domain is required for HAProxy auth'), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
callStaticConfigureAuth({
|
||||
instance: instanceName,
|
||||
enabled: true,
|
||||
domain: domain
|
||||
}).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', 'Auth configured for ' + domain), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to configure auth'), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteSite: function(instanceName) {
|
||||
var self = this;
|
||||
|
||||
if (!confirm('Delete static site "' + instanceName + '" and all its files?')) return;
|
||||
|
||||
callStaticDelete({ name: instanceName }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', 'Site deleted'), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to delete site'), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
createSite: function() {
|
||||
var self = this;
|
||||
|
||||
var name = prompt('Enter site name (lowercase, no spaces):');
|
||||
if (!name) return;
|
||||
|
||||
// Validate name
|
||||
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
|
||||
ui.addNotification(null, E('p', 'Invalid name. Use lowercase letters, numbers, underscore.'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var domain = prompt('Enter domain (optional, e.g., site.example.com):');
|
||||
|
||||
callStaticCreate({ name: name, domain: domain || '' }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', 'Site "' + name + '" created on port ' + result.port), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', result.error || 'Failed to create site'), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshView: function() {
|
||||
var self = this;
|
||||
|
||||
callStaticList({}).then(function(result) {
|
||||
var container = document.getElementById('file-list-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
var instancesContainer = document.getElementById('static-instances');
|
||||
instancesContainer.innerHTML = '';
|
||||
|
||||
if (!result.success) {
|
||||
instancesContainer.appendChild(E('p', { 'style': 'color: red;' }, result.error || 'Failed to load sites'));
|
||||
return;
|
||||
}
|
||||
|
||||
var instances = result.instances || [];
|
||||
if (instances.length === 0) {
|
||||
instancesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
|
||||
'No static sites yet. Click "Create New Site" to get started.'));
|
||||
} else {
|
||||
instances.forEach(function(inst) {
|
||||
instancesContainer.appendChild(self.renderInstanceCard(inst));
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var instances = (data && data.instances) || [];
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', { 'class': 'cbi-map-title' }, 'Static Sites'),
|
||||
E('div', { 'class': 'cbi-map-descr' },
|
||||
'Upload and manage static HTML sites with optional Basic Auth via HAProxy. Fast KISS publishing without Hexo build process.'),
|
||||
|
||||
// Create button
|
||||
E('div', { 'style': 'margin: 20px 0;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-add',
|
||||
'click': function() { self.createSite(); }
|
||||
}, 'Create New Site')
|
||||
]),
|
||||
|
||||
// Instances container
|
||||
E('div', { 'id': 'static-instances' }),
|
||||
|
||||
// File list container (shown when managing a site)
|
||||
E('div', { 'id': 'file-list-container', 'style': 'margin-top: 20px;' })
|
||||
]);
|
||||
|
||||
// Render initial instances
|
||||
var instancesContainer = view.querySelector('#static-instances');
|
||||
if (instances.length === 0) {
|
||||
instancesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
|
||||
'No static sites yet. Click "Create New Site" to get started.'));
|
||||
} else {
|
||||
instances.forEach(function(inst) {
|
||||
instancesContainer.appendChild(self.renderInstanceCard(inst));
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -3163,6 +3163,295 @@ delete_backup() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Static Site Methods (KISS Upload)
|
||||
# ============================================
|
||||
|
||||
# List static sites or files in a site
|
||||
static_list() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
|
||||
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
|
||||
if [ -n "$instance" ]; then
|
||||
# List files in specific instance
|
||||
local static_dir="$data_path/static/$instance"
|
||||
if [ ! -d "$static_dir" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Static instance '$instance' not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
json_add_string "instance" "$instance"
|
||||
json_add_array "files"
|
||||
|
||||
for f in "$static_dir"/*; do
|
||||
[ -f "$f" ] || continue
|
||||
local filename=$(basename "$f")
|
||||
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
||||
local modified=$(stat -c%Y "$f" 2>/dev/null || echo 0)
|
||||
|
||||
json_add_object
|
||||
json_add_string "name" "$filename"
|
||||
json_add_int "size" "$size"
|
||||
json_add_int "modified" "$modified"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
else
|
||||
# List all static instances
|
||||
json_add_array "instances"
|
||||
|
||||
for dir in "$data_path/static"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
local name=$(basename "$dir")
|
||||
local count=$(find "$dir" -type f 2>/dev/null | wc -l)
|
||||
local port=$(uci -q get hexojs.${name}.port)
|
||||
local domain=$(uci -q get hexojs.${name}.domain)
|
||||
local auth=$(uci -q get hexojs.${name}.auth_enabled)
|
||||
|
||||
json_add_object
|
||||
json_add_string "name" "$name"
|
||||
json_add_int "file_count" "$count"
|
||||
json_add_int "port" "${port:-0}"
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_boolean "auth_enabled" "${auth:-0}"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Upload file to static site (base64 encoded)
|
||||
static_upload() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
json_get_var filename filename
|
||||
json_get_var content content
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$filename" ] || [ -z "$content" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Filename and content required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
[ -z "$instance" ] && instance="default"
|
||||
|
||||
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
|
||||
local static_dir="$data_path/static/$instance"
|
||||
|
||||
# Auto-create instance if needed
|
||||
if [ ! -d "$static_dir" ]; then
|
||||
"$HEXOCTL" static create "$instance" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Sanitize filename (prevent path traversal)
|
||||
local safe_filename=$(basename "$filename" | tr -cd 'a-zA-Z0-9._-')
|
||||
local target_path="$static_dir/$safe_filename"
|
||||
|
||||
# Decode base64 content and save
|
||||
echo "$content" | base64 -d > "$target_path" 2>/dev/null
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ] && [ -f "$target_path" ]; then
|
||||
local size=$(stat -c%s "$target_path" 2>/dev/null || echo 0)
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "File uploaded"
|
||||
json_add_string "filename" "$safe_filename"
|
||||
json_add_string "path" "$target_path"
|
||||
json_add_int "size" "$size"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Failed to save file"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Create static site instance
|
||||
static_create() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var name name
|
||||
json_get_var domain domain
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Instance name required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$("$HEXOCTL" static create "$name" 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
# Set domain if provided
|
||||
[ -n "$domain" ] && uci set hexojs.${name}.domain="$domain" && uci commit hexojs
|
||||
|
||||
local port=$(uci -q get hexojs.${name}.port)
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Static instance created"
|
||||
json_add_string "name" "$name"
|
||||
json_add_int "port" "$port"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Delete static site instance
|
||||
static_delete() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var name name
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Instance name required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$("$HEXOCTL" static delete "$name" 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Static instance deleted"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Publish static site to /www/static/
|
||||
static_publish() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
|
||||
json_init
|
||||
|
||||
[ -z "$instance" ] && instance="default"
|
||||
|
||||
local output=$("$HEXOCTL" static publish "$instance" 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Published to /www/static/$instance"
|
||||
json_add_string "url" "/static/$instance/"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Delete file from static site
|
||||
static_delete_file() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
json_get_var filename filename
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$instance" ] || [ -z "$filename" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Instance and filename required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local data_path=$(uci_get main.data_path) || data_path="$DATA_PATH"
|
||||
local static_dir="$data_path/static/$instance"
|
||||
local safe_filename=$(basename "$filename")
|
||||
local target="$static_dir/$safe_filename"
|
||||
|
||||
if [ -f "$target" ]; then
|
||||
rm -f "$target"
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "File deleted"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "File not found"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Configure HAProxy auth for static site
|
||||
static_configure_auth() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var instance instance
|
||||
json_get_var enabled enabled
|
||||
json_get_var domain domain
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$instance" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Instance required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
[ -z "$domain" ] && domain=$(uci -q get hexojs.${instance}.domain)
|
||||
|
||||
if [ -z "$domain" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Domain required for HAProxy auth"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Update UCI config
|
||||
uci set hexojs.${instance}.auth_enabled="${enabled:-1}"
|
||||
uci set hexojs.${instance}.domain="$domain"
|
||||
uci commit hexojs
|
||||
|
||||
# Apply auth via hexoctl
|
||||
local output=$("$HEXOCTL" auth apply "$instance" 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Auth configured for $domain"
|
||||
json_add_string "domain" "$domain"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# GitHub Integration Methods
|
||||
# ============================================
|
||||
@ -3379,7 +3668,14 @@ case "$1" in
|
||||
"handle_webhook": {"event": "str", "repository": "str", "ref": "str", "secret": "str"},
|
||||
"setup_webhook": {"auto_build": "bool", "webhook_secret": "str"},
|
||||
"get_instance_health": {"instance": "str"},
|
||||
"get_pipeline_status": {}
|
||||
"get_pipeline_status": {},
|
||||
"static_list": {"instance": "str"},
|
||||
"static_upload": {"instance": "str", "filename": "str", "content": "str"},
|
||||
"static_create": {"name": "str", "domain": "str"},
|
||||
"static_delete": {"name": "str"},
|
||||
"static_publish": {"instance": "str"},
|
||||
"static_delete_file": {"instance": "str", "filename": "str"},
|
||||
"static_configure_auth": {"instance": "str", "enabled": "bool", "domain": "str"}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
@ -3459,6 +3755,13 @@ EOF
|
||||
setup_webhook) setup_webhook ;;
|
||||
get_instance_health) get_instance_health ;;
|
||||
get_pipeline_status) get_pipeline_status ;;
|
||||
static_list) static_list ;;
|
||||
static_upload) static_upload ;;
|
||||
static_create) static_create ;;
|
||||
static_delete) static_delete ;;
|
||||
static_publish) static_publish ;;
|
||||
static_delete_file) static_delete_file ;;
|
||||
static_configure_auth) static_configure_auth ;;
|
||||
*) echo '{"error": "Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@ -105,5 +105,13 @@
|
||||
"type": "view",
|
||||
"path": "hexojs/settings"
|
||||
}
|
||||
},
|
||||
"admin/services/hexojs/static": {
|
||||
"title": "Static Sites",
|
||||
"order": 115,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "hexojs/static"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,8 @@
|
||||
"get_tor_status",
|
||||
"get_instance_endpoints",
|
||||
"get_instance_health",
|
||||
"get_pipeline_status"
|
||||
"get_pipeline_status",
|
||||
"static_list"
|
||||
]
|
||||
},
|
||||
"uci": ["hexojs"]
|
||||
@ -84,7 +85,13 @@
|
||||
"unpublish_from_tor",
|
||||
"full_publish",
|
||||
"handle_webhook",
|
||||
"setup_webhook"
|
||||
"setup_webhook",
|
||||
"static_upload",
|
||||
"static_create",
|
||||
"static_delete",
|
||||
"static_publish",
|
||||
"static_delete_file",
|
||||
"static_configure_auth"
|
||||
]
|
||||
},
|
||||
"uci": ["hexojs"]
|
||||
|
||||
@ -294,6 +294,8 @@ return view.extend({
|
||||
'</div>';
|
||||
};
|
||||
|
||||
return KissTheme.wrap([m.render()], 'admin/services/jitsi');
|
||||
return m.render().then(function(mapEl) {
|
||||
return KissTheme.wrap([mapEl], 'admin/services/jitsi');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
29
package/secubox/luci-app-media-hub/Makefile
Normal file
29
package/secubox/luci-app-media-hub/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-media-hub
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
LUCI_TITLE:=LuCI Media Services Hub
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/luci-app-media-hub/install
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.media-hub $(1)/usr/libexec/rpcd/luci.media-hub
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-media-hub.json $(1)/usr/share/rpcd/acl.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-media-hub.json $(1)/usr/share/luci/menu.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/media-hub
|
||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/media-hub/*.js $(1)/www/luci-static/resources/view/media-hub/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-media-hub))
|
||||
@ -0,0 +1,376 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require poll';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callMediaHubStatus = rpc.declare({
|
||||
object: 'luci.media-hub',
|
||||
method: 'status',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callMediaHubServices = rpc.declare({
|
||||
object: 'luci.media-hub',
|
||||
method: 'services',
|
||||
expect: { services: [] }
|
||||
});
|
||||
|
||||
var callServiceStart = rpc.declare({
|
||||
object: 'luci.media-hub',
|
||||
method: 'service_start',
|
||||
params: ['id']
|
||||
});
|
||||
|
||||
var callServiceStop = rpc.declare({
|
||||
object: 'luci.media-hub',
|
||||
method: 'service_stop',
|
||||
params: ['id']
|
||||
});
|
||||
|
||||
var callServiceRestart = rpc.declare({
|
||||
object: 'luci.media-hub',
|
||||
method: 'service_restart',
|
||||
params: ['id']
|
||||
});
|
||||
|
||||
// Category icons and colors
|
||||
var categoryConfig = {
|
||||
streaming: { icon: '🎬', color: '#e74c3c', label: 'Streaming' },
|
||||
conferencing: { icon: '📹', color: '#3498db', label: 'Conferencing' },
|
||||
apps: { icon: '📊', color: '#9b59b6', label: 'Apps' },
|
||||
display: { icon: '🪞', color: '#1abc9c', label: 'Display' },
|
||||
social: { icon: '🦣', color: '#e67e22', label: 'Social' },
|
||||
monitoring: { icon: '📡', color: '#2ecc71', label: 'Monitoring' }
|
||||
};
|
||||
|
||||
// Status colors and icons
|
||||
var statusConfig = {
|
||||
running: { color: '#27ae60', icon: '●', label: 'Running' },
|
||||
stopped: { color: '#e74c3c', icon: '○', label: 'Stopped' },
|
||||
not_installed: { color: '#95a5a6', icon: '◌', label: 'Not Installed' },
|
||||
unknown: { color: '#f39c12', icon: '?', label: 'Unknown' }
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callMediaHubStatus(),
|
||||
callMediaHubServices()
|
||||
]);
|
||||
},
|
||||
|
||||
renderStatusBar: function(status) {
|
||||
var total = status.total_services || 0;
|
||||
var installed = status.installed || 0;
|
||||
var running = status.running || 0;
|
||||
var stopped = status.stopped || 0;
|
||||
|
||||
return E('div', { 'class': 'media-hub-status-bar' }, [
|
||||
E('div', { 'class': 'status-item' }, [
|
||||
E('span', { 'class': 'status-value', 'style': 'color: #3498db' }, String(total)),
|
||||
E('span', { 'class': 'status-label' }, 'Total')
|
||||
]),
|
||||
E('div', { 'class': 'status-item' }, [
|
||||
E('span', { 'class': 'status-value', 'style': 'color: #9b59b6' }, String(installed)),
|
||||
E('span', { 'class': 'status-label' }, 'Installed')
|
||||
]),
|
||||
E('div', { 'class': 'status-item' }, [
|
||||
E('span', { 'class': 'status-value', 'style': 'color: #27ae60' }, String(running)),
|
||||
E('span', { 'class': 'status-label' }, 'Running')
|
||||
]),
|
||||
E('div', { 'class': 'status-item' }, [
|
||||
E('span', { 'class': 'status-value', 'style': 'color: #e74c3c' }, String(stopped)),
|
||||
E('span', { 'class': 'status-label' }, 'Stopped')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderServiceCard: function(service) {
|
||||
var self = this;
|
||||
var statusCfg = statusConfig[service.status] || statusConfig.unknown;
|
||||
var categoryCfg = categoryConfig[service.category] || { icon: '📦', color: '#7f8c8d', label: 'Other' };
|
||||
|
||||
var controls = [];
|
||||
|
||||
if (service.installed) {
|
||||
if (service.status === 'running') {
|
||||
controls.push(
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-negative',
|
||||
'style': 'margin-right: 5px; padding: 4px 12px; font-size: 12px;',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
return callServiceStop(service.id).then(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
})
|
||||
}, '⏹ Stop'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'style': 'padding: 4px 12px; font-size: 12px;',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
return callServiceRestart(service.id).then(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
})
|
||||
}, '🔄 Restart')
|
||||
);
|
||||
} else if (service.status === 'stopped') {
|
||||
controls.push(
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-positive',
|
||||
'style': 'padding: 4px 12px; font-size: 12px;',
|
||||
'click': ui.createHandlerFn(this, function() {
|
||||
return callServiceStart(service.id).then(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
})
|
||||
}, '▶ Start')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add settings link
|
||||
if (service.url) {
|
||||
controls.push(
|
||||
E('a', {
|
||||
'href': service.url,
|
||||
'class': 'cbi-button cbi-button-neutral',
|
||||
'style': 'margin-left: 5px; padding: 4px 12px; font-size: 12px; text-decoration: none;'
|
||||
}, '⚙ Settings')
|
||||
);
|
||||
}
|
||||
|
||||
return E('div', {
|
||||
'class': 'media-service-card',
|
||||
'data-status': service.status,
|
||||
'style': 'border-left: 4px solid ' + categoryCfg.color + ';'
|
||||
}, [
|
||||
E('div', { 'class': 'card-header' }, [
|
||||
E('span', { 'class': 'service-emoji' }, service.emoji || '📦'),
|
||||
E('div', { 'class': 'service-info' }, [
|
||||
E('span', { 'class': 'service-name' }, service.name),
|
||||
E('span', { 'class': 'service-category', 'style': 'color: ' + categoryCfg.color }, categoryCfg.label)
|
||||
]),
|
||||
E('span', {
|
||||
'class': 'service-status',
|
||||
'style': 'color: ' + statusCfg.color,
|
||||
'title': statusCfg.label
|
||||
}, statusCfg.icon + ' ' + statusCfg.label)
|
||||
]),
|
||||
E('div', { 'class': 'card-body' }, [
|
||||
E('p', { 'class': 'service-description' }, service.description),
|
||||
service.port > 0 ? E('p', { 'class': 'service-port' }, 'Port: ' + service.port) : E('span')
|
||||
]),
|
||||
E('div', { 'class': 'card-footer' }, controls)
|
||||
]);
|
||||
},
|
||||
|
||||
renderCategorySection: function(category, services) {
|
||||
var categoryCfg = categoryConfig[category] || { icon: '📦', color: '#7f8c8d', label: category };
|
||||
var categoryServices = services.filter(function(s) { return s.category === category; });
|
||||
|
||||
if (categoryServices.length === 0) return E('span');
|
||||
|
||||
var self = this;
|
||||
return E('div', { 'class': 'media-category-section' }, [
|
||||
E('h3', { 'class': 'category-title', 'style': 'color: ' + categoryCfg.color }, [
|
||||
E('span', { 'class': 'category-icon' }, categoryCfg.icon),
|
||||
' ',
|
||||
categoryCfg.label,
|
||||
E('span', { 'class': 'category-count' }, ' (' + categoryServices.length + ')')
|
||||
]),
|
||||
E('div', { 'class': 'media-cards-grid' },
|
||||
categoryServices.map(function(service) {
|
||||
return self.renderServiceCard(service);
|
||||
})
|
||||
)
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0];
|
||||
var services = data[1];
|
||||
|
||||
// Sort by category then by name
|
||||
services.sort(function(a, b) {
|
||||
if (a.category !== b.category) {
|
||||
return a.category.localeCompare(b.category);
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Get unique categories in order
|
||||
var categories = ['streaming', 'conferencing', 'apps', 'display', 'social', 'monitoring'];
|
||||
|
||||
var self = this;
|
||||
var view = E('div', { 'class': 'media-hub-dashboard' }, [
|
||||
E('style', {}, `
|
||||
.media-hub-dashboard {
|
||||
padding: 20px;
|
||||
}
|
||||
.media-hub-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.media-hub-header h2 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.media-hub-header .subtitle {
|
||||
color: #666;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.media-hub-status-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.status-item {
|
||||
text-align: center;
|
||||
}
|
||||
.status-value {
|
||||
display: block;
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.status-label {
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.media-category-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.category-title {
|
||||
font-size: 1.4em;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
.category-icon {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.category-count {
|
||||
font-weight: normal;
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
}
|
||||
.media-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.media-service-card {
|
||||
background: #1a1a2e;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.media-service-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
|
||||
}
|
||||
.media-service-card[data-status="running"] {
|
||||
background: linear-gradient(135deg, #1a2e1a 0%, #162e16 100%);
|
||||
}
|
||||
.media-service-card[data-status="stopped"] {
|
||||
background: linear-gradient(135deg, #2e1a1a 0%, #2e1616 100%);
|
||||
}
|
||||
.media-service-card[data-status="not_installed"] {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.service-emoji {
|
||||
font-size: 2.5em;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.service-info {
|
||||
flex: 1;
|
||||
}
|
||||
.service-name {
|
||||
display: block;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
.service-category {
|
||||
font-size: 0.85em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.service-status {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.card-body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.service-description {
|
||||
color: #aaa;
|
||||
font-size: 0.95em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.service-port {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
font-family: monospace;
|
||||
}
|
||||
.card-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.card-footer button, .card-footer a {
|
||||
border-radius: 6px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.media-hub-status-bar {
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
.media-cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
`),
|
||||
E('div', { 'class': 'media-hub-header' }, [
|
||||
E('h2', {}, '🎬 Media Services Hub'),
|
||||
E('p', { 'class': 'subtitle' }, 'Unified dashboard for all SecuBox media services')
|
||||
]),
|
||||
this.renderStatusBar(status)
|
||||
]);
|
||||
|
||||
categories.forEach(function(category) {
|
||||
var section = self.renderCategorySection(category, services);
|
||||
if (section.tagName !== 'SPAN') {
|
||||
view.appendChild(section);
|
||||
}
|
||||
});
|
||||
|
||||
// Setup polling for status updates
|
||||
poll.add(L.bind(function() {
|
||||
return callMediaHubServices().then(L.bind(function(services) {
|
||||
// Update status indicators without full reload
|
||||
services.forEach(function(service) {
|
||||
var card = document.querySelector('.media-service-card[data-status]');
|
||||
// Could update individual cards here for smooth updates
|
||||
});
|
||||
}, this));
|
||||
}, this), 30);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
523
package/secubox/luci-app-media-hub/root/usr/libexec/rpcd/luci.media-hub
Executable file
523
package/secubox/luci-app-media-hub/root/usr/libexec/rpcd/luci.media-hub
Executable file
@ -0,0 +1,523 @@
|
||||
#!/bin/sh
|
||||
# Media Hub RPCD backend - Unified media services dashboard
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
# Service definitions: id|name|emoji|port|ctl_cmd|container|category
|
||||
MEDIA_SERVICES="
|
||||
jellyfin|Jellyfin|🎬|8096|jellyfinctl|jellyfin|streaming
|
||||
lyrion|Lyrion Music|🎵|9000|lyrionctl|lyrion|streaming
|
||||
jitsi|Jitsi Meet|📹|8443|jitsctl|jitsi-web|conferencing
|
||||
peertube|PeerTube|📺|9000|peertubectl|peertube|streaming
|
||||
streamlit|Streamlit|📊|8501|streamlitctl|streamlit|apps
|
||||
magicmirror|MagicMirror²|🪞|8080|mmctl|magicmirror|display
|
||||
gotosocial|GoToSocial|🦣|8080|gotosocialctl|gotosocial|social
|
||||
"
|
||||
|
||||
# Check if a service is installed
|
||||
_service_installed() {
|
||||
local ctl="$1"
|
||||
command -v "$ctl" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check container status
|
||||
_container_status() {
|
||||
local name="$1"
|
||||
if command -v lxc-info >/dev/null 2>&1; then
|
||||
lxc-info -n "$name" -s 2>/dev/null | grep -q "RUNNING" && echo "running" && return
|
||||
[ -d "/srv/lxc/$name" ] && echo "stopped" && return
|
||||
fi
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$" && echo "running" && return
|
||||
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${name}$" && echo "stopped" && return
|
||||
fi
|
||||
echo "not_installed"
|
||||
}
|
||||
|
||||
# Check if port is listening
|
||||
_port_listening() {
|
||||
local port="$1"
|
||||
netstat -tln 2>/dev/null | grep -q ":${port} " && echo "1" || echo "0"
|
||||
}
|
||||
|
||||
# Get service status via its control command
|
||||
_get_service_status() {
|
||||
local ctl="$1"
|
||||
local output
|
||||
|
||||
if ! _service_installed "$ctl"; then
|
||||
echo "not_installed"
|
||||
return
|
||||
fi
|
||||
|
||||
# Try to get status from the service's ctl command
|
||||
output=$("$ctl" status 2>/dev/null | head -5)
|
||||
if echo "$output" | grep -qi "running\|active\|started"; then
|
||||
echo "running"
|
||||
elif echo "$output" | grep -qi "stopped\|inactive\|not running"; then
|
||||
echo "stopped"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Status - Overall dashboard status
|
||||
# ===========================================
|
||||
|
||||
get_status() {
|
||||
json_init
|
||||
|
||||
local total=0
|
||||
local running=0
|
||||
local stopped=0
|
||||
local not_installed=0
|
||||
|
||||
echo "$MEDIA_SERVICES" | while IFS='|' read -r id name emoji port ctl container category; do
|
||||
[ -z "$id" ] && continue
|
||||
total=$((total + 1))
|
||||
|
||||
if ! _service_installed "$ctl"; then
|
||||
not_installed=$((not_installed + 1))
|
||||
elif [ "$(_port_listening "$port")" = "1" ]; then
|
||||
running=$((running + 1))
|
||||
else
|
||||
stopped=$((stopped + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Count installed services
|
||||
local installed=0
|
||||
local active=0
|
||||
for svc in jellyfin lyrion jitsi peertube streamlit magicmirror gotosocial; do
|
||||
case "$svc" in
|
||||
jellyfin) [ -x /usr/sbin/jellyfinctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8096 " && active=$((active+1)) ;;
|
||||
lyrion) [ -x /usr/sbin/lyrionctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":9000 " && active=$((active+1)) ;;
|
||||
jitsi) [ -x /usr/sbin/jitsctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8443 " && active=$((active+1)) ;;
|
||||
peertube) [ -x /usr/sbin/peertubectl ] && installed=$((installed+1)) ;;
|
||||
streamlit) [ -x /usr/sbin/streamlitctl ] && installed=$((installed+1)) && netstat -tln 2>/dev/null | grep -q ":8501 " && active=$((active+1)) ;;
|
||||
magicmirror) [ -x /usr/sbin/mmctl ] && installed=$((installed+1)) ;;
|
||||
gotosocial) [ -x /usr/sbin/gotosocialctl ] && installed=$((installed+1)) ;;
|
||||
esac
|
||||
done
|
||||
|
||||
json_add_int "total_services" 7
|
||||
json_add_int "installed" "$installed"
|
||||
json_add_int "running" "$active"
|
||||
json_add_int "stopped" "$((installed - active))"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Services - List all media services
|
||||
# ===========================================
|
||||
|
||||
get_services() {
|
||||
json_init
|
||||
json_add_array "services"
|
||||
|
||||
# Jellyfin
|
||||
json_add_object
|
||||
json_add_string "id" "jellyfin"
|
||||
json_add_string "name" "Jellyfin"
|
||||
json_add_string "emoji" "🎬"
|
||||
json_add_string "description" "Media server for movies, TV, music & photos"
|
||||
json_add_string "category" "streaming"
|
||||
json_add_int "port" 8096
|
||||
json_add_string "ctl" "jellyfinctl"
|
||||
if [ -x /usr/sbin/jellyfinctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if netstat -tln 2>/dev/null | grep -q ":8096 "; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/jellyfin"
|
||||
json_close_object
|
||||
|
||||
# Lyrion
|
||||
json_add_object
|
||||
json_add_string "id" "lyrion"
|
||||
json_add_string "name" "Lyrion Music Server"
|
||||
json_add_string "emoji" "🎵"
|
||||
json_add_string "description" "Music streaming (Squeezebox/LMS)"
|
||||
json_add_string "category" "streaming"
|
||||
json_add_int "port" 9000
|
||||
json_add_string "ctl" "lyrionctl"
|
||||
if [ -x /usr/sbin/lyrionctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if netstat -tln 2>/dev/null | grep -q ":9000 "; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/lyrion"
|
||||
json_close_object
|
||||
|
||||
# Jitsi Meet
|
||||
json_add_object
|
||||
json_add_string "id" "jitsi"
|
||||
json_add_string "name" "Jitsi Meet"
|
||||
json_add_string "emoji" "📹"
|
||||
json_add_string "description" "Video conferencing with E2E encryption"
|
||||
json_add_string "category" "conferencing"
|
||||
json_add_int "port" 8443
|
||||
json_add_string "ctl" "jitsctl"
|
||||
if [ -x /usr/sbin/jitsctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
# Check if jitsi containers are running
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "jitsi"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/jitsi"
|
||||
json_close_object
|
||||
|
||||
# PeerTube
|
||||
json_add_object
|
||||
json_add_string "id" "peertube"
|
||||
json_add_string "name" "PeerTube"
|
||||
json_add_string "emoji" "📺"
|
||||
json_add_string "description" "Federated video platform"
|
||||
json_add_string "category" "streaming"
|
||||
json_add_int "port" 9000
|
||||
json_add_string "ctl" "peertubectl"
|
||||
if [ -x /usr/sbin/peertubectl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "peertube"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/peertube"
|
||||
json_close_object
|
||||
|
||||
# Streamlit
|
||||
json_add_object
|
||||
json_add_string "id" "streamlit"
|
||||
json_add_string "name" "Streamlit Apps"
|
||||
json_add_string "emoji" "📊"
|
||||
json_add_string "description" "Python data apps platform"
|
||||
json_add_string "category" "apps"
|
||||
json_add_int "port" 8501
|
||||
json_add_string "ctl" "streamlitctl"
|
||||
if [ -x /usr/sbin/streamlitctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if lxc-info -n streamlit -s 2>/dev/null | grep -q "RUNNING"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/streamlit"
|
||||
json_close_object
|
||||
|
||||
# MagicMirror²
|
||||
json_add_object
|
||||
json_add_string "id" "magicmirror"
|
||||
json_add_string "name" "MagicMirror²"
|
||||
json_add_string "emoji" "🪞"
|
||||
json_add_string "description" "Smart mirror display platform"
|
||||
json_add_string "category" "display"
|
||||
json_add_int "port" 8080
|
||||
json_add_string "ctl" "mmctl"
|
||||
if [ -x /usr/sbin/mmctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if lxc-info -n magicmirror -s 2>/dev/null | grep -q "RUNNING" || docker ps --format '{{.Names}}' 2>/dev/null | grep -q "magicmirror"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/magicmirror2"
|
||||
json_close_object
|
||||
|
||||
# GoToSocial
|
||||
json_add_object
|
||||
json_add_string "id" "gotosocial"
|
||||
json_add_string "name" "GoToSocial"
|
||||
json_add_string "emoji" "🦣"
|
||||
json_add_string "description" "Fediverse social network"
|
||||
json_add_string "category" "social"
|
||||
json_add_int "port" 8080
|
||||
json_add_string "ctl" "gotosocialctl"
|
||||
if [ -x /usr/sbin/gotosocialctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
if lxc-info -n gotosocial -s 2>/dev/null | grep -q "RUNNING" || docker ps --format '{{.Names}}' 2>/dev/null | grep -q "gotosocial"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/gotosocial"
|
||||
json_close_object
|
||||
|
||||
# Media Flow (monitoring)
|
||||
json_add_object
|
||||
json_add_string "id" "media-flow"
|
||||
json_add_string "name" "Media Flow"
|
||||
json_add_string "emoji" "📡"
|
||||
json_add_string "description" "Stream detection & monitoring"
|
||||
json_add_string "category" "monitoring"
|
||||
json_add_int "port" 0
|
||||
json_add_string "ctl" ""
|
||||
# Media Flow is always "installed" if the menu exists
|
||||
if [ -f /usr/share/luci/menu.d/luci-app-media-flow.json ]; then
|
||||
json_add_boolean "installed" 1
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
json_add_string "url" "/cgi-bin/luci/admin/services/media-flow"
|
||||
json_close_object
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Service Status - Get detailed status for one service
|
||||
# ===========================================
|
||||
|
||||
get_service_status() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service_id id
|
||||
|
||||
json_init
|
||||
|
||||
case "$service_id" in
|
||||
jellyfin)
|
||||
if [ -x /usr/sbin/jellyfinctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
local status=$(jellyfinctl status 2>/dev/null)
|
||||
if echo "$status" | grep -qi "running"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
json_add_string "details" "$status"
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
;;
|
||||
lyrion)
|
||||
if [ -x /usr/sbin/lyrionctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
local status=$(lyrionctl status 2>/dev/null)
|
||||
if echo "$status" | grep -qi "running"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
json_add_string "details" "$status"
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
;;
|
||||
jitsi)
|
||||
if [ -x /usr/sbin/jitsctl ]; then
|
||||
json_add_boolean "installed" 1
|
||||
local status=$(jitsctl status 2>/dev/null)
|
||||
if echo "$status" | grep -qi "running"; then
|
||||
json_add_string "status" "running"
|
||||
else
|
||||
json_add_string "status" "stopped"
|
||||
fi
|
||||
json_add_string "details" "$status"
|
||||
else
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "not_installed"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
json_add_boolean "installed" 0
|
||||
json_add_string "status" "unknown"
|
||||
json_add_string "error" "Unknown service: $service_id"
|
||||
;;
|
||||
esac
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Service Control
|
||||
# ===========================================
|
||||
|
||||
service_start() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service_id id
|
||||
|
||||
json_init
|
||||
|
||||
local ctl=""
|
||||
case "$service_id" in
|
||||
jellyfin) ctl="jellyfinctl" ;;
|
||||
lyrion) ctl="lyrionctl" ;;
|
||||
jitsi) ctl="jitsctl" ;;
|
||||
peertube) ctl="peertubectl" ;;
|
||||
streamlit) ctl="streamlitctl" ;;
|
||||
magicmirror) ctl="mmctl" ;;
|
||||
gotosocial) ctl="gotosocialctl" ;;
|
||||
esac
|
||||
|
||||
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service not installed or unknown"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$("$ctl" start 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service started"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_stop() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service_id id
|
||||
|
||||
json_init
|
||||
|
||||
local ctl=""
|
||||
case "$service_id" in
|
||||
jellyfin) ctl="jellyfinctl" ;;
|
||||
lyrion) ctl="lyrionctl" ;;
|
||||
jitsi) ctl="jitsctl" ;;
|
||||
peertube) ctl="peertubectl" ;;
|
||||
streamlit) ctl="streamlitctl" ;;
|
||||
magicmirror) ctl="mmctl" ;;
|
||||
gotosocial) ctl="gotosocialctl" ;;
|
||||
esac
|
||||
|
||||
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service not installed or unknown"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$("$ctl" stop 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service stopped"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_restart() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service_id id
|
||||
|
||||
json_init
|
||||
|
||||
local ctl=""
|
||||
case "$service_id" in
|
||||
jellyfin) ctl="jellyfinctl" ;;
|
||||
lyrion) ctl="lyrionctl" ;;
|
||||
jitsi) ctl="jitsctl" ;;
|
||||
peertube) ctl="peertubectl" ;;
|
||||
streamlit) ctl="streamlitctl" ;;
|
||||
magicmirror) ctl="mmctl" ;;
|
||||
gotosocial) ctl="gotosocialctl" ;;
|
||||
esac
|
||||
|
||||
if [ -z "$ctl" ] || ! command -v "$ctl" >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service not installed or unknown"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
local output=$("$ctl" restart 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service restarted"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Main Dispatcher
|
||||
# ===========================================
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
cat << 'EOF'
|
||||
{
|
||||
"status": {},
|
||||
"services": {},
|
||||
"service_status": {"id": "str"},
|
||||
"service_start": {"id": "str"},
|
||||
"service_stop": {"id": "str"},
|
||||
"service_restart": {"id": "str"}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
status) get_status ;;
|
||||
services) get_services ;;
|
||||
service_status) get_service_status ;;
|
||||
service_start) service_start ;;
|
||||
service_stop) service_stop ;;
|
||||
service_restart) service_restart ;;
|
||||
*) echo '{"error": "Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,13 @@
|
||||
{
|
||||
"admin/services/media-hub": {
|
||||
"title": "Media Hub",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "media-hub/dashboard"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-media-hub"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
{
|
||||
"luci-app-media-hub": {
|
||||
"description": "Grant access to Media Hub dashboard",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.media-hub": [
|
||||
"status",
|
||||
"services",
|
||||
"service_status"
|
||||
]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.media-hub": [
|
||||
"service_start",
|
||||
"service_stop",
|
||||
"service_restart"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
package/secubox/luci-app-saas-relay/Makefile
Normal file
29
package/secubox/luci-app-saas-relay/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-saas-relay
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
LUCI_TITLE:=LuCI SaaS Relay Dashboard
|
||||
LUCI_DEPENDS:=+secubox-app-saas-relay +luci-base
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/luci-app-saas-relay/install
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.saas-relay $(1)/usr/libexec/rpcd/luci.saas-relay
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-saas-relay.json $(1)/usr/share/rpcd/acl.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-saas-relay.json $(1)/usr/share/luci/menu.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/saas-relay
|
||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/saas-relay/*.js $(1)/www/luci-static/resources/view/saas-relay/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-saas-relay))
|
||||
@ -0,0 +1,457 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
'require poll';
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'status',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callListServices = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'list_services',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callServiceEnable = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'service_enable',
|
||||
params: ['id'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callServiceDisable = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'service_disable',
|
||||
params: ['id'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callServiceAdd = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'service_add',
|
||||
params: ['id', 'name', 'domain', 'emoji'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callServiceDelete = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'service_delete',
|
||||
params: ['id'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStart = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'start',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callStop = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'stop',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callSetup = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'setup',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callListCookies = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'list_cookies',
|
||||
params: ['service'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callImportCookies = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'import_cookies',
|
||||
params: ['service', 'cookies'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callClearCookies = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'clear_cookies',
|
||||
params: ['service'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetLog = rpc.declare({
|
||||
object: 'luci.saas-relay',
|
||||
method: 'get_log',
|
||||
params: ['lines'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callStatus(),
|
||||
callListServices()
|
||||
]);
|
||||
},
|
||||
|
||||
renderStatusCard: function(status) {
|
||||
var statusEmoji = status.status === 'running' ? '✅' : '⏸️';
|
||||
var enabledEmoji = status.enabled ? '🔓' : '🔐';
|
||||
|
||||
return E('div', { 'class': 'cbi-section', 'style': 'background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #e0e0e0; border-radius: 12px; padding: 20px; margin-bottom: 20px;' }, [
|
||||
E('h3', { 'style': 'color: #f97316; margin-bottom: 15px;' }, '🔄 SaaS Relay Status'),
|
||||
E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;' }, [
|
||||
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
|
||||
E('div', { 'style': 'font-size: 2em;' }, statusEmoji),
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Status'),
|
||||
E('div', { 'style': 'font-weight: bold;' }, status.status || 'Unknown')
|
||||
]),
|
||||
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
|
||||
E('div', { 'style': 'font-size: 2em;' }, '🔗'),
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Services'),
|
||||
E('div', { 'style': 'font-weight: bold;' }, (status.enabled_services || 0) + ' / ' + (status.service_count || 0))
|
||||
]),
|
||||
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
|
||||
E('div', { 'style': 'font-size: 2em;' }, '🍪'),
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Cookies'),
|
||||
E('div', { 'style': 'font-weight: bold;' }, status.total_cookies || 0)
|
||||
]),
|
||||
E('div', { 'class': 'stat-box', 'style': 'background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px; text-align: center;' }, [
|
||||
E('div', { 'style': 'font-size: 2em;' }, '🌐'),
|
||||
E('div', { 'style': 'font-size: 0.9em; color: #999;' }, 'Port'),
|
||||
E('div', { 'style': 'font-weight: bold;' }, status.proxy_port || 8890)
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderServiceCard: function(service) {
|
||||
var self = this;
|
||||
var statusEmoji = service.enabled ? '🟢' : '🔴';
|
||||
var cookieEmoji = service.cookie_count > 0 ? '🍪' : '⚪';
|
||||
|
||||
var card = E('div', {
|
||||
'class': 'service-card',
|
||||
'style': 'background: #fff; border: 1px solid #ddd; border-radius: 10px; padding: 15px; margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;'
|
||||
}, [
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 15px;' }, [
|
||||
E('span', { 'style': 'font-size: 2em;' }, service.emoji || '🔗'),
|
||||
E('div', {}, [
|
||||
E('div', { 'style': 'font-weight: bold; font-size: 1.1em;' }, [
|
||||
statusEmoji, ' ', service.name
|
||||
]),
|
||||
E('div', { 'style': 'color: #666; font-size: 0.9em;' }, service.domain),
|
||||
E('div', { 'style': 'color: #999; font-size: 0.8em;' }, [
|
||||
cookieEmoji, ' ', service.cookie_count, ' cookies'
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; gap: 8px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'style': 'padding: 5px 10px;',
|
||||
'click': function() { self.toggleService(service); }
|
||||
}, service.enabled ? '🔐 Disable' : '🔓 Enable'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'style': 'padding: 5px 10px;',
|
||||
'click': function() { self.manageCookies(service); }
|
||||
}, '🍪 Cookies'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'style': 'padding: 5px 10px;',
|
||||
'click': function() { self.deleteService(service.id); }
|
||||
}, '🗑️')
|
||||
])
|
||||
]);
|
||||
|
||||
return card;
|
||||
},
|
||||
|
||||
toggleService: function(service) {
|
||||
var self = this;
|
||||
var action = service.enabled ? callServiceDisable : callServiceEnable;
|
||||
|
||||
action({ id: service.id }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', '❌ ' + (result.error || 'Failed')), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteService: function(id) {
|
||||
var self = this;
|
||||
if (!confirm('🗑️ Delete service "' + id + '" and its cookies?')) return;
|
||||
|
||||
callServiceDelete({ id: id }).then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
|
||||
self.refreshView();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
manageCookies: function(service) {
|
||||
var self = this;
|
||||
|
||||
callListCookies({ service: service.id }).then(function(result) {
|
||||
var content = E('div', {}, [
|
||||
E('h4', {}, '🍪 Cookies for ' + service.name),
|
||||
E('p', {}, 'Current: ' + (result.count || 0) + ' cookies'),
|
||||
E('hr'),
|
||||
E('h5', {}, 'Import Cookies (JSON format)'),
|
||||
E('textarea', {
|
||||
'id': 'cookie-import-text',
|
||||
'style': 'width: 100%; height: 150px; font-family: monospace;',
|
||||
'placeholder': '{"cookie_name": "cookie_value", ...}'
|
||||
}, result.cookies !== '{}' ? result.cookies : ''),
|
||||
E('div', { 'style': 'margin-top: 10px; display: flex; gap: 10px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
var cookies = document.getElementById('cookie-import-text').value;
|
||||
try {
|
||||
JSON.parse(cookies);
|
||||
callImportCookies({ service: service.id, cookies: cookies }).then(function(res) {
|
||||
if (res.success) {
|
||||
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
|
||||
ui.hideModal();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
ui.addNotification(null, E('p', '❌ Invalid JSON'), 'error');
|
||||
}
|
||||
}
|
||||
}, '📥 Import'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-remove',
|
||||
'click': function() {
|
||||
if (confirm('Clear all cookies for ' + service.name + '?')) {
|
||||
callClearCookies({ service: service.id }).then(function(res) {
|
||||
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
|
||||
ui.hideModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
}, '🗑️ Clear'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': ui.hideModal
|
||||
}, 'Close')
|
||||
])
|
||||
]);
|
||||
|
||||
ui.showModal('Cookie Manager', content);
|
||||
});
|
||||
},
|
||||
|
||||
addService: function() {
|
||||
var self = this;
|
||||
|
||||
var content = E('div', {}, [
|
||||
E('h4', {}, '➕ Add New Service'),
|
||||
E('div', { 'style': 'display: grid; gap: 10px;' }, [
|
||||
E('label', {}, [
|
||||
'ID (lowercase, no spaces):',
|
||||
E('input', { 'type': 'text', 'id': 'new-svc-id', 'style': 'width: 100%; padding: 5px;' })
|
||||
]),
|
||||
E('label', {}, [
|
||||
'Name:',
|
||||
E('input', { 'type': 'text', 'id': 'new-svc-name', 'style': 'width: 100%; padding: 5px;' })
|
||||
]),
|
||||
E('label', {}, [
|
||||
'Domain:',
|
||||
E('input', { 'type': 'text', 'id': 'new-svc-domain', 'style': 'width: 100%; padding: 5px;', 'placeholder': 'example.com' })
|
||||
]),
|
||||
E('label', {}, [
|
||||
'Emoji:',
|
||||
E('input', { 'type': 'text', 'id': 'new-svc-emoji', 'style': 'width: 100%; padding: 5px;', 'placeholder': '🔗', 'value': '🔗' })
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'margin-top: 15px; display: flex; gap: 10px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() {
|
||||
var id = document.getElementById('new-svc-id').value;
|
||||
var name = document.getElementById('new-svc-name').value;
|
||||
var domain = document.getElementById('new-svc-domain').value;
|
||||
var emoji = document.getElementById('new-svc-emoji').value || '🔗';
|
||||
|
||||
if (!id || !name || !domain) {
|
||||
ui.addNotification(null, E('p', '❌ All fields required'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
callServiceAdd({ id: id, name: name, domain: domain, emoji: emoji }).then(function(res) {
|
||||
if (res.success) {
|
||||
ui.addNotification(null, E('p', res.emoji + ' ' + res.message), 'success');
|
||||
ui.hideModal();
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', '❌ ' + res.error), 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, '➕ Add Service'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': ui.hideModal
|
||||
}, 'Cancel')
|
||||
])
|
||||
]);
|
||||
|
||||
ui.showModal('Add Service', content);
|
||||
},
|
||||
|
||||
startRelay: function() {
|
||||
var self = this;
|
||||
callStart().then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stopRelay: function() {
|
||||
var self = this;
|
||||
callStop().then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setupRelay: function() {
|
||||
var self = this;
|
||||
callSetup().then(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', result.emoji + ' ' + result.message), 'success');
|
||||
self.refreshView();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', '❌ ' + result.error), 'error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showLog: function() {
|
||||
callGetLog({ lines: 50 }).then(function(result) {
|
||||
var entries = result.entries || [];
|
||||
var logContent = entries.length > 0 ? entries.join('\n') : 'No activity logged';
|
||||
|
||||
var content = E('div', {}, [
|
||||
E('h4', {}, '📋 Activity Log'),
|
||||
E('pre', {
|
||||
'style': 'background: #1a1a2e; color: #e0e0e0; padding: 15px; border-radius: 8px; max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.85em;'
|
||||
}, logContent),
|
||||
E('div', { 'style': 'margin-top: 10px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': ui.hideModal
|
||||
}, 'Close')
|
||||
])
|
||||
]);
|
||||
|
||||
ui.showModal('Activity Log', content);
|
||||
});
|
||||
},
|
||||
|
||||
refreshView: function() {
|
||||
var self = this;
|
||||
Promise.all([callStatus(), callListServices()]).then(function(results) {
|
||||
var status = results[0];
|
||||
var services = results[1].services || [];
|
||||
|
||||
// Update status card
|
||||
var statusContainer = document.getElementById('status-container');
|
||||
statusContainer.innerHTML = '';
|
||||
statusContainer.appendChild(self.renderStatusCard(status));
|
||||
|
||||
// Update services
|
||||
var servicesContainer = document.getElementById('services-container');
|
||||
servicesContainer.innerHTML = '';
|
||||
services.forEach(function(svc) {
|
||||
servicesContainer.appendChild(self.renderServiceCard(svc));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var self = this;
|
||||
var status = data[0] || {};
|
||||
var services = (data[1] && data[1].services) || [];
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map' }, [
|
||||
E('h2', { 'class': 'cbi-map-title' }, '🔄 SaaS Relay'),
|
||||
E('div', { 'class': 'cbi-map-descr' },
|
||||
'Shared browser session proxy for team access to external SaaS services. Uses SecuBox authentication with mitmproxy cookie injection.'),
|
||||
|
||||
// Control buttons
|
||||
E('div', { 'style': 'margin: 20px 0; display: flex; gap: 10px; flex-wrap: wrap;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() { self.startRelay(); }
|
||||
}, '▶️ Start'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { self.stopRelay(); }
|
||||
}, '⏹️ Stop'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { self.setupRelay(); }
|
||||
}, '⚙️ Setup'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-add',
|
||||
'click': function() { self.addService(); }
|
||||
}, '➕ Add Service'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { self.showLog(); }
|
||||
}, '📋 View Log'),
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { self.refreshView(); }
|
||||
}, '🔄 Refresh')
|
||||
]),
|
||||
|
||||
// Status card container
|
||||
E('div', { 'id': 'status-container' }),
|
||||
|
||||
// Services section
|
||||
E('h3', { 'style': 'margin-top: 20px;' }, '🔗 Connected Services'),
|
||||
E('div', { 'id': 'services-container' })
|
||||
]);
|
||||
|
||||
// Render initial content
|
||||
var statusContainer = view.querySelector('#status-container');
|
||||
statusContainer.appendChild(this.renderStatusCard(status));
|
||||
|
||||
var servicesContainer = view.querySelector('#services-container');
|
||||
if (services.length === 0) {
|
||||
servicesContainer.appendChild(E('p', { 'style': 'color: #666; text-align: center; padding: 20px;' },
|
||||
'No services configured. Click "➕ Add Service" to get started.'));
|
||||
} else {
|
||||
services.forEach(function(svc) {
|
||||
servicesContainer.appendChild(self.renderServiceCard(svc));
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
461
package/secubox/luci-app-saas-relay/root/usr/libexec/rpcd/luci.saas-relay
Executable file
461
package/secubox/luci-app-saas-relay/root/usr/libexec/rpcd/luci.saas-relay
Executable file
@ -0,0 +1,461 @@
|
||||
#!/bin/sh
|
||||
# SaaS Relay RPCD backend
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
CONFIG="saas-relay"
|
||||
SAASCTL="/usr/sbin/saasctl"
|
||||
DATA_PATH="/srv/saas-relay"
|
||||
COOKIES_PATH="$DATA_PATH/cookies"
|
||||
LOG_PATH="$DATA_PATH/logs"
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
|
||||
# ===========================================
|
||||
# Status Methods
|
||||
# ===========================================
|
||||
|
||||
get_status() {
|
||||
json_init
|
||||
|
||||
local enabled=$(uci_get main.enabled) || enabled="0"
|
||||
local status=$(uci_get main.status) || status="stopped"
|
||||
local proxy_port=$(uci_get main.proxy_port) || proxy_port="8890"
|
||||
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_string "status" "$status"
|
||||
json_add_int "proxy_port" "$proxy_port"
|
||||
json_add_string "data_path" "$DATA_PATH"
|
||||
|
||||
# Count services and cookies
|
||||
local service_count=0
|
||||
local cookie_count=0
|
||||
local enabled_services=0
|
||||
|
||||
config_load "$CONFIG"
|
||||
_count_service() {
|
||||
local section="$1"
|
||||
local svc_enabled domain
|
||||
config_get domain "$section" domain
|
||||
[ -z "$domain" ] && return
|
||||
|
||||
service_count=$((service_count + 1))
|
||||
config_get svc_enabled "$section" enabled "0"
|
||||
[ "$svc_enabled" = "1" ] && enabled_services=$((enabled_services + 1))
|
||||
}
|
||||
config_foreach _count_service service
|
||||
|
||||
for f in "$COOKIES_PATH"/*.json; do
|
||||
[ -f "$f" ] || continue
|
||||
local count=$(grep -c '"' "$f" 2>/dev/null | awk '{print int($1/2)}')
|
||||
cookie_count=$((cookie_count + count))
|
||||
done
|
||||
|
||||
json_add_int "service_count" "$service_count"
|
||||
json_add_int "enabled_services" "$enabled_services"
|
||||
json_add_int "total_cookies" "$cookie_count"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Service Methods
|
||||
# ===========================================
|
||||
|
||||
list_services() {
|
||||
json_init
|
||||
json_add_array "services"
|
||||
|
||||
config_load "$CONFIG"
|
||||
|
||||
_add_service() {
|
||||
local section="$1"
|
||||
local enabled name emoji domain status cookie_domains auth_required last_check
|
||||
|
||||
config_get domain "$section" domain
|
||||
[ -z "$domain" ] && return
|
||||
|
||||
config_get enabled "$section" enabled "0"
|
||||
config_get name "$section" name "$section"
|
||||
config_get emoji "$section" emoji "🔗"
|
||||
config_get status "$section" status "disconnected"
|
||||
config_get cookie_domains "$section" cookie_domains "$domain"
|
||||
config_get auth_required "$section" auth_required "1"
|
||||
config_get last_check "$section" last_check "0"
|
||||
|
||||
# Count cookies for this service
|
||||
local cookie_file="$COOKIES_PATH/${section}.json"
|
||||
local cookie_count=0
|
||||
[ -f "$cookie_file" ] && cookie_count=$(grep -c '"' "$cookie_file" 2>/dev/null | awk '{print int($1/2)}')
|
||||
|
||||
json_add_object
|
||||
json_add_string "id" "$section"
|
||||
json_add_string "name" "$name"
|
||||
json_add_string "emoji" "$emoji"
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_string "status" "$status"
|
||||
json_add_int "cookie_count" "$cookie_count"
|
||||
json_add_string "cookie_domains" "$cookie_domains"
|
||||
json_add_boolean "auth_required" "$auth_required"
|
||||
json_add_int "last_check" "$last_check"
|
||||
json_close_object
|
||||
}
|
||||
|
||||
config_foreach _add_service service
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_enable() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service ID required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
uci set ${CONFIG}.${id}.enabled='1'
|
||||
uci commit ${CONFIG}
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service enabled"
|
||||
json_add_string "emoji" "🔓"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_disable() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service ID required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
uci set ${CONFIG}.${id}.enabled='0'
|
||||
uci commit ${CONFIG}
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service disabled"
|
||||
json_add_string "emoji" "🔐"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_add() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
json_get_var name name
|
||||
json_get_var domain domain
|
||||
json_get_var emoji emoji
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$id" ] || [ -z "$name" ] || [ -z "$domain" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "ID, name, and domain required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
[ -z "$emoji" ] && emoji="🔗"
|
||||
|
||||
uci set ${CONFIG}.${id}=service
|
||||
uci set ${CONFIG}.${id}.enabled='1'
|
||||
uci set ${CONFIG}.${id}.name="$name"
|
||||
uci set ${CONFIG}.${id}.emoji="$emoji"
|
||||
uci set ${CONFIG}.${id}.domain="$domain"
|
||||
uci set ${CONFIG}.${id}.cookie_domains="$domain,.$domain"
|
||||
uci set ${CONFIG}.${id}.auth_required='1'
|
||||
uci set ${CONFIG}.${id}.status='disconnected'
|
||||
uci commit ${CONFIG}
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service added: $name"
|
||||
json_add_string "emoji" "$emoji"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
service_delete() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var id id
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service ID required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
uci delete ${CONFIG}.${id} 2>/dev/null
|
||||
uci commit ${CONFIG}
|
||||
|
||||
# Remove cookies
|
||||
rm -f "$COOKIES_PATH/${id}.json"
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Service deleted"
|
||||
json_add_string "emoji" "🗑️"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Cookie Methods
|
||||
# ===========================================
|
||||
|
||||
list_cookies() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service service
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
|
||||
if [ -n "$service" ]; then
|
||||
local cookie_file="$COOKIES_PATH/${service}.json"
|
||||
json_add_string "service" "$service"
|
||||
|
||||
if [ -f "$cookie_file" ]; then
|
||||
local content=$(cat "$cookie_file")
|
||||
json_add_string "cookies" "$content"
|
||||
local count=$(grep -c '"' "$cookie_file" 2>/dev/null | awk '{print int($1/2)}')
|
||||
json_add_int "count" "$count"
|
||||
else
|
||||
json_add_string "cookies" "{}"
|
||||
json_add_int "count" 0
|
||||
fi
|
||||
else
|
||||
json_add_array "services"
|
||||
|
||||
for f in "$COOKIES_PATH"/*.json; do
|
||||
[ -f "$f" ] || continue
|
||||
local svc=$(basename "$f" .json)
|
||||
local count=$(grep -c '"' "$f" 2>/dev/null | awk '{print int($1/2)}')
|
||||
local size=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
||||
local modified=$(stat -c%Y "$f" 2>/dev/null || echo 0)
|
||||
|
||||
json_add_object
|
||||
json_add_string "service" "$svc"
|
||||
json_add_int "count" "$count"
|
||||
json_add_int "size" "$size"
|
||||
json_add_int "modified" "$modified"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
import_cookies() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service service
|
||||
json_get_var cookies cookies
|
||||
|
||||
json_init
|
||||
|
||||
if [ -z "$service" ] || [ -z "$cookies" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "Service and cookies required"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$COOKIES_PATH"
|
||||
echo "$cookies" > "$COOKIES_PATH/${service}.json"
|
||||
chmod 600 "$COOKIES_PATH/${service}.json"
|
||||
|
||||
local count=$(grep -c '"' "$COOKIES_PATH/${service}.json" 2>/dev/null | awk '{print int($1/2)}')
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Imported $count cookies"
|
||||
json_add_string "emoji" "🍪"
|
||||
json_add_int "count" "$count"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
clear_cookies() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var service service
|
||||
|
||||
json_init
|
||||
|
||||
if [ -n "$service" ]; then
|
||||
rm -f "$COOKIES_PATH/${service}.json"
|
||||
json_add_string "message" "Cleared cookies for $service"
|
||||
else
|
||||
rm -f "$COOKIES_PATH"/*.json
|
||||
json_add_string "message" "Cleared all cookies"
|
||||
fi
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "emoji" "🗑️"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Control Methods
|
||||
# ===========================================
|
||||
|
||||
start_relay() {
|
||||
json_init
|
||||
|
||||
local output=$("$SAASCTL" start 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "SaaS Relay started"
|
||||
json_add_string "emoji" "🔄"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
stop_relay() {
|
||||
json_init
|
||||
|
||||
local output=$("$SAASCTL" stop 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "SaaS Relay stopped"
|
||||
json_add_string "emoji" "🔌"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
setup_relay() {
|
||||
json_init
|
||||
|
||||
local output=$("$SAASCTL" setup 2>&1)
|
||||
local result=$?
|
||||
|
||||
if [ "$result" -eq 0 ]; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Setup complete"
|
||||
json_add_string "emoji" "✅"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$output"
|
||||
fi
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Activity Log
|
||||
# ===========================================
|
||||
|
||||
get_log() {
|
||||
read input
|
||||
json_load "$input"
|
||||
json_get_var lines lines
|
||||
|
||||
[ -z "$lines" ] && lines=50
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_array "entries"
|
||||
|
||||
if [ -f "$LOG_PATH/activity.log" ]; then
|
||||
tail -n "$lines" "$LOG_PATH/activity.log" | while read line; do
|
||||
json_add_string "" "$line"
|
||||
done
|
||||
fi
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
clear_log() {
|
||||
json_init
|
||||
|
||||
> "$LOG_PATH/activity.log" 2>/dev/null
|
||||
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Log cleared"
|
||||
json_add_string "emoji" "📋"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# Main Dispatcher
|
||||
# ===========================================
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
cat << 'EOF'
|
||||
{
|
||||
"status": {},
|
||||
"list_services": {},
|
||||
"service_enable": {"id": "str"},
|
||||
"service_disable": {"id": "str"},
|
||||
"service_add": {"id": "str", "name": "str", "domain": "str", "emoji": "str"},
|
||||
"service_delete": {"id": "str"},
|
||||
"list_cookies": {"service": "str"},
|
||||
"import_cookies": {"service": "str", "cookies": "str"},
|
||||
"clear_cookies": {"service": "str"},
|
||||
"start": {},
|
||||
"stop": {},
|
||||
"setup": {},
|
||||
"get_log": {"lines": "int"},
|
||||
"clear_log": {}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
status) get_status ;;
|
||||
list_services) list_services ;;
|
||||
service_enable) service_enable ;;
|
||||
service_disable) service_disable ;;
|
||||
service_add) service_add ;;
|
||||
service_delete) service_delete ;;
|
||||
list_cookies) list_cookies ;;
|
||||
import_cookies) import_cookies ;;
|
||||
clear_cookies) clear_cookies ;;
|
||||
start) start_relay ;;
|
||||
stop) stop_relay ;;
|
||||
setup) setup_relay ;;
|
||||
get_log) get_log ;;
|
||||
clear_log) clear_log ;;
|
||||
*) echo '{"error": "Unknown method"}' ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/services/saas-relay": {
|
||||
"title": "SaaS Relay",
|
||||
"order": 85,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "saas-relay/overview"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-saas-relay"],
|
||||
"uci": { "saas-relay": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
{
|
||||
"luci-app-saas-relay": {
|
||||
"description": "Grant access to SaaS Relay",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.saas-relay": [
|
||||
"status",
|
||||
"list_services",
|
||||
"list_cookies",
|
||||
"get_log"
|
||||
]
|
||||
},
|
||||
"uci": ["saas-relay"]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.saas-relay": [
|
||||
"service_enable",
|
||||
"service_disable",
|
||||
"service_add",
|
||||
"service_delete",
|
||||
"import_cookies",
|
||||
"clear_cookies",
|
||||
"start",
|
||||
"stop",
|
||||
"setup",
|
||||
"clear_log"
|
||||
]
|
||||
},
|
||||
"uci": ["saas-relay"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,11 +60,24 @@ return view.extend({
|
||||
}, s);
|
||||
});
|
||||
|
||||
// Login stats
|
||||
var lastLogin = user.last_login || 'never';
|
||||
var loginSuccess = user.login_success || 0;
|
||||
var loginFailure = user.login_failure || 0;
|
||||
|
||||
// Color code failures
|
||||
var failColor = loginFailure > 10 ? '#f44336' : (loginFailure > 0 ? '#ff9800' : '#4caf50');
|
||||
|
||||
return E('tr', {}, [
|
||||
E('td', {}, user.username),
|
||||
E('td', {}, user.email),
|
||||
E('td', {}, lastLogin),
|
||||
E('td', { 'style': 'text-align:center;' }, [
|
||||
E('span', { 'style': 'color:#4caf50;font-weight:bold;' }, String(loginSuccess)),
|
||||
E('span', {}, ' / '),
|
||||
E('span', { 'style': 'color:' + failColor + ';font-weight:bold;' }, String(loginFailure))
|
||||
]),
|
||||
E('td', {}, services),
|
||||
E('td', {}, user.enabled === '1' ? 'Yes' : 'No'),
|
||||
E('td', {}, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button',
|
||||
@ -271,8 +284,9 @@ return view.extend({
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('Username')),
|
||||
E('th', { 'class': 'th' }, _('Email')),
|
||||
E('th', { 'class': 'th' }, _('Last Login')),
|
||||
E('th', { 'class': 'th', 'style': 'text-align:center;' }, _('OK / Fail')),
|
||||
E('th', { 'class': 'th' }, _('Services')),
|
||||
E('th', { 'class': 'th' }, _('Enabled')),
|
||||
E('th', { 'class': 'th' }, _('Actions'))
|
||||
])
|
||||
].concat(userRows)) :
|
||||
|
||||
@ -11,7 +11,7 @@ Description: Unified AI security insights dashboard for SecuBox.
|
||||
DNS Guard, Network Anomaly, CVE Triage, and LocalRecall.
|
||||
Provides security posture scoring and AI-powered analysis.
|
||||
Filename: luci-app-ai-insights_1.0.0-r1_all.ipk
|
||||
Size: 11638
|
||||
Size: 11639
|
||||
|
||||
Package: luci-app-auth-guardian
|
||||
Version: 0.4.0-r3
|
||||
@ -23,7 +23,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Comprehensive authentication and session management with captive portal, OAuth2/OIDC integration, voucher system, and time-based access control
|
||||
Filename: luci-app-auth-guardian_0.4.0-r3_all.ipk
|
||||
Size: 12400
|
||||
Size: 12391
|
||||
|
||||
Package: luci-app-backup
|
||||
Version: 1.0.0-r1
|
||||
@ -35,7 +35,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI Backup Manager
|
||||
Filename: luci-app-backup_1.0.0-r1_all.ipk
|
||||
Size: 4534
|
||||
Size: 4537
|
||||
|
||||
Package: luci-app-bandwidth-manager
|
||||
Version: 0.5.0-r2
|
||||
@ -59,7 +59,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Dashboard for managing local CDN caching proxy on OpenWrt
|
||||
Filename: luci-app-cdn-cache_0.5.0-r3_all.ipk
|
||||
Size: 24255
|
||||
Size: 24257
|
||||
|
||||
Package: luci-app-client-guardian
|
||||
Version: 0.4.0-r7
|
||||
@ -71,7 +71,7 @@ Architecture: all
|
||||
Installed-Size: 276480
|
||||
Description: Network Access Control with client monitoring, zone management, captive portal, parental controls, and SMS/email alerts
|
||||
Filename: luci-app-client-guardian_0.4.0-r7_all.ipk
|
||||
Size: 52684
|
||||
Size: 52687
|
||||
|
||||
Package: luci-app-cloner
|
||||
Version: 1.0.0-r1
|
||||
@ -82,7 +82,7 @@ Architecture: all
|
||||
Installed-Size: 102400
|
||||
Description: SecuBox cloning station for building and deploying clone images
|
||||
Filename: luci-app-cloner_1.0.0-r1_all.ipk
|
||||
Size: 19433
|
||||
Size: 19435
|
||||
|
||||
Package: luci-app-config-advisor
|
||||
Version: 1.0.0-r1
|
||||
@ -105,7 +105,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI Cookie Tracker Dashboard
|
||||
Filename: luci-app-cookie-tracker_1.0.0-r1_all.ipk
|
||||
Size: 5653
|
||||
Size: 5660
|
||||
|
||||
Package: luci-app-crowdsec-dashboard
|
||||
Version: 0.7.0-r32
|
||||
@ -117,7 +117,7 @@ Architecture: all
|
||||
Installed-Size: 184320
|
||||
Description: Real-time security monitoring dashboard for CrowdSec on OpenWrt
|
||||
Filename: luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk
|
||||
Size: 34938
|
||||
Size: 34940
|
||||
|
||||
Package: luci-app-cve-triage
|
||||
Version: 1.0.0-r1
|
||||
@ -129,7 +129,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI CVE Triage Dashboard
|
||||
Filename: luci-app-cve-triage_1.0.0-r1_all.ipk
|
||||
Size: 5948
|
||||
Size: 5943
|
||||
|
||||
Package: luci-app-cyberfeed
|
||||
Version: 0.1.1-r1
|
||||
@ -153,7 +153,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: LuCI SecuBox Device Intelligence
|
||||
Filename: luci-app-device-intel_1.0.0-r1_all.ipk
|
||||
Size: 12047
|
||||
Size: 12049
|
||||
|
||||
Package: luci-app-dnsguard
|
||||
Version: 1.1.0-r1
|
||||
@ -172,7 +172,7 @@ Description: SecuBox DNS Guard provides privacy-focused DNS management with AI-
|
||||
- Real-time alerts and blocklist management
|
||||
- Domain analysis with LocalAI integration
|
||||
Filename: luci-app-dnsguard_1.1.0-r1_all.ipk
|
||||
Size: 12448
|
||||
Size: 12449
|
||||
|
||||
Package: luci-app-dns-provider
|
||||
Version: 1.0.0-r1
|
||||
@ -184,7 +184,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI DNS Provider Manager
|
||||
Filename: luci-app-dns-provider_1.0.0-r1_all.ipk
|
||||
Size: 7168
|
||||
Size: 7174
|
||||
|
||||
Package: luci-app-domoticz
|
||||
Version: 1.0.0-r1
|
||||
@ -196,7 +196,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI Domoticz Home Automation Configuration
|
||||
Filename: luci-app-domoticz_1.0.0-r1_all.ipk
|
||||
Size: 7123
|
||||
Size: 7118
|
||||
|
||||
Package: luci-app-exposure
|
||||
Version: 1.0.0-r3
|
||||
@ -208,7 +208,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: LuCI SecuBox Service Exposure Manager
|
||||
Filename: luci-app-exposure_1.0.0-r3_all.ipk
|
||||
Size: 11696
|
||||
Size: 11698
|
||||
|
||||
Package: luci-app-gitea
|
||||
Version: 1.0.0-r2
|
||||
@ -220,7 +220,7 @@ Architecture: all
|
||||
Installed-Size: 92160
|
||||
Description: Modern dashboard for Gitea Platform management on OpenWrt
|
||||
Filename: luci-app-gitea_1.0.0-r2_all.ipk
|
||||
Size: 16623
|
||||
Size: 16621
|
||||
|
||||
Package: luci-app-glances
|
||||
Version: 1.0.0-r2
|
||||
@ -232,7 +232,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: Modern dashboard for Glances system monitoring with SecuBox theme
|
||||
Filename: luci-app-glances_1.0.0-r2_all.ipk
|
||||
Size: 7022
|
||||
Size: 7017
|
||||
|
||||
Package: luci-app-gotosocial
|
||||
Version: 0.1.0-r1
|
||||
@ -244,7 +244,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: LuCI app for GoToSocial Fediverse Server
|
||||
Filename: luci-app-gotosocial_0.1.0-r1_all.ipk
|
||||
Size: 8207
|
||||
Size: 8210
|
||||
|
||||
Package: luci-app-haproxy
|
||||
Version: 1.0.0-r8
|
||||
@ -268,7 +268,7 @@ Architecture: all
|
||||
Installed-Size: 194560
|
||||
Description: Modern dashboard for Hexo static site generator on OpenWrt
|
||||
Filename: luci-app-hexojs_1.0.0-r3_all.ipk
|
||||
Size: 30452
|
||||
Size: 30450
|
||||
|
||||
Package: luci-app-iot-guard
|
||||
Version: 1.0.0-r1
|
||||
@ -280,7 +280,7 @@ Architecture: all
|
||||
Installed-Size: 61440
|
||||
Description: IoT device isolation and security monitoring interface
|
||||
Filename: luci-app-iot-guard_1.0.0-r1_all.ipk
|
||||
Size: 10535
|
||||
Size: 10538
|
||||
|
||||
Package: luci-app-jabber
|
||||
Version: 0
|
||||
@ -292,7 +292,7 @@ Architecture: all
|
||||
Installed-Size: 61440
|
||||
Description: LuCI Jabber/XMPP Server (Prosody)
|
||||
Filename: luci-app-jabber_0_all.ipk
|
||||
Size: 9304
|
||||
Size: 9308
|
||||
|
||||
Package: luci-app-jellyfin
|
||||
Version: 1.0.0-r1
|
||||
@ -304,7 +304,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: LuCI Jellyfin Media Server Configuration
|
||||
Filename: luci-app-jellyfin_1.0.0-r1_all.ipk
|
||||
Size: 10482
|
||||
Size: 10485
|
||||
|
||||
Package: luci-app-jitsi
|
||||
Version: 1.0.0-r1
|
||||
@ -316,7 +316,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI Jitsi Meet Configuration
|
||||
Filename: luci-app-jitsi_1.0.0-r1_all.ipk
|
||||
Size: 5175
|
||||
Size: 5173
|
||||
|
||||
Package: luci-app-ksm-manager
|
||||
Version: 0.4.0-r2
|
||||
@ -328,7 +328,7 @@ Architecture: all
|
||||
Installed-Size: 112640
|
||||
Description: Centralized cryptographic key management with hardware security module (HSM) support for Nitrokey and YubiKey devices. Provides secure key storage, certificate management, SSH key handling, and secret storage with audit logging.
|
||||
Filename: luci-app-ksm-manager_0.4.0-r2_all.ipk
|
||||
Size: 18779
|
||||
Size: 18777
|
||||
|
||||
Package: luci-app-localai
|
||||
Version: 0.1.0-r15
|
||||
@ -352,7 +352,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI LocalRecall AI Memory Dashboard
|
||||
Filename: luci-app-localrecall_1.0.0-r1_all.ipk
|
||||
Size: 8418
|
||||
Size: 8420
|
||||
|
||||
Package: luci-app-lyrion
|
||||
Version: 1.0.0-r1
|
||||
@ -364,7 +364,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI support for Lyrion Music Server
|
||||
Filename: luci-app-lyrion_1.0.0-r1_all.ipk
|
||||
Size: 6839
|
||||
Size: 6840
|
||||
|
||||
Package: luci-app-mac-guardian
|
||||
Version: 0.5.0-r1
|
||||
@ -376,7 +376,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI MAC Guardian - WiFi MAC Security Monitor
|
||||
Filename: luci-app-mac-guardian_0.5.0-r1_all.ipk
|
||||
Size: 6664
|
||||
Size: 6660
|
||||
|
||||
Package: luci-app-magicmirror2
|
||||
Version: 0.4.0-r6
|
||||
@ -388,7 +388,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Modern dashboard for MagicMirror2 smart display platform with module manager and SecuBox theme
|
||||
Filename: luci-app-magicmirror2_0.4.0-r6_all.ipk
|
||||
Size: 12361
|
||||
Size: 12358
|
||||
|
||||
Package: luci-app-mailinabox
|
||||
Version: 1.0.0-r1
|
||||
@ -400,7 +400,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI support for Mail-in-a-Box
|
||||
Filename: luci-app-mailinabox_1.0.0-r1_all.ipk
|
||||
Size: 5484
|
||||
Size: 5487
|
||||
|
||||
Package: luci-app-mailserver
|
||||
Version: 1.0.0-r1
|
||||
@ -412,7 +412,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI Mail Server Manager
|
||||
Filename: luci-app-mailserver_1.0.0-r1_all.ipk
|
||||
Size: 6511
|
||||
Size: 6509
|
||||
|
||||
Package: luci-app-master-link
|
||||
Version: 1.0.0-r1
|
||||
@ -424,7 +424,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI SecuBox Master-Link Mesh Management
|
||||
Filename: luci-app-master-link_1.0.0-r1_all.ipk
|
||||
Size: 6304
|
||||
Size: 6309
|
||||
|
||||
Package: luci-app-matrix
|
||||
Version: 1.0.0-r1
|
||||
@ -448,7 +448,7 @@ Architecture: all
|
||||
Installed-Size: 133120
|
||||
Description: Real-time detection and monitoring of streaming services (Netflix, YouTube, Spotify, etc.) with quality estimation, history tracking, and alerts. Supports nDPId local DPI and netifyd.
|
||||
Filename: luci-app-media-flow_0.6.4-r1_all.ipk
|
||||
Size: 25381
|
||||
Size: 25379
|
||||
|
||||
Package: luci-app-metablogizer
|
||||
Version: 1.1.0-r1
|
||||
@ -460,7 +460,7 @@ Architecture: all
|
||||
Installed-Size: 133120
|
||||
Description: LuCI support for MetaBlogizer Static Site Publisher
|
||||
Filename: luci-app-metablogizer_1.1.0-r1_all.ipk
|
||||
Size: 26207
|
||||
Size: 26203
|
||||
|
||||
Package: luci-app-metabolizer
|
||||
Version: 1.0.0-r2
|
||||
@ -472,7 +472,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI support for Metabolizer CMS
|
||||
Filename: luci-app-metabolizer_1.0.0-r2_all.ipk
|
||||
Size: 4821
|
||||
Size: 4819
|
||||
|
||||
Package: luci-app-mitmproxy
|
||||
Version: 0.5.0-r2
|
||||
@ -484,7 +484,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Modern dashboard for mitmproxy HTTPS traffic inspection with SecuBox theme
|
||||
Filename: luci-app-mitmproxy_0.5.0-r2_all.ipk
|
||||
Size: 13232
|
||||
Size: 13234
|
||||
|
||||
Package: luci-app-mmpm
|
||||
Version: 0.2.0-r3
|
||||
@ -496,7 +496,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: Web interface for MMPM - MagicMirror Package Manager
|
||||
Filename: luci-app-mmpm_0.2.0-r3_all.ipk
|
||||
Size: 7975
|
||||
Size: 7971
|
||||
|
||||
Package: luci-app-mqtt-bridge
|
||||
Version: 0.4.0-r4
|
||||
@ -508,7 +508,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: USB-to-MQTT IoT hub with SecuBox theme
|
||||
Filename: luci-app-mqtt-bridge_0.4.0-r4_all.ipk
|
||||
Size: 22689
|
||||
Size: 22687
|
||||
|
||||
Package: luci-app-ndpid
|
||||
Version: 1.1.2-r2
|
||||
@ -520,7 +520,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Modern dashboard for nDPId deep packet inspection on OpenWrt
|
||||
Filename: luci-app-ndpid_1.1.2-r2_all.ipk
|
||||
Size: 21704
|
||||
Size: 21701
|
||||
|
||||
Package: luci-app-netdata-dashboard
|
||||
Version: 0.5.0-r2
|
||||
@ -532,7 +532,7 @@ Architecture: all
|
||||
Installed-Size: 112640
|
||||
Description: Real-time system monitoring dashboard with Netdata integration for OpenWrt
|
||||
Filename: luci-app-netdata-dashboard_0.5.0-r2_all.ipk
|
||||
Size: 20558
|
||||
Size: 20559
|
||||
|
||||
Package: luci-app-network-anomaly
|
||||
Version: 1.0.0-r1
|
||||
@ -544,7 +544,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI Network Anomaly Detection Dashboard
|
||||
Filename: luci-app-network-anomaly_1.0.0-r1_all.ipk
|
||||
Size: 7641
|
||||
Size: 7643
|
||||
|
||||
Package: luci-app-network-modes
|
||||
Version: 0.5.0-r3
|
||||
@ -568,7 +568,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: Unified network services dashboard with DNS/hosts sync, CDN cache control, and WPAD auto-proxy configuration
|
||||
Filename: luci-app-network-tweaks_1.0.0-r7_all.ipk
|
||||
Size: 15949
|
||||
Size: 15947
|
||||
|
||||
Package: luci-app-nextcloud
|
||||
Version: 1.0.0-r1
|
||||
@ -580,7 +580,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: LuCI support for Nextcloud LXC
|
||||
Filename: luci-app-nextcloud_1.0.0-r1_all.ipk
|
||||
Size: 10347
|
||||
Size: 10348
|
||||
|
||||
Package: luci-app-ollama
|
||||
Version: 0.1.0-r1
|
||||
@ -592,7 +592,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Modern dashboard for Ollama LLM management on OpenWrt
|
||||
Filename: luci-app-ollama_0.1.0-r1_all.ipk
|
||||
Size: 14338
|
||||
Size: 14337
|
||||
|
||||
Package: luci-app-peertube
|
||||
Version: 0
|
||||
@ -604,7 +604,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI PeerTube Video Platform
|
||||
Filename: luci-app-peertube_0_all.ipk
|
||||
Size: 5760
|
||||
Size: 5757
|
||||
|
||||
Package: luci-app-picobrew
|
||||
Version: 1.0.0-r1
|
||||
@ -616,7 +616,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: Modern dashboard for PicoBrew Server management on OpenWrt
|
||||
Filename: luci-app-picobrew_1.0.0-r1_all.ipk
|
||||
Size: 9534
|
||||
Size: 9529
|
||||
|
||||
Package: luci-app-secubox
|
||||
Version: 0.7.1-r4
|
||||
@ -628,7 +628,7 @@ Architecture: all
|
||||
Installed-Size: 440320
|
||||
Description: Central control hub for all SecuBox modules. Provides unified dashboard, module status, system health monitoring, and quick actions.
|
||||
Filename: luci-app-secubox_0.7.1-r4_all.ipk
|
||||
Size: 82095
|
||||
Size: 82096
|
||||
|
||||
Package: luci-app-secubox-admin
|
||||
Version: 1.0.0-r19
|
||||
@ -639,7 +639,7 @@ Architecture: all
|
||||
Installed-Size: 337920
|
||||
Description: Unified admin control center for SecuBox appstore plugins with system monitoring
|
||||
Filename: luci-app-secubox-admin_1.0.0-r19_all.ipk
|
||||
Size: 58041
|
||||
Size: 58040
|
||||
|
||||
Package: luci-app-secubox-crowdsec
|
||||
Version: 1.0.0-r3
|
||||
@ -651,7 +651,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: LuCI SecuBox CrowdSec Dashboard
|
||||
Filename: luci-app-secubox-crowdsec_1.0.0-r3_all.ipk
|
||||
Size: 13918
|
||||
Size: 13916
|
||||
|
||||
Package: luci-app-secubox-mirror
|
||||
Version: 0.1.0-r1
|
||||
@ -675,7 +675,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: Real-time DSA switch port statistics, error monitoring, and network health diagnostics
|
||||
Filename: luci-app-secubox-netdiag_1.0.0-r1_all.ipk
|
||||
Size: 15347
|
||||
Size: 15344
|
||||
|
||||
Package: luci-app-secubox-netifyd
|
||||
Version: 1.2.1-r1
|
||||
@ -699,7 +699,7 @@ Architecture: all
|
||||
Installed-Size: 245760
|
||||
Description: LuCI SecuBox P2P Hub
|
||||
Filename: luci-app-secubox-p2p_0.1.0-r1_all.ipk
|
||||
Size: 46832
|
||||
Size: 46831
|
||||
|
||||
Package: luci-app-secubox-portal
|
||||
Version: 0.7.0-r3
|
||||
@ -711,7 +711,7 @@ Architecture: all
|
||||
Installed-Size: 194560
|
||||
Description: Unified entry point for all SecuBox applications with tabbed navigation
|
||||
Filename: luci-app-secubox-portal_0.7.0-r3_all.ipk
|
||||
Size: 41686
|
||||
Size: 41681
|
||||
|
||||
Package: luci-app-secubox-security-threats
|
||||
Version: 1.0.0-r4
|
||||
@ -723,7 +723,18 @@ Architecture: all
|
||||
Installed-Size: 61440
|
||||
Description: Unified dashboard integrating netifyd DPI threats with CrowdSec intelligence for real-time threat monitoring and automated blocking
|
||||
Filename: luci-app-secubox-security-threats_1.0.0-r4_all.ipk
|
||||
Size: 10659
|
||||
Size: 10657
|
||||
|
||||
Package: luci-app-secubox-users
|
||||
Version: 1.0.0-r1
|
||||
Depends: secubox-core-users
|
||||
Section: luci
|
||||
Maintainer: OpenWrt LuCI community
|
||||
Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI SecuBox User Management
|
||||
Filename: luci-app-secubox-users_1.0.0-r1_all.ipk
|
||||
Size: 5141
|
||||
|
||||
Package: luci-app-service-registry
|
||||
Version: 1.0.0-r1
|
||||
@ -735,7 +746,7 @@ Architecture: all
|
||||
Installed-Size: 194560
|
||||
Description: Unified service aggregation with HAProxy vhosts, Tor hidden services, and QR-coded landing page
|
||||
Filename: luci-app-service-registry_1.0.0-r1_all.ipk
|
||||
Size: 39952
|
||||
Size: 39951
|
||||
|
||||
Package: luci-app-simplex
|
||||
Version: 1.0.0-r1
|
||||
@ -747,7 +758,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI SimpleX Chat Server Configuration
|
||||
Filename: luci-app-simplex_1.0.0-r1_all.ipk
|
||||
Size: 7036
|
||||
Size: 7042
|
||||
|
||||
Package: luci-app-streamlit
|
||||
Version: 1.0.0-r11
|
||||
@ -759,7 +770,7 @@ Architecture: all
|
||||
Installed-Size: 112640
|
||||
Description: Multi-instance Streamlit management with Gitea integration
|
||||
Filename: luci-app-streamlit_1.0.0-r11_all.ipk
|
||||
Size: 20568
|
||||
Size: 20567
|
||||
|
||||
Package: luci-app-system-hub
|
||||
Version: 0.5.1-r4
|
||||
@ -771,7 +782,7 @@ Architecture: all
|
||||
Installed-Size: 327680
|
||||
Description: Central system control with monitoring, services, logs, and backup
|
||||
Filename: luci-app-system-hub_0.5.1-r4_all.ipk
|
||||
Size: 62082
|
||||
Size: 62081
|
||||
|
||||
Package: luci-app-threat-analyst
|
||||
Version: 1.0.0-r1
|
||||
@ -783,7 +794,7 @@ Architecture: all
|
||||
Installed-Size: 51200
|
||||
Description: LuCI Threat Analyst Dashboard
|
||||
Filename: luci-app-threat-analyst_1.0.0-r1_all.ipk
|
||||
Size: 10144
|
||||
Size: 10145
|
||||
|
||||
Package: luci-app-tor
|
||||
Version: 1.0.0-r1
|
||||
@ -795,7 +806,7 @@ Architecture: all
|
||||
Installed-Size: 92160
|
||||
Description: Modern dashboard for Tor anonymization on OpenWrt
|
||||
Filename: luci-app-tor_1.0.0-r1_all.ipk
|
||||
Size: 17822
|
||||
Size: 17816
|
||||
|
||||
Package: luci-app-tor-shield
|
||||
Version: 1.0.0-r10
|
||||
@ -807,7 +818,7 @@ Architecture: all
|
||||
Installed-Size: 122880
|
||||
Description: Modern dashboard for Tor anonymization on OpenWrt
|
||||
Filename: luci-app-tor-shield_1.0.0-r10_all.ipk
|
||||
Size: 22766
|
||||
Size: 22764
|
||||
|
||||
Package: luci-app-traffic-shaper
|
||||
Version: 0.4.0-r2
|
||||
@ -819,7 +830,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: Advanced traffic shaping with TC/CAKE for precise bandwidth control
|
||||
Filename: luci-app-traffic-shaper_0.4.0-r2_all.ipk
|
||||
Size: 14591
|
||||
Size: 14587
|
||||
|
||||
Package: luci-app-vhost-manager
|
||||
Version: 0.5.0-r5
|
||||
@ -831,7 +842,7 @@ Architecture: all
|
||||
Installed-Size: 153600
|
||||
Description: Nginx reverse proxy manager with Let's Encrypt SSL certificates, authentication, and WebSocket support
|
||||
Filename: luci-app-vhost-manager_0.5.0-r5_all.ipk
|
||||
Size: 26284
|
||||
Size: 26283
|
||||
|
||||
Package: luci-app-voip
|
||||
Version: 1.0.0-r1
|
||||
@ -843,7 +854,7 @@ Architecture: all
|
||||
Installed-Size: 81920
|
||||
Description: LuCI VoIP PBX Management
|
||||
Filename: luci-app-voip_1.0.0-r1_all.ipk
|
||||
Size: 11046
|
||||
Size: 11043
|
||||
|
||||
Package: luci-app-vortex-dns
|
||||
Version: 1.0.0-r1
|
||||
@ -855,7 +866,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: LuCI Vortex DNS Dashboard
|
||||
Filename: luci-app-vortex-dns_1.0.0-r1_all.ipk
|
||||
Size: 6078
|
||||
Size: 6079
|
||||
|
||||
Package: luci-app-vortex-firewall
|
||||
Version: 1.0.0-r1
|
||||
@ -867,7 +878,7 @@ Architecture: all
|
||||
Installed-Size: 30720
|
||||
Description: LuCI Vortex DNS Firewall Dashboard
|
||||
Filename: luci-app-vortex-firewall_1.0.0-r1_all.ipk
|
||||
Size: 5451
|
||||
Size: 5450
|
||||
|
||||
Package: luci-app-wazuh
|
||||
Version: 1.0.0-r1
|
||||
@ -878,7 +889,7 @@ Architecture: all
|
||||
Installed-Size: 71680
|
||||
Description: Unified security monitoring dashboard for Wazuh SIEM/XDR integration
|
||||
Filename: luci-app-wazuh_1.0.0-r1_all.ipk
|
||||
Size: 11072
|
||||
Size: 11073
|
||||
|
||||
Package: luci-app-wireguard-dashboard
|
||||
Version: 0.7.0-r5
|
||||
@ -890,7 +901,7 @@ Architecture: all
|
||||
Installed-Size: 215040
|
||||
Description: Modern dashboard for WireGuard VPN monitoring on OpenWrt
|
||||
Filename: luci-app-wireguard-dashboard_0.7.0-r5_all.ipk
|
||||
Size: 42291
|
||||
Size: 42286
|
||||
|
||||
Package: luci-app-zigbee2mqtt
|
||||
Version: 1.0.0-r2
|
||||
@ -902,7 +913,7 @@ Architecture: all
|
||||
Installed-Size: 40960
|
||||
Description: Graphical interface for managing the Zigbee2MQTT LXC application.
|
||||
Filename: luci-app-zigbee2mqtt_1.0.0-r2_all.ipk
|
||||
Size: 6591
|
||||
Size: 6592
|
||||
|
||||
Package: luci-theme-secubox
|
||||
Version: 0.4.7-r1
|
||||
@ -914,7 +925,7 @@ Architecture: all
|
||||
Installed-Size: 450560
|
||||
Description: Global CyberMood design system (CSS/JS/i18n) shared by all SecuBox dashboards.
|
||||
Filename: luci-theme-secubox_0.4.7-r1_all.ipk
|
||||
Size: 110239
|
||||
Size: 110238
|
||||
|
||||
Package: secubox-app
|
||||
Version: 1.0.0-r2
|
||||
@ -925,7 +936,7 @@ Installed-Size: 92160
|
||||
Description: Command line helper for SecuBox App Store manifests. Installs /usr/sbin/secubox-app
|
||||
and ships the default manifests under /usr/share/secubox/plugins/.
|
||||
Filename: secubox-app_1.0.0-r2_all.ipk
|
||||
Size: 11182
|
||||
Size: 11183
|
||||
|
||||
Package: secubox-app-adguardhome
|
||||
Version: 1.0.0-r2
|
||||
@ -939,7 +950,7 @@ Description: Installer, configuration, and service manager for running AdGuard
|
||||
inside Docker on SecuBox-powered OpenWrt systems. Network-wide ad blocker
|
||||
with DNS-over-HTTPS/TLS support and detailed analytics.
|
||||
Filename: secubox-app-adguardhome_1.0.0-r2_all.ipk
|
||||
Size: 2883
|
||||
Size: 2880
|
||||
|
||||
Package: secubox-app-auth-logger
|
||||
Version: 1.2.2-r1
|
||||
@ -957,7 +968,7 @@ Description: Logs authentication failures from LuCI/rpcd and Dropbear SSH
|
||||
- JavaScript hook to intercept login failures
|
||||
- CrowdSec parser and bruteforce scenario
|
||||
Filename: secubox-app-auth-logger_1.2.2-r1_all.ipk
|
||||
Size: 9377
|
||||
Size: 9374
|
||||
|
||||
Package: secubox-app-crowdsec-custom
|
||||
Version: 1.1.0-r1
|
||||
@ -1009,7 +1020,7 @@ Description: SecuBox CrowdSec Firewall Bouncer for OpenWrt.
|
||||
- Automatic restart on firewall reload
|
||||
- procd service management
|
||||
Filename: secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk
|
||||
Size: 5049325
|
||||
Size: 5049324
|
||||
|
||||
Package: secubox-app-cyberfeed
|
||||
Version: 0.2.1-r1
|
||||
@ -1023,7 +1034,7 @@ Description: Cyberpunk-themed RSS feed aggregator for OpenWrt/SecuBox.
|
||||
Features emoji injection, neon styling, and RSS-Bridge support
|
||||
for social media feeds (Facebook, Twitter, Mastodon).
|
||||
Filename: secubox-app-cyberfeed_0.2.1-r1_all.ipk
|
||||
Size: 12453
|
||||
Size: 12450
|
||||
|
||||
Package: secubox-app-device-intel
|
||||
Version: 1.0.0-r1
|
||||
@ -1037,7 +1048,7 @@ Description: Unified device inventory aggregating mac-guardian, client-guardian
|
||||
P2P mesh, and exposure scanner data. Includes heuristic classification
|
||||
and pluggable emulator modules for MQTT, Zigbee, and USB devices.
|
||||
Filename: secubox-app-device-intel_1.0.0-r1_all.ipk
|
||||
Size: 13108
|
||||
Size: 13104
|
||||
|
||||
Package: secubox-app-dns-provider
|
||||
Version: 1.0.0-r1
|
||||
@ -1051,7 +1062,7 @@ Description: Programmatic DNS record management via provider APIs (OVH, Gandi
|
||||
Cloudflare). Provides the dnsctl CLI for record CRUD, zone sync
|
||||
DNS propagation verification, and ACME DNS-01 challenge support.
|
||||
Filename: secubox-app-dns-provider_1.0.0-r1_all.ipk
|
||||
Size: 8261
|
||||
Size: 8257
|
||||
|
||||
Package: secubox-app-domoticz
|
||||
Version: 1.0.0-r4
|
||||
@ -1064,7 +1075,7 @@ Installed-Size: 30720
|
||||
Description: Installer, configuration, and service manager for running Domoticz
|
||||
inside an LXC Alpine container on SecuBox-powered OpenWrt systems.
|
||||
Filename: secubox-app-domoticz_1.0.0-r4_all.ipk
|
||||
Size: 7503
|
||||
Size: 7507
|
||||
|
||||
Package: secubox-app-exposure
|
||||
Version: 1.0.0-r1
|
||||
@ -1079,7 +1090,7 @@ Description: Unified service exposure manager for SecuBox.
|
||||
- Dynamic Tor hidden service management
|
||||
- HAProxy SSL reverse proxy configuration
|
||||
Filename: secubox-app-exposure_1.0.0-r1_all.ipk
|
||||
Size: 9147
|
||||
Size: 9145
|
||||
|
||||
Package: secubox-app-gitea
|
||||
Version: 1.0.0-r5
|
||||
@ -1102,7 +1113,7 @@ Description: Gitea Git Platform - Self-hosted lightweight Git service
|
||||
Runs in LXC container with Alpine Linux.
|
||||
Configure in /etc/config/gitea.
|
||||
Filename: secubox-app-gitea_1.0.0-r5_all.ipk
|
||||
Size: 9439
|
||||
Size: 9441
|
||||
|
||||
Package: secubox-app-gk2hub
|
||||
Version: 0.1.0-r1
|
||||
@ -1116,7 +1127,7 @@ Description: Dynamic landing page generator for GK2 SecuBox services.
|
||||
Aggregates Streamlit apps, MetaBlogizer sites, and infrastructure
|
||||
services into a single service directory page.
|
||||
Filename: secubox-app-gk2hub_0.1.0-r1_all.ipk
|
||||
Size: 4064
|
||||
Size: 4059
|
||||
|
||||
Package: secubox-app-glances
|
||||
Version: 1.0.0-r1
|
||||
@ -1139,7 +1150,7 @@ Description: Glances - Cross-platform system monitoring tool for SecuBox.
|
||||
Runs in LXC container for isolation and security.
|
||||
Configure in /etc/config/glances.
|
||||
Filename: secubox-app-glances_1.0.0-r1_all.ipk
|
||||
Size: 6142
|
||||
Size: 6137
|
||||
|
||||
Package: secubox-app-guacamole
|
||||
Version: 1.0.0-r1
|
||||
@ -1153,7 +1164,7 @@ Description: Apache Guacamole clientless remote desktop gateway.
|
||||
Runs in an LXC Debian container with guacd and Tomcat.
|
||||
Supports SSH, VNC, and RDP connections via web browser.
|
||||
Filename: secubox-app-guacamole_1.0.0-r1_all.ipk
|
||||
Size: 6948
|
||||
Size: 6945
|
||||
|
||||
Package: secubox-app-haproxy
|
||||
Version: 1.0.0-r24
|
||||
@ -1173,7 +1184,7 @@ Description: HAProxy load balancer and reverse proxy running in an LXC containe
|
||||
- Stats dashboard
|
||||
- Rate limiting and ACLs
|
||||
Filename: secubox-app-haproxy_1.0.0-r24_all.ipk
|
||||
Size: 22012
|
||||
Size: 22008
|
||||
|
||||
Package: secubox-app-hexojs
|
||||
Version: 1.0.0-r8
|
||||
@ -1182,7 +1193,7 @@ License: MIT
|
||||
Section: utils
|
||||
Maintainer: CyberMind Studio <contact@cybermind.fr>
|
||||
Architecture: all
|
||||
Installed-Size: 501760
|
||||
Installed-Size: 522240
|
||||
Description: Hexo CMS - Self-hosted static blog generator for OpenWrt
|
||||
|
||||
Features:
|
||||
@ -1197,7 +1208,7 @@ Description: Hexo CMS - Self-hosted static blog generator for OpenWrt
|
||||
Runs in LXC container with Alpine Linux.
|
||||
Configure in /etc/config/hexojs.
|
||||
Filename: secubox-app-hexojs_1.0.0-r8_all.ipk
|
||||
Size: 94937
|
||||
Size: 100060
|
||||
|
||||
Package: secubox-app-jabber
|
||||
Version: 1.0.0-r1
|
||||
@ -1211,7 +1222,7 @@ Description: Jabber/XMPP instant messaging server based on Prosody.
|
||||
Runs in an LXC Debian container with full XMPP support.
|
||||
Features multi-user chat (MUC), file uploads, and S2S federation.
|
||||
Filename: secubox-app-jabber_1.0.0-r1_all.ipk
|
||||
Size: 13277
|
||||
Size: 13276
|
||||
|
||||
Package: secubox-app-jellyfin
|
||||
Version: 3.0.0-r1
|
||||
@ -1224,7 +1235,7 @@ Installed-Size: 20480
|
||||
Description: Jellyfin media server running in LXC container.
|
||||
Free media server for streaming movies, TV shows, music, and photos.
|
||||
Filename: secubox-app-jellyfin_3.0.0-r1_all.ipk
|
||||
Size: 4748
|
||||
Size: 4755
|
||||
|
||||
Package: secubox-app-jitsi
|
||||
Version: 1.0.0-r1
|
||||
@ -1249,7 +1260,7 @@ Description: Jitsi Meet - Secure, fully featured video conferencing for SecuBox
|
||||
Integrates with HAProxy for SSL termination.
|
||||
Configure in /etc/config/jitsi.
|
||||
Filename: secubox-app-jitsi_1.0.0-r1_all.ipk
|
||||
Size: 8927
|
||||
Size: 8924
|
||||
|
||||
Package: secubox-app-localai
|
||||
Version: 3.9.0-r1
|
||||
@ -1271,7 +1282,7 @@ Description: LocalAI native binary package for OpenWrt.
|
||||
|
||||
API: http://<router-ip>:8081/v1
|
||||
Filename: secubox-app-localai_3.9.0-r1_all.ipk
|
||||
Size: 5848
|
||||
Size: 5841
|
||||
|
||||
Package: secubox-app-localai-wb
|
||||
Version: 2.25.0-r1
|
||||
@ -1315,7 +1326,7 @@ Description: Lyrion Media Server (formerly Logitech Media Server / Squeezebox S
|
||||
Auto-detects available runtime, preferring LXC for lower resource usage.
|
||||
Configure runtime in /etc/config/lyrion.
|
||||
Filename: secubox-app-lyrion_2.0.2-r1_all.ipk
|
||||
Size: 8130
|
||||
Size: 8124
|
||||
|
||||
Package: secubox-app-mac-guardian
|
||||
Version: 0.5.0-r1
|
||||
@ -1330,7 +1341,7 @@ Description: WiFi MAC address security monitor for SecuBox.
|
||||
and spoofing. Integrates with CrowdSec and provides
|
||||
real-time hostapd hotplug detection.
|
||||
Filename: secubox-app-mac-guardian_0.5.0-r1_all.ipk
|
||||
Size: 12098
|
||||
Size: 12097
|
||||
|
||||
Package: secubox-app-magicmirror2
|
||||
Version: 0.4.0-r8
|
||||
@ -1377,7 +1388,7 @@ Description: Complete email server solution using docker-mailserver for SecuBox
|
||||
|
||||
Commands: mailinaboxctl --help
|
||||
Filename: secubox-app-mailinabox_2.0.0-r1_all.ipk
|
||||
Size: 7569
|
||||
Size: 7574
|
||||
|
||||
Package: secubox-app-mailserver
|
||||
Version: 2.0.0-r1
|
||||
@ -1390,7 +1401,7 @@ Installed-Size: 20480
|
||||
Description: Postfix + Dovecot mail server running in LXC container.
|
||||
Supports IMAP/SMTP with SSL/TLS.
|
||||
Filename: secubox-app-mailserver_2.0.0-r1_all.ipk
|
||||
Size: 5697
|
||||
Size: 5693
|
||||
|
||||
Package: secubox-app-matrix
|
||||
Version: 1.0.0-r1
|
||||
@ -1425,7 +1436,7 @@ Description: Metabolizer Blog Pipeline - Integrated CMS with Git-based workflow
|
||||
|
||||
Pipeline: Edit in Streamlit -> Push to Gitea -> Build with Hexo -> Publish
|
||||
Filename: secubox-app-metabolizer_1.0.0-r3_all.ipk
|
||||
Size: 13980
|
||||
Size: 13975
|
||||
|
||||
Package: secubox-app-mitmproxy
|
||||
Version: 0.5.0-r19
|
||||
@ -1452,7 +1463,7 @@ Description: mitmproxy - Interactive HTTPS proxy for SecuBox-powered OpenWrt sy
|
||||
Runs in LXC container for isolation and security.
|
||||
Configure in /etc/config/mitmproxy.
|
||||
Filename: secubox-app-mitmproxy_0.5.0-r19_all.ipk
|
||||
Size: 22958
|
||||
Size: 22957
|
||||
|
||||
Package: secubox-app-mmpm
|
||||
Version: 0.2.0-r5
|
||||
@ -1473,7 +1484,7 @@ Description: MMPM (MagicMirror Package Manager) for SecuBox.
|
||||
|
||||
Runs inside the MagicMirror2 LXC container.
|
||||
Filename: secubox-app-mmpm_0.2.0-r5_all.ipk
|
||||
Size: 3978
|
||||
Size: 3979
|
||||
|
||||
Package: secubox-app-nextcloud
|
||||
Version: 1.0.0-r2
|
||||
@ -1509,7 +1520,7 @@ Description: Ollama - Simple local LLM runtime for SecuBox-powered OpenWrt syst
|
||||
Runs in Docker/Podman container.
|
||||
Configure in /etc/config/ollama.
|
||||
Filename: secubox-app-ollama_0.1.0-r1_all.ipk
|
||||
Size: 5735
|
||||
Size: 5736
|
||||
|
||||
Package: secubox-app-picobrew
|
||||
Version: 1.0.0-r7
|
||||
@ -1544,7 +1555,7 @@ Installed-Size: 20480
|
||||
Description: Self-hosted RustDesk relay server for remote desktop access.
|
||||
Downloads and manages hbbs (ID server) and hbbr (relay server) binaries.
|
||||
Filename: secubox-app-rustdesk_1.0.0-r1_all.ipk
|
||||
Size: 4469
|
||||
Size: 4467
|
||||
|
||||
Package: secubox-app-simplex
|
||||
Version: 1.0.0-r1
|
||||
@ -1568,7 +1579,7 @@ Description: SimpleX Chat self-hosted messaging infrastructure for SecuBox.
|
||||
Privacy-first messaging relay that you control.
|
||||
Configure in /etc/config/simplex.
|
||||
Filename: secubox-app-simplex_1.0.0-r1_all.ipk
|
||||
Size: 9369
|
||||
Size: 9367
|
||||
|
||||
Package: secubox-app-smbfs
|
||||
Version: 1.0.0-r1
|
||||
@ -1582,7 +1593,7 @@ Description: SMB/CIFS remote directory mount manager for SecuBox. Manages share
|
||||
network mounts for media servers (Jellyfin, Lyrion), backups, and
|
||||
general-purpose remote storage over SMB/CIFS protocol.
|
||||
Filename: secubox-app-smbfs_1.0.0-r1_all.ipk
|
||||
Size: 5269
|
||||
Size: 5268
|
||||
|
||||
Package: secubox-app-streamlit
|
||||
Version: 1.0.0-r5
|
||||
@ -1609,7 +1620,7 @@ Description: Streamlit App Platform - Self-hosted Python data app platform
|
||||
|
||||
Configure in /etc/config/streamlit.
|
||||
Filename: secubox-app-streamlit_1.0.0-r5_all.ipk
|
||||
Size: 16517
|
||||
Size: 16519
|
||||
|
||||
Package: secubox-app-tor
|
||||
Version: 1.0.0-r1
|
||||
@ -1632,7 +1643,7 @@ Description: SecuBox Tor Shield - One-click Tor anonymization for OpenWrt
|
||||
|
||||
Configure in /etc/config/tor-shield.
|
||||
Filename: secubox-app-tor_1.0.0-r1_all.ipk
|
||||
Size: 7372
|
||||
Size: 7370
|
||||
|
||||
Package: secubox-app-voip
|
||||
Version: 1.0.0-r1
|
||||
@ -1646,7 +1657,7 @@ Description: VoIP PBX solution with Asterisk in LXC container.
|
||||
Features OVH SIP trunk integration, WebRTC support
|
||||
and Jabber/XMPP relay for SMS and voicemail notifications.
|
||||
Filename: secubox-app-voip_1.0.0-r1_all.ipk
|
||||
Size: 11955
|
||||
Size: 11954
|
||||
|
||||
Package: secubox-app-webapp
|
||||
Version: 1.5.0-r7
|
||||
@ -1664,7 +1675,7 @@ Description: SecuBox Control Center Dashboard - A web-based dashboard for monit
|
||||
- Service management
|
||||
- Network interface control
|
||||
Filename: secubox-app-webapp_1.5.0-r7_all.ipk
|
||||
Size: 39176
|
||||
Size: 39177
|
||||
|
||||
Package: secubox-app-zigbee2mqtt
|
||||
Version: 1.0.0-r3
|
||||
@ -1677,7 +1688,7 @@ Installed-Size: 20480
|
||||
Description: Installer, configuration, and service manager for running Zigbee2MQTT
|
||||
inside an Alpine LXC container on SecuBox-powered OpenWrt systems.
|
||||
Filename: secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk
|
||||
Size: 5533
|
||||
Size: 5539
|
||||
|
||||
Package: secubox-config-advisor
|
||||
Version: 0.1.0-r1
|
||||
@ -1696,7 +1707,7 @@ Description: AI-powered configuration security advisor for SecuBox.
|
||||
- LocalAI integration for intelligent analysis
|
||||
- Automated remediation suggestions
|
||||
Filename: secubox-config-advisor_0.1.0-r1_all.ipk
|
||||
Size: 14850
|
||||
Size: 14849
|
||||
|
||||
Package: secubox-content-pkg
|
||||
Version: 1.0.0-r1
|
||||
@ -1709,7 +1720,7 @@ Installed-Size: 20480
|
||||
Description: Package Metablogizer sites and Streamlit apps as IPKs for P2P distribution.
|
||||
Auto-publishes content to the mesh feed for peer auto-sync.
|
||||
Filename: secubox-content-pkg_1.0.0-r1_all.ipk
|
||||
Size: 3914
|
||||
Size: 3904
|
||||
|
||||
Package: secubox-cookie-tracker
|
||||
Version: 1.0.0-r1
|
||||
@ -1732,7 +1743,7 @@ Description: Cookie Tracker for SecuBox InterceptoR.
|
||||
|
||||
Works with secubox-app-mitmproxy for transparent interception.
|
||||
Filename: secubox-cookie-tracker_1.0.0-r1_all.ipk
|
||||
Size: 10645
|
||||
Size: 10641
|
||||
|
||||
Package: secubox-core
|
||||
Version: 0.10.0-r16
|
||||
@ -1752,7 +1763,7 @@ Description: SecuBox Core Framework provides the foundational infrastructure fo
|
||||
- Unified CLI interface
|
||||
- ubus RPC backend
|
||||
Filename: secubox-core_0.10.0-r16_all.ipk
|
||||
Size: 123047
|
||||
Size: 123045
|
||||
|
||||
Package: secubox-cve-triage
|
||||
Version: 1.0.0-r1
|
||||
@ -1772,7 +1783,7 @@ Description: AI-powered CVE analysis and vulnerability management agent for Sec
|
||||
- Approval workflow for patch recommendations
|
||||
- LXC and Docker package monitoring
|
||||
Filename: secubox-cve-triage_1.0.0-r1_all.ipk
|
||||
Size: 11826
|
||||
Size: 11827
|
||||
|
||||
Package: secubox-dns-guard
|
||||
Version: 1.0.0-r1
|
||||
@ -1791,7 +1802,7 @@ Description: SecuBox DNS Guard provides AI-powered DNS anomaly detection using
|
||||
- Unusual TLD pattern detection
|
||||
- Automatic blocklist generation with approval workflow
|
||||
Filename: secubox-dns-guard_1.0.0-r1_all.ipk
|
||||
Size: 12488
|
||||
Size: 12485
|
||||
|
||||
Package: secubox-identity
|
||||
Version: 0.1.0-r1
|
||||
@ -1810,7 +1821,7 @@ Description: Decentralized identity management for SecuBox mesh nodes.
|
||||
- Peer identity verification
|
||||
- Trust scoring integration
|
||||
Filename: secubox-identity_0.1.0-r1_all.ipk
|
||||
Size: 8089
|
||||
Size: 8088
|
||||
|
||||
Package: secubox-iot-guard
|
||||
Version: 1.0.0-r1
|
||||
@ -1826,7 +1837,7 @@ Description: IoT device isolation, classification, and security monitoring.
|
||||
risk scoring. Orchestrates Client Guardian, MAC Guardian
|
||||
Vortex Firewall, and Bandwidth Manager for IoT protection.
|
||||
Filename: secubox-iot-guard_1.0.0-r1_all.ipk
|
||||
Size: 13374
|
||||
Size: 13365
|
||||
|
||||
Package: secubox-localrecall
|
||||
Version: 1.0.0-r1
|
||||
@ -1841,7 +1852,7 @@ Description: Persistent memory system for SecuBox AI agents.
|
||||
for context across sessions. LocalAI integration for
|
||||
semantic search and AI-powered summarization.
|
||||
Filename: secubox-localrecall_1.0.0-r1_all.ipk
|
||||
Size: 7802
|
||||
Size: 7796
|
||||
|
||||
Package: secubox-master-link
|
||||
Version: 1.0.0-r1
|
||||
@ -1863,7 +1874,7 @@ Description: Secure mesh onboarding for SecuBox nodes via master/peer link.
|
||||
|
||||
Configure in /etc/config/master-link.
|
||||
Filename: secubox-master-link_1.0.0-r1_all.ipk
|
||||
Size: 15038
|
||||
Size: 15034
|
||||
|
||||
Package: secubox-mcp-server
|
||||
Version: 1.0.0-r1
|
||||
@ -1891,7 +1902,7 @@ Description: Model Context Protocol (MCP) server for SecuBox.
|
||||
- ai.explain_ban (Explain CrowdSec decisions)
|
||||
- ai.security_posture (Security assessment)
|
||||
Filename: secubox-mcp-server_1.0.0-r1_all.ipk
|
||||
Size: 11433
|
||||
Size: 11427
|
||||
|
||||
Package: secubox-mirrornet
|
||||
Version: 0.1.0-r1
|
||||
@ -1909,7 +1920,7 @@ Description: MirrorNet core mesh orchestration for SecuBox.
|
||||
- Mesh health monitoring and anomaly detection
|
||||
- DID-based identity (did:plc compatible)
|
||||
Filename: secubox-mirrornet_0.1.0-r1_all.ipk
|
||||
Size: 15306
|
||||
Size: 15303
|
||||
|
||||
Package: secubox-network-anomaly
|
||||
Version: 1.0.0-r1
|
||||
@ -1924,7 +1935,7 @@ Description: AI-powered network anomaly detection for SecuBox.
|
||||
DNS anomalies, and protocol anomalies using statistical
|
||||
analysis and optional LocalAI integration.
|
||||
Filename: secubox-network-anomaly_1.0.0-r1_all.ipk
|
||||
Size: 6170
|
||||
Size: 6163
|
||||
|
||||
Package: secubox-p2p
|
||||
Version: 0.6.0-r3
|
||||
@ -1943,7 +1954,7 @@ Description: SecuBox P2P Hub backend providing peer discovery, mesh networking
|
||||
and MirrorBox NetMesh Catalog for cross-chain distributed service
|
||||
registry with HAProxy vhost discovery and multi-endpoint access URLs.
|
||||
Filename: secubox-p2p_0.6.0-r3_all.ipk
|
||||
Size: 47872
|
||||
Size: 47860
|
||||
|
||||
Package: secubox-p2p-intel
|
||||
Version: 0.1.0-r1
|
||||
@ -1962,7 +1973,7 @@ Description: Decentralized threat intelligence sharing for SecuBox mesh.
|
||||
- CrowdSec and mitmproxy integration
|
||||
- Automatic firewall rule application
|
||||
Filename: secubox-p2p-intel_0.1.0-r1_all.ipk
|
||||
Size: 9798
|
||||
Size: 9799
|
||||
|
||||
Package: secubox-threat-analyst
|
||||
Version: 1.0.0-r1
|
||||
@ -1981,7 +1992,7 @@ Description: Autonomous threat analysis agent for SecuBox.
|
||||
|
||||
Part of SecuBox AI Gateway (Couche 2).
|
||||
Filename: secubox-threat-analyst_1.0.0-r1_all.ipk
|
||||
Size: 9867
|
||||
Size: 9864
|
||||
|
||||
Package: secubox-vortex-dns
|
||||
Version: 1.0.0-r1
|
||||
@ -2000,7 +2011,7 @@ Description: Meshed multi-dynamic subdomain delegation system for SecuBox.
|
||||
- Gossip-based exposure config sync
|
||||
- Submastering for nested hierarchies
|
||||
Filename: secubox-vortex-dns_1.0.0-r1_all.ipk
|
||||
Size: 5443
|
||||
Size: 5439
|
||||
|
||||
Package: secubox-vortex-firewall
|
||||
Version: 1.0.0-r1
|
||||
@ -2015,5 +2026,5 @@ Description: DNS-level threat blocking with x47 impact multiplier.
|
||||
any connection is established. Integrates threat feeds from
|
||||
abuse.ch, OpenPhish, and local DNS Guard detections.
|
||||
Filename: secubox-vortex-firewall_1.0.0-r1_all.ipk
|
||||
Size: 8898
|
||||
Size: 8892
|
||||
|
||||
|
||||
Binary file not shown.
@ -1,12 +1,12 @@
|
||||
{
|
||||
"feed_url": "/secubox-feed",
|
||||
"generated": "2026-02-19T18:57:57+01:00",
|
||||
"generated": "2026-02-20T08:56:23+01:00",
|
||||
"packages": [
|
||||
{
|
||||
"name": "luci-app-ai-insights",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-ai-insights_1.0.0-r1_all.ipk",
|
||||
"size": 11638,
|
||||
"size": 11639,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -18,7 +18,7 @@
|
||||
"name": "luci-app-auth-guardian",
|
||||
"version": "0.4.0-r3",
|
||||
"filename": "luci-app-auth-guardian_0.4.0-r3_all.ipk",
|
||||
"size": 12400,
|
||||
"size": 12391,
|
||||
"category": "security",
|
||||
"icon": "key",
|
||||
"description": "Authentication management",
|
||||
@ -30,7 +30,7 @@
|
||||
"name": "luci-app-backup",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-backup_1.0.0-r1_all.ipk",
|
||||
"size": 4534,
|
||||
"size": 4537,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -54,7 +54,7 @@
|
||||
"name": "luci-app-cdn-cache",
|
||||
"version": "0.5.0-r3",
|
||||
"filename": "luci-app-cdn-cache_0.5.0-r3_all.ipk",
|
||||
"size": 24255,
|
||||
"size": 24257,
|
||||
"category": "network",
|
||||
"icon": "globe",
|
||||
"description": "CDN caching",
|
||||
@ -66,7 +66,7 @@
|
||||
"name": "luci-app-client-guardian",
|
||||
"version": "0.4.0-r7",
|
||||
"filename": "luci-app-client-guardian_0.4.0-r7_all.ipk",
|
||||
"size": 52684,
|
||||
"size": 52687,
|
||||
"category": "network",
|
||||
"icon": "users",
|
||||
"description": "Client management and monitoring",
|
||||
@ -78,7 +78,7 @@
|
||||
"name": "luci-app-cloner",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-cloner_1.0.0-r1_all.ipk",
|
||||
"size": 19433,
|
||||
"size": 19435,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -102,7 +102,7 @@
|
||||
"name": "luci-app-cookie-tracker",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-cookie-tracker_1.0.0-r1_all.ipk",
|
||||
"size": 5653,
|
||||
"size": 5660,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -114,7 +114,7 @@
|
||||
"name": "luci-app-crowdsec-dashboard",
|
||||
"version": "0.7.0-r32",
|
||||
"filename": "luci-app-crowdsec-dashboard_0.7.0-r32_all.ipk",
|
||||
"size": 34938,
|
||||
"size": 34940,
|
||||
"category": "security",
|
||||
"icon": "shield",
|
||||
"description": "CrowdSec security monitoring",
|
||||
@ -126,7 +126,7 @@
|
||||
"name": "luci-app-cve-triage",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-cve-triage_1.0.0-r1_all.ipk",
|
||||
"size": 5948,
|
||||
"size": 5943,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -150,7 +150,7 @@
|
||||
"name": "luci-app-device-intel",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-device-intel_1.0.0-r1_all.ipk",
|
||||
"size": 12047,
|
||||
"size": 12049,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -162,7 +162,7 @@
|
||||
"name": "luci-app-dnsguard",
|
||||
"version": "1.1.0-r1",
|
||||
"filename": "luci-app-dnsguard_1.1.0-r1_all.ipk",
|
||||
"size": 12448,
|
||||
"size": 12449,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -174,7 +174,7 @@
|
||||
"name": "luci-app-dns-provider",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-dns-provider_1.0.0-r1_all.ipk",
|
||||
"size": 7168,
|
||||
"size": 7174,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -186,7 +186,7 @@
|
||||
"name": "luci-app-domoticz",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-domoticz_1.0.0-r1_all.ipk",
|
||||
"size": 7123,
|
||||
"size": 7118,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -198,7 +198,7 @@
|
||||
"name": "luci-app-exposure",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-exposure_1.0.0-r3_all.ipk",
|
||||
"size": 11696,
|
||||
"size": 11698,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -210,7 +210,7 @@
|
||||
"name": "luci-app-gitea",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-gitea_1.0.0-r2_all.ipk",
|
||||
"size": 16623,
|
||||
"size": 16621,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -222,7 +222,7 @@
|
||||
"name": "luci-app-glances",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-glances_1.0.0-r2_all.ipk",
|
||||
"size": 7022,
|
||||
"size": 7017,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -234,7 +234,7 @@
|
||||
"name": "luci-app-gotosocial",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "luci-app-gotosocial_0.1.0-r1_all.ipk",
|
||||
"size": 8207,
|
||||
"size": 8210,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -258,7 +258,7 @@
|
||||
"name": "luci-app-hexojs",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-hexojs_1.0.0-r3_all.ipk",
|
||||
"size": 30452,
|
||||
"size": 30450,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -270,7 +270,7 @@
|
||||
"name": "luci-app-iot-guard",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-iot-guard_1.0.0-r1_all.ipk",
|
||||
"size": 10535,
|
||||
"size": 10538,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -282,7 +282,7 @@
|
||||
"name": "luci-app-jabber",
|
||||
"version": "0",
|
||||
"filename": "luci-app-jabber_0_all.ipk",
|
||||
"size": 9304,
|
||||
"size": 9308,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -294,7 +294,7 @@
|
||||
"name": "luci-app-jellyfin",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-jellyfin_1.0.0-r1_all.ipk",
|
||||
"size": 10482,
|
||||
"size": 10485,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -306,7 +306,7 @@
|
||||
"name": "luci-app-jitsi",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-jitsi_1.0.0-r1_all.ipk",
|
||||
"size": 5175,
|
||||
"size": 5173,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -318,7 +318,7 @@
|
||||
"name": "luci-app-ksm-manager",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-ksm-manager_0.4.0-r2_all.ipk",
|
||||
"size": 18779,
|
||||
"size": 18777,
|
||||
"category": "system",
|
||||
"icon": "cpu",
|
||||
"description": "Kernel memory management",
|
||||
@ -342,7 +342,7 @@
|
||||
"name": "luci-app-localrecall",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-localrecall_1.0.0-r1_all.ipk",
|
||||
"size": 8418,
|
||||
"size": 8420,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -354,7 +354,7 @@
|
||||
"name": "luci-app-lyrion",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-lyrion_1.0.0-r1_all.ipk",
|
||||
"size": 6839,
|
||||
"size": 6840,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -366,7 +366,7 @@
|
||||
"name": "luci-app-mac-guardian",
|
||||
"version": "0.5.0-r1",
|
||||
"filename": "luci-app-mac-guardian_0.5.0-r1_all.ipk",
|
||||
"size": 6664,
|
||||
"size": 6660,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -378,7 +378,7 @@
|
||||
"name": "luci-app-magicmirror2",
|
||||
"version": "0.4.0-r6",
|
||||
"filename": "luci-app-magicmirror2_0.4.0-r6_all.ipk",
|
||||
"size": 12361,
|
||||
"size": 12358,
|
||||
"category": "iot",
|
||||
"icon": "monitor",
|
||||
"description": "Smart mirror display",
|
||||
@ -390,7 +390,7 @@
|
||||
"name": "luci-app-mailinabox",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-mailinabox_1.0.0-r1_all.ipk",
|
||||
"size": 5484,
|
||||
"size": 5487,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -402,7 +402,7 @@
|
||||
"name": "luci-app-mailserver",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-mailserver_1.0.0-r1_all.ipk",
|
||||
"size": 6511,
|
||||
"size": 6509,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -414,7 +414,7 @@
|
||||
"name": "luci-app-master-link",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-master-link_1.0.0-r1_all.ipk",
|
||||
"size": 6304,
|
||||
"size": 6309,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -438,7 +438,7 @@
|
||||
"name": "luci-app-media-flow",
|
||||
"version": "0.6.4-r1",
|
||||
"filename": "luci-app-media-flow_0.6.4-r1_all.ipk",
|
||||
"size": 25381,
|
||||
"size": 25379,
|
||||
"category": "media",
|
||||
"icon": "film",
|
||||
"description": "Media streaming",
|
||||
@ -450,7 +450,7 @@
|
||||
"name": "luci-app-metablogizer",
|
||||
"version": "1.1.0-r1",
|
||||
"filename": "luci-app-metablogizer_1.1.0-r1_all.ipk",
|
||||
"size": 26207,
|
||||
"size": 26203,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -462,7 +462,7 @@
|
||||
"name": "luci-app-metabolizer",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-metabolizer_1.0.0-r2_all.ipk",
|
||||
"size": 4821,
|
||||
"size": 4819,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -474,7 +474,7 @@
|
||||
"name": "luci-app-mitmproxy",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-mitmproxy_0.5.0-r2_all.ipk",
|
||||
"size": 13232,
|
||||
"size": 13234,
|
||||
"category": "security",
|
||||
"icon": "lock",
|
||||
"description": "HTTPS proxy and traffic inspection",
|
||||
@ -486,7 +486,7 @@
|
||||
"name": "luci-app-mmpm",
|
||||
"version": "0.2.0-r3",
|
||||
"filename": "luci-app-mmpm_0.2.0-r3_all.ipk",
|
||||
"size": 7975,
|
||||
"size": 7971,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -498,7 +498,7 @@
|
||||
"name": "luci-app-mqtt-bridge",
|
||||
"version": "0.4.0-r4",
|
||||
"filename": "luci-app-mqtt-bridge_0.4.0-r4_all.ipk",
|
||||
"size": 22689,
|
||||
"size": 22687,
|
||||
"category": "iot",
|
||||
"icon": "message-square",
|
||||
"description": "MQTT bridge",
|
||||
@ -510,7 +510,7 @@
|
||||
"name": "luci-app-ndpid",
|
||||
"version": "1.1.2-r2",
|
||||
"filename": "luci-app-ndpid_1.1.2-r2_all.ipk",
|
||||
"size": 21704,
|
||||
"size": 21701,
|
||||
"category": "security",
|
||||
"icon": "eye",
|
||||
"description": "Deep packet inspection",
|
||||
@ -522,7 +522,7 @@
|
||||
"name": "luci-app-netdata-dashboard",
|
||||
"version": "0.5.0-r2",
|
||||
"filename": "luci-app-netdata-dashboard_0.5.0-r2_all.ipk",
|
||||
"size": 20558,
|
||||
"size": 20559,
|
||||
"category": "monitoring",
|
||||
"icon": "bar-chart-2",
|
||||
"description": "System monitoring dashboard",
|
||||
@ -534,7 +534,7 @@
|
||||
"name": "luci-app-network-anomaly",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-network-anomaly_1.0.0-r1_all.ipk",
|
||||
"size": 7641,
|
||||
"size": 7643,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -558,7 +558,7 @@
|
||||
"name": "luci-app-network-tweaks",
|
||||
"version": "1.0.0-r7",
|
||||
"filename": "luci-app-network-tweaks_1.0.0-r7_all.ipk",
|
||||
"size": 15949,
|
||||
"size": 15947,
|
||||
"category": "network",
|
||||
"icon": "wifi",
|
||||
"description": "Network configuration",
|
||||
@ -570,7 +570,7 @@
|
||||
"name": "luci-app-nextcloud",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-nextcloud_1.0.0-r1_all.ipk",
|
||||
"size": 10347,
|
||||
"size": 10348,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -582,7 +582,7 @@
|
||||
"name": "luci-app-ollama",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "luci-app-ollama_0.1.0-r1_all.ipk",
|
||||
"size": 14338,
|
||||
"size": 14337,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -594,7 +594,7 @@
|
||||
"name": "luci-app-peertube",
|
||||
"version": "0",
|
||||
"filename": "luci-app-peertube_0_all.ipk",
|
||||
"size": 5760,
|
||||
"size": 5757,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -606,7 +606,7 @@
|
||||
"name": "luci-app-picobrew",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-picobrew_1.0.0-r1_all.ipk",
|
||||
"size": 9534,
|
||||
"size": 9529,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -618,7 +618,7 @@
|
||||
"name": "luci-app-secubox",
|
||||
"version": "0.7.1-r4",
|
||||
"filename": "luci-app-secubox_0.7.1-r4_all.ipk",
|
||||
"size": 82095,
|
||||
"size": 82096,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -630,7 +630,7 @@
|
||||
"name": "luci-app-secubox-admin",
|
||||
"version": "1.0.0-r19",
|
||||
"filename": "luci-app-secubox-admin_1.0.0-r19_all.ipk",
|
||||
"size": 58041,
|
||||
"size": 58040,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -642,7 +642,7 @@
|
||||
"name": "luci-app-secubox-crowdsec",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "luci-app-secubox-crowdsec_1.0.0-r3_all.ipk",
|
||||
"size": 13918,
|
||||
"size": 13916,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -666,7 +666,7 @@
|
||||
"name": "luci-app-secubox-netdiag",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-secubox-netdiag_1.0.0-r1_all.ipk",
|
||||
"size": 15347,
|
||||
"size": 15344,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -690,7 +690,7 @@
|
||||
"name": "luci-app-secubox-p2p",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "luci-app-secubox-p2p_0.1.0-r1_all.ipk",
|
||||
"size": 46832,
|
||||
"size": 46831,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -702,7 +702,7 @@
|
||||
"name": "luci-app-secubox-portal",
|
||||
"version": "0.7.0-r3",
|
||||
"filename": "luci-app-secubox-portal_0.7.0-r3_all.ipk",
|
||||
"size": 41686,
|
||||
"size": 41681,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -714,7 +714,19 @@
|
||||
"name": "luci-app-secubox-security-threats",
|
||||
"version": "1.0.0-r4",
|
||||
"filename": "luci-app-secubox-security-threats_1.0.0-r4_all.ipk",
|
||||
"size": 10659,
|
||||
"size": 10657,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
"installed": false,
|
||||
"luci_app": null
|
||||
}
|
||||
,
|
||||
{
|
||||
"name": "luci-app-secubox-users",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-secubox-users_1.0.0-r1_all.ipk",
|
||||
"size": 5141,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox system component",
|
||||
@ -726,7 +738,7 @@
|
||||
"name": "luci-app-service-registry",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-service-registry_1.0.0-r1_all.ipk",
|
||||
"size": 39952,
|
||||
"size": 39951,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -738,7 +750,7 @@
|
||||
"name": "luci-app-simplex",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-simplex_1.0.0-r1_all.ipk",
|
||||
"size": 7036,
|
||||
"size": 7042,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -750,7 +762,7 @@
|
||||
"name": "luci-app-streamlit",
|
||||
"version": "1.0.0-r11",
|
||||
"filename": "luci-app-streamlit_1.0.0-r11_all.ipk",
|
||||
"size": 20568,
|
||||
"size": 20567,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -762,7 +774,7 @@
|
||||
"name": "luci-app-system-hub",
|
||||
"version": "0.5.1-r4",
|
||||
"filename": "luci-app-system-hub_0.5.1-r4_all.ipk",
|
||||
"size": 62082,
|
||||
"size": 62081,
|
||||
"category": "system",
|
||||
"icon": "settings",
|
||||
"description": "System management",
|
||||
@ -774,7 +786,7 @@
|
||||
"name": "luci-app-threat-analyst",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-threat-analyst_1.0.0-r1_all.ipk",
|
||||
"size": 10144,
|
||||
"size": 10145,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -786,7 +798,7 @@
|
||||
"name": "luci-app-tor",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-tor_1.0.0-r1_all.ipk",
|
||||
"size": 17822,
|
||||
"size": 17816,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -798,7 +810,7 @@
|
||||
"name": "luci-app-tor-shield",
|
||||
"version": "1.0.0-r10",
|
||||
"filename": "luci-app-tor-shield_1.0.0-r10_all.ipk",
|
||||
"size": 22766,
|
||||
"size": 22764,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -810,7 +822,7 @@
|
||||
"name": "luci-app-traffic-shaper",
|
||||
"version": "0.4.0-r2",
|
||||
"filename": "luci-app-traffic-shaper_0.4.0-r2_all.ipk",
|
||||
"size": 14591,
|
||||
"size": 14587,
|
||||
"category": "network",
|
||||
"icon": "filter",
|
||||
"description": "Traffic shaping and QoS",
|
||||
@ -822,7 +834,7 @@
|
||||
"name": "luci-app-vhost-manager",
|
||||
"version": "0.5.0-r5",
|
||||
"filename": "luci-app-vhost-manager_0.5.0-r5_all.ipk",
|
||||
"size": 26284,
|
||||
"size": 26283,
|
||||
"category": "network",
|
||||
"icon": "server",
|
||||
"description": "Virtual host management",
|
||||
@ -834,7 +846,7 @@
|
||||
"name": "luci-app-voip",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-voip_1.0.0-r1_all.ipk",
|
||||
"size": 11046,
|
||||
"size": 11043,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -846,7 +858,7 @@
|
||||
"name": "luci-app-vortex-dns",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-vortex-dns_1.0.0-r1_all.ipk",
|
||||
"size": 6078,
|
||||
"size": 6079,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -858,7 +870,7 @@
|
||||
"name": "luci-app-vortex-firewall",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-vortex-firewall_1.0.0-r1_all.ipk",
|
||||
"size": 5451,
|
||||
"size": 5450,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -870,7 +882,7 @@
|
||||
"name": "luci-app-wazuh",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "luci-app-wazuh_1.0.0-r1_all.ipk",
|
||||
"size": 11072,
|
||||
"size": 11073,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -882,7 +894,7 @@
|
||||
"name": "luci-app-wireguard-dashboard",
|
||||
"version": "0.7.0-r5",
|
||||
"filename": "luci-app-wireguard-dashboard_0.7.0-r5_all.ipk",
|
||||
"size": 42291,
|
||||
"size": 42286,
|
||||
"category": "vpn",
|
||||
"icon": "shield",
|
||||
"description": "WireGuard VPN dashboard",
|
||||
@ -894,7 +906,7 @@
|
||||
"name": "luci-app-zigbee2mqtt",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "luci-app-zigbee2mqtt_1.0.0-r2_all.ipk",
|
||||
"size": 6591,
|
||||
"size": 6592,
|
||||
"category": "iot",
|
||||
"icon": "radio",
|
||||
"description": "Zigbee device management",
|
||||
@ -906,7 +918,7 @@
|
||||
"name": "luci-theme-secubox",
|
||||
"version": "0.4.7-r1",
|
||||
"filename": "luci-theme-secubox_0.4.7-r1_all.ipk",
|
||||
"size": 110239,
|
||||
"size": 110238,
|
||||
"category": "theme",
|
||||
"icon": "palette",
|
||||
"description": "LuCI theme",
|
||||
@ -918,7 +930,7 @@
|
||||
"name": "secubox-app",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app_1.0.0-r2_all.ipk",
|
||||
"size": 11182,
|
||||
"size": 11183,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -930,7 +942,7 @@
|
||||
"name": "secubox-app-adguardhome",
|
||||
"version": "1.0.0-r2",
|
||||
"filename": "secubox-app-adguardhome_1.0.0-r2_all.ipk",
|
||||
"size": 2883,
|
||||
"size": 2880,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -942,7 +954,7 @@
|
||||
"name": "secubox-app-auth-logger",
|
||||
"version": "1.2.2-r1",
|
||||
"filename": "secubox-app-auth-logger_1.2.2-r1_all.ipk",
|
||||
"size": 9377,
|
||||
"size": 9374,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -966,7 +978,7 @@
|
||||
"name": "secubox-app-cs-firewall-bouncer",
|
||||
"version": "0.0.31-r4_aarch64",
|
||||
"filename": "secubox-app-cs-firewall-bouncer_0.0.31-r4_aarch64_cortex-a72.ipk",
|
||||
"size": 5049325,
|
||||
"size": 5049324,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -978,7 +990,7 @@
|
||||
"name": "secubox-app-cyberfeed",
|
||||
"version": "0.2.1-r1",
|
||||
"filename": "secubox-app-cyberfeed_0.2.1-r1_all.ipk",
|
||||
"size": 12453,
|
||||
"size": 12450,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -990,7 +1002,7 @@
|
||||
"name": "secubox-app-device-intel",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-device-intel_1.0.0-r1_all.ipk",
|
||||
"size": 13108,
|
||||
"size": 13104,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1002,7 +1014,7 @@
|
||||
"name": "secubox-app-dns-provider",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-dns-provider_1.0.0-r1_all.ipk",
|
||||
"size": 8261,
|
||||
"size": 8257,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1014,7 +1026,7 @@
|
||||
"name": "secubox-app-domoticz",
|
||||
"version": "1.0.0-r4",
|
||||
"filename": "secubox-app-domoticz_1.0.0-r4_all.ipk",
|
||||
"size": 7503,
|
||||
"size": 7507,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1026,7 +1038,7 @@
|
||||
"name": "secubox-app-exposure",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-exposure_1.0.0-r1_all.ipk",
|
||||
"size": 9147,
|
||||
"size": 9145,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1038,7 +1050,7 @@
|
||||
"name": "secubox-app-gitea",
|
||||
"version": "1.0.0-r5",
|
||||
"filename": "secubox-app-gitea_1.0.0-r5_all.ipk",
|
||||
"size": 9439,
|
||||
"size": 9441,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1050,7 +1062,7 @@
|
||||
"name": "secubox-app-gk2hub",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-app-gk2hub_0.1.0-r1_all.ipk",
|
||||
"size": 4064,
|
||||
"size": 4059,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1062,7 +1074,7 @@
|
||||
"name": "secubox-app-glances",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-glances_1.0.0-r1_all.ipk",
|
||||
"size": 6142,
|
||||
"size": 6137,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1074,7 +1086,7 @@
|
||||
"name": "secubox-app-guacamole",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-guacamole_1.0.0-r1_all.ipk",
|
||||
"size": 6948,
|
||||
"size": 6945,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1086,7 +1098,7 @@
|
||||
"name": "secubox-app-haproxy",
|
||||
"version": "1.0.0-r24",
|
||||
"filename": "secubox-app-haproxy_1.0.0-r24_all.ipk",
|
||||
"size": 22012,
|
||||
"size": 22008,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1098,7 +1110,7 @@
|
||||
"name": "secubox-app-hexojs",
|
||||
"version": "1.0.0-r8",
|
||||
"filename": "secubox-app-hexojs_1.0.0-r8_all.ipk",
|
||||
"size": 94937,
|
||||
"size": 100060,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1110,7 +1122,7 @@
|
||||
"name": "secubox-app-jabber",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-jabber_1.0.0-r1_all.ipk",
|
||||
"size": 13277,
|
||||
"size": 13276,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1122,7 +1134,7 @@
|
||||
"name": "secubox-app-jellyfin",
|
||||
"version": "3.0.0-r1",
|
||||
"filename": "secubox-app-jellyfin_3.0.0-r1_all.ipk",
|
||||
"size": 4748,
|
||||
"size": 4755,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1134,7 +1146,7 @@
|
||||
"name": "secubox-app-jitsi",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-jitsi_1.0.0-r1_all.ipk",
|
||||
"size": 8927,
|
||||
"size": 8924,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1146,7 +1158,7 @@
|
||||
"name": "secubox-app-localai",
|
||||
"version": "3.9.0-r1",
|
||||
"filename": "secubox-app-localai_3.9.0-r1_all.ipk",
|
||||
"size": 5848,
|
||||
"size": 5841,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1170,7 +1182,7 @@
|
||||
"name": "secubox-app-lyrion",
|
||||
"version": "2.0.2-r1",
|
||||
"filename": "secubox-app-lyrion_2.0.2-r1_all.ipk",
|
||||
"size": 8130,
|
||||
"size": 8124,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1182,7 +1194,7 @@
|
||||
"name": "secubox-app-mac-guardian",
|
||||
"version": "0.5.0-r1",
|
||||
"filename": "secubox-app-mac-guardian_0.5.0-r1_all.ipk",
|
||||
"size": 12098,
|
||||
"size": 12097,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1206,7 +1218,7 @@
|
||||
"name": "secubox-app-mailinabox",
|
||||
"version": "2.0.0-r1",
|
||||
"filename": "secubox-app-mailinabox_2.0.0-r1_all.ipk",
|
||||
"size": 7569,
|
||||
"size": 7574,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1218,7 +1230,7 @@
|
||||
"name": "secubox-app-mailserver",
|
||||
"version": "2.0.0-r1",
|
||||
"filename": "secubox-app-mailserver_2.0.0-r1_all.ipk",
|
||||
"size": 5697,
|
||||
"size": 5693,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1242,7 +1254,7 @@
|
||||
"name": "secubox-app-metabolizer",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "secubox-app-metabolizer_1.0.0-r3_all.ipk",
|
||||
"size": 13980,
|
||||
"size": 13975,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1254,7 +1266,7 @@
|
||||
"name": "secubox-app-mitmproxy",
|
||||
"version": "0.5.0-r19",
|
||||
"filename": "secubox-app-mitmproxy_0.5.0-r19_all.ipk",
|
||||
"size": 22958,
|
||||
"size": 22957,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1266,7 +1278,7 @@
|
||||
"name": "secubox-app-mmpm",
|
||||
"version": "0.2.0-r5",
|
||||
"filename": "secubox-app-mmpm_0.2.0-r5_all.ipk",
|
||||
"size": 3978,
|
||||
"size": 3979,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1290,7 +1302,7 @@
|
||||
"name": "secubox-app-ollama",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-app-ollama_0.1.0-r1_all.ipk",
|
||||
"size": 5735,
|
||||
"size": 5736,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1314,7 +1326,7 @@
|
||||
"name": "secubox-app-rustdesk",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-rustdesk_1.0.0-r1_all.ipk",
|
||||
"size": 4469,
|
||||
"size": 4467,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1326,7 +1338,7 @@
|
||||
"name": "secubox-app-simplex",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-simplex_1.0.0-r1_all.ipk",
|
||||
"size": 9369,
|
||||
"size": 9367,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1338,7 +1350,7 @@
|
||||
"name": "secubox-app-smbfs",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-smbfs_1.0.0-r1_all.ipk",
|
||||
"size": 5269,
|
||||
"size": 5268,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1350,7 +1362,7 @@
|
||||
"name": "secubox-app-streamlit",
|
||||
"version": "1.0.0-r5",
|
||||
"filename": "secubox-app-streamlit_1.0.0-r5_all.ipk",
|
||||
"size": 16517,
|
||||
"size": 16519,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1362,7 +1374,7 @@
|
||||
"name": "secubox-app-tor",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-tor_1.0.0-r1_all.ipk",
|
||||
"size": 7372,
|
||||
"size": 7370,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1374,7 +1386,7 @@
|
||||
"name": "secubox-app-voip",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-app-voip_1.0.0-r1_all.ipk",
|
||||
"size": 11955,
|
||||
"size": 11954,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1386,7 +1398,7 @@
|
||||
"name": "secubox-app-webapp",
|
||||
"version": "1.5.0-r7",
|
||||
"filename": "secubox-app-webapp_1.5.0-r7_all.ipk",
|
||||
"size": 39176,
|
||||
"size": 39177,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1398,7 +1410,7 @@
|
||||
"name": "secubox-app-zigbee2mqtt",
|
||||
"version": "1.0.0-r3",
|
||||
"filename": "secubox-app-zigbee2mqtt_1.0.0-r3_all.ipk",
|
||||
"size": 5533,
|
||||
"size": 5539,
|
||||
"category": "secubox",
|
||||
"icon": "package",
|
||||
"description": "SecuBox backend service",
|
||||
@ -1410,7 +1422,7 @@
|
||||
"name": "secubox-config-advisor",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-config-advisor_0.1.0-r1_all.ipk",
|
||||
"size": 14850,
|
||||
"size": 14849,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1422,7 +1434,7 @@
|
||||
"name": "secubox-content-pkg",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-content-pkg_1.0.0-r1_all.ipk",
|
||||
"size": 3914,
|
||||
"size": 3904,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1434,7 +1446,7 @@
|
||||
"name": "secubox-cookie-tracker",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-cookie-tracker_1.0.0-r1_all.ipk",
|
||||
"size": 10645,
|
||||
"size": 10641,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1446,7 +1458,7 @@
|
||||
"name": "secubox-core",
|
||||
"version": "0.10.0-r16",
|
||||
"filename": "secubox-core_0.10.0-r16_all.ipk",
|
||||
"size": 123047,
|
||||
"size": 123045,
|
||||
"category": "system",
|
||||
"icon": "box",
|
||||
"description": "SecuBox core components",
|
||||
@ -1458,7 +1470,7 @@
|
||||
"name": "secubox-cve-triage",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-cve-triage_1.0.0-r1_all.ipk",
|
||||
"size": 11826,
|
||||
"size": 11827,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1470,7 +1482,7 @@
|
||||
"name": "secubox-dns-guard",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-dns-guard_1.0.0-r1_all.ipk",
|
||||
"size": 12488,
|
||||
"size": 12485,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1482,7 +1494,7 @@
|
||||
"name": "secubox-identity",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-identity_0.1.0-r1_all.ipk",
|
||||
"size": 8089,
|
||||
"size": 8088,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1494,7 +1506,7 @@
|
||||
"name": "secubox-iot-guard",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-iot-guard_1.0.0-r1_all.ipk",
|
||||
"size": 13374,
|
||||
"size": 13365,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1506,7 +1518,7 @@
|
||||
"name": "secubox-localrecall",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-localrecall_1.0.0-r1_all.ipk",
|
||||
"size": 7802,
|
||||
"size": 7796,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1518,7 +1530,7 @@
|
||||
"name": "secubox-master-link",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-master-link_1.0.0-r1_all.ipk",
|
||||
"size": 15038,
|
||||
"size": 15034,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1530,7 +1542,7 @@
|
||||
"name": "secubox-mcp-server",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-mcp-server_1.0.0-r1_all.ipk",
|
||||
"size": 11433,
|
||||
"size": 11427,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1542,7 +1554,7 @@
|
||||
"name": "secubox-mirrornet",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-mirrornet_0.1.0-r1_all.ipk",
|
||||
"size": 15306,
|
||||
"size": 15303,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1554,7 +1566,7 @@
|
||||
"name": "secubox-network-anomaly",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-network-anomaly_1.0.0-r1_all.ipk",
|
||||
"size": 6170,
|
||||
"size": 6163,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1566,7 +1578,7 @@
|
||||
"name": "secubox-p2p",
|
||||
"version": "0.6.0-r3",
|
||||
"filename": "secubox-p2p_0.6.0-r3_all.ipk",
|
||||
"size": 47872,
|
||||
"size": 47860,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1578,7 +1590,7 @@
|
||||
"name": "secubox-p2p-intel",
|
||||
"version": "0.1.0-r1",
|
||||
"filename": "secubox-p2p-intel_0.1.0-r1_all.ipk",
|
||||
"size": 9798,
|
||||
"size": 9799,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1590,7 +1602,7 @@
|
||||
"name": "secubox-threat-analyst",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-threat-analyst_1.0.0-r1_all.ipk",
|
||||
"size": 9867,
|
||||
"size": 9864,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1602,7 +1614,7 @@
|
||||
"name": "secubox-vortex-dns",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-vortex-dns_1.0.0-r1_all.ipk",
|
||||
"size": 5443,
|
||||
"size": 5439,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
@ -1614,7 +1626,7 @@
|
||||
"name": "secubox-vortex-firewall",
|
||||
"version": "1.0.0-r1",
|
||||
"filename": "secubox-vortex-firewall_1.0.0-r1_all.ipk",
|
||||
"size": 8898,
|
||||
"size": 8892,
|
||||
"category": "utility",
|
||||
"icon": "package",
|
||||
"description": "SecuBox package",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user