feat(network-tweaks): Add CDN cache and WPAD proxy controls

- Add CDN cache status card with enable/disable and restart buttons
- Add WPAD auto-proxy card with enable/disable toggle
- Add getProxyStatus, getWpadStatus, setWpadEnabled RPCD methods
- Move menu to Services section
- Update ACL for CDN cache and WPAD control

Also fixes:
- security-threats: Fix HAProxy socket path for connection stats
- tor-shield: Add missing ACL methods for excluded destinations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-28 14:07:38 +01:00
parent aa5b8097c5
commit dc4ec3f102
9 changed files with 367 additions and 16 deletions

View File

@ -5,13 +5,13 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-network-tweaks PKG_NAME:=luci-app-network-tweaks
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=3 PKG_RELEASE:=4
PKG_ARCH:=all PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=Network Tweaks - Auto Proxy DNS & Hosts LUCI_TITLE:=Network Tweaks Dashboard
LUCI_DESCRIPTION:=Automatically generates DNS and hosts entries from enabled vhosts for seamless local domain resolution LUCI_DESCRIPTION:=Unified network services dashboard with DNS/hosts sync, CDN cache control, and WPAD auto-proxy configuration
LUCI_DEPENDS:=+luci-base +rpcd +luci-app-vhost-manager +dnsmasq LUCI_DEPENDS:=+luci-base +rpcd +luci-app-vhost-manager +dnsmasq
LUCI_PKGARCH:=all LUCI_PKGARCH:=all

View File

@ -36,6 +36,32 @@ var callGetCumulativeImpact = rpc.declare({
expect: { } expect: { }
}); });
var callCdnCacheStatus = rpc.declare({
object: 'luci.cdn-cache',
method: 'status',
expect: { }
});
var callCdnCacheSetEnabled = rpc.declare({
object: 'luci.cdn-cache',
method: 'set_enabled',
params: ['enabled'],
expect: { }
});
var callGetWpadStatus = rpc.declare({
object: 'luci.network-tweaks',
method: 'getWpadStatus',
expect: { }
});
var callSetWpadEnabled = rpc.declare({
object: 'luci.network-tweaks',
method: 'setWpadEnabled',
params: ['enabled'],
expect: { }
});
var callSetComponentEnabled = rpc.declare({ var callSetComponentEnabled = rpc.declare({
object: 'luci.network-tweaks', object: 'luci.network-tweaks',
method: 'setComponentEnabled', method: 'setComponentEnabled',
@ -43,7 +69,14 @@ var callSetComponentEnabled = rpc.declare({
expect: { } expect: { }
}); });
var callGetProxyStatus = rpc.declare({
object: 'luci.network-tweaks',
method: 'getProxyStatus',
expect: { }
});
return view.extend({ return view.extend({
proxyStatusData: {},
componentsData: [], componentsData: [],
cumulativeData: {}, cumulativeData: {},
networkModeData: {}, networkModeData: {},
@ -60,7 +93,8 @@ return view.extend({
return Promise.all([ return Promise.all([
callNetworkTweaksStatus(), callNetworkTweaksStatus(),
callGetNetworkComponents(), callGetNetworkComponents(),
callGetCumulativeImpact() callGetCumulativeImpact(),
callGetProxyStatus()
]); ]);
}, },
@ -68,10 +102,12 @@ return view.extend({
var status = data[0] || {}; var status = data[0] || {};
var componentsResponse = data[1] || {}; var componentsResponse = data[1] || {};
var cumulativeResponse = data[2] || {}; var cumulativeResponse = data[2] || {};
var proxyStatus = data[3] || {};
this.componentsData = componentsResponse.components || []; this.componentsData = componentsResponse.components || [];
this.cumulativeData = cumulativeResponse || {}; this.cumulativeData = cumulativeResponse || {};
this.networkModeData = componentsResponse.network_mode || {}; this.networkModeData = componentsResponse.network_mode || {};
this.proxyStatusData = proxyStatus || {};
var m, s, o; var m, s, o;
@ -129,6 +165,7 @@ return view.extend({
return E('div', { 'class': 'network-tweaks-dashboard' }, [ return E('div', { 'class': 'network-tweaks-dashboard' }, [
this.renderCumulativeImpact(), this.renderCumulativeImpact(),
this.renderNetworkModeStatus(), this.renderNetworkModeStatus(),
this.renderProxySettings(),
this.renderComponentsGrid(), this.renderComponentsGrid(),
this.renderSyncSection() this.renderSyncSection()
]); ]);
@ -198,6 +235,137 @@ return view.extend({
]); ]);
}, },
renderProxySettings: function() {
var proxy = this.proxyStatusData;
var cdnCache = proxy.cdn_cache || {};
var wpad = proxy.wpad || {};
var cdnStatusClass = cdnCache.running ? 'status-running' : 'status-stopped';
var cdnStatusText = cdnCache.running ? _('Running') : _('Stopped');
var cdnListeningText = cdnCache.listening ?
_('Listening on port ') + (cdnCache.port || 3128) :
_('Not listening');
var wpadStatusClass = wpad.enabled ? 'status-running' : 'status-stopped';
var wpadStatusText = wpad.enabled ? _('Enabled') : _('Disabled');
return E('div', { 'class': 'proxy-settings-section' }, [
E('h3', {}, _('Proxy & Caching')),
E('div', { 'class': 'proxy-grid', 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;' }, [
// CDN Cache Card
E('div', { 'class': 'proxy-card', 'style': 'background: #16213e; border-radius: 8px; padding: 1rem; border: 1px solid #333;' }, [
E('div', { 'class': 'proxy-header', 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem;' }, [
E('span', { 'style': 'font-size: 1.5em;' }, '\ud83d\udce6'),
E('span', { 'style': 'font-weight: bold;' }, _('CDN Cache'))
]),
E('span', {
'class': 'status-badge ' + cdnStatusClass,
'style': 'padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8em; ' +
(cdnCache.running ? 'background: #22c55e;' : 'background: #ef4444;')
}, cdnStatusText)
]),
E('div', { 'class': 'proxy-info', 'style': 'font-size: 0.9em; color: #888; margin-bottom: 0.75rem;' }, [
E('div', {}, cdnListeningText),
E('div', {}, cdnCache.installed ? _('nginx proxy installed') : _('Not installed'))
]),
E('div', { 'class': 'proxy-actions', 'style': 'display: flex; gap: 0.5rem;' }, [
E('button', {
'class': 'btn cbi-button',
'click': L.bind(this.handleCdnCacheToggle, this),
'disabled': !cdnCache.installed
}, cdnCache.enabled ? _('Disable') : _('Enable')),
E('button', {
'class': 'btn cbi-button',
'click': L.bind(this.handleCdnCacheRestart, this),
'disabled': !cdnCache.installed
}, _('Restart'))
])
]),
// WPAD Card
E('div', { 'class': 'proxy-card', 'style': 'background: #16213e; border-radius: 8px; padding: 1rem; border: 1px solid #333;' }, [
E('div', { 'class': 'proxy-header', 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5rem;' }, [
E('span', { 'style': 'font-size: 1.5em;' }, '\ud83c\udf10'),
E('span', { 'style': 'font-weight: bold;' }, _('WPAD Auto-Proxy'))
]),
E('span', {
'class': 'status-badge ' + wpadStatusClass,
'style': 'padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.8em; ' +
(wpad.enabled ? 'background: #22c55e;' : 'background: #ef4444;')
}, wpadStatusText)
]),
E('div', { 'class': 'proxy-info', 'style': 'font-size: 0.9em; color: #888; margin-bottom: 0.75rem;' }, [
E('div', {}, wpad.dhcp_configured ? _('DHCP Option 252 configured') : _('DHCP not configured')),
E('div', {}, wpad.url ? wpad.url : _('No PAC URL set'))
]),
E('div', { 'class': 'proxy-actions', 'style': 'display: flex; gap: 0.5rem;' }, [
E('button', {
'class': 'btn cbi-button',
'click': L.bind(this.handleWpadToggle, this)
}, wpad.enabled ? _('Disable') : _('Enable'))
])
])
])
]);
},
handleCdnCacheToggle: function(ev) {
var currentEnabled = this.proxyStatusData.cdn_cache?.enabled || false;
var newEnabled = !currentEnabled;
ui.showModal(_('Updating...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
return callCdnCacheSetEnabled(newEnabled ? 1 : 0).then(L.bind(function() {
ui.hideModal();
this.refreshData();
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
});
},
handleCdnCacheRestart: function(ev) {
ui.showModal(_('Restarting...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
return rpc.declare({
object: 'luci.cdn-cache',
method: 'restart',
expect: { }
})().then(L.bind(function() {
ui.hideModal();
ui.addNotification(null, E('p', _('CDN Cache restarted')), 'info');
this.refreshData();
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
});
},
handleWpadToggle: function(ev) {
var currentEnabled = this.proxyStatusData.wpad?.enabled || false;
var newEnabled = !currentEnabled;
ui.showModal(_('Updating...'), [
E('p', { 'class': 'spinning' }, _('Please wait...'))
]);
return callSetWpadEnabled(newEnabled ? 1 : 0).then(L.bind(function(result) {
ui.hideModal();
if (result.success) {
ui.addNotification(null, E('p', result.message || _('WPAD updated')), 'info');
}
this.refreshData();
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
});
},
renderComponentsGrid: function() { renderComponentsGrid: function() {
var components = this.componentsData; var components = this.componentsData;
@ -362,14 +530,17 @@ return view.extend({
refreshData: function() { refreshData: function() {
return Promise.all([ return Promise.all([
callGetNetworkComponents(), callGetNetworkComponents(),
callGetCumulativeImpact() callGetCumulativeImpact(),
callGetProxyStatus()
]).then(L.bind(function(data) { ]).then(L.bind(function(data) {
var componentsResponse = data[0] || {}; var componentsResponse = data[0] || {};
var cumulativeResponse = data[1] || {}; var cumulativeResponse = data[1] || {};
var proxyStatus = data[2] || {};
this.componentsData = componentsResponse.components || []; this.componentsData = componentsResponse.components || [];
this.cumulativeData = cumulativeResponse || {}; this.cumulativeData = cumulativeResponse || {};
this.networkModeData = componentsResponse.network_mode || {}; this.networkModeData = componentsResponse.network_mode || {};
this.proxyStatusData = proxyStatus || {};
this.updateDisplay(); this.updateDisplay();
}, this)); }, this));
@ -383,6 +554,7 @@ return view.extend({
dom.content(dashboard, [ dom.content(dashboard, [
this.renderCumulativeImpact(), this.renderCumulativeImpact(),
this.renderNetworkModeStatus(), this.renderNetworkModeStatus(),
this.renderProxySettings(),
this.renderComponentsGrid(), this.renderComponentsGrid(),
this.renderSyncSection() this.renderSyncSection()
]); ]);

View File

@ -284,6 +284,13 @@ case "$1" in
json_add_string "app_id" "string" json_add_string "app_id" "string"
json_add_string "enabled" "boolean" json_add_string "enabled" "boolean"
json_close_object json_close_object
json_add_object "getWpadStatus"
json_close_object
json_add_object "setWpadEnabled"
json_add_string "enabled" "boolean"
json_close_object
json_add_object "getProxyStatus"
json_close_object
json_dump json_dump
;; ;;
@ -465,6 +472,166 @@ case "$1" in
json_dump json_dump
;; ;;
getWpadStatus)
# Get WPAD/PAC file status
json_init
json_add_boolean "success" 1
# Check if WPAD file exists
wpad_enabled=0
wpad_file=""
if [ -f /www/wpad.dat ]; then
wpad_enabled=1
wpad_file="/www/wpad.dat"
fi
# Check DHCP option 252 configuration
dhcp_wpad_enabled=0
config_load dhcp
check_dhcp_option() {
local value
config_get value "$1" "252"
[ -n "$value" ] && dhcp_wpad_enabled=1
}
config_foreach check_dhcp_option dhcp
# Check dnsmasq wpad entry
dns_wpad_enabled=0
if grep -q "wpad" /etc/hosts 2>/dev/null || \
grep -q "wpad" /tmp/hosts.vhosts 2>/dev/null; then
dns_wpad_enabled=1
fi
json_add_object "wpad"
json_add_boolean "enabled" "$wpad_enabled"
json_add_string "file" "$wpad_file"
json_add_boolean "dhcp_configured" "$dhcp_wpad_enabled"
json_add_boolean "dns_configured" "$dns_wpad_enabled"
json_close_object
json_dump
;;
setWpadEnabled)
# Enable/disable WPAD
read input
json_load "$input"
json_get_var enabled enabled
if [ "$enabled" = "1" ] || [ "$enabled" = "true" ]; then
# Get LAN IP
lan_ip=$(uci get network.lan.ipaddr 2>/dev/null | cut -d'/' -f1)
[ -z "$lan_ip" ] && lan_ip="192.168.255.1"
# Create WPAD/PAC file
cat > /www/wpad.dat << EOF
function FindProxyForURL(url, host) {
// Direct access for local network
if (isInNet(host, "192.168.0.0", "255.255.0.0") ||
isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
shExpMatch(host, "*.local") ||
shExpMatch(host, "*.lan")) {
return "DIRECT";
}
// Use CDN Cache proxy for everything else
return "PROXY ${lan_ip}:3128; DIRECT";
}
EOF
chmod 644 /www/wpad.dat
# Add DHCP option 252 for WPAD discovery
uci set dhcp.lan.dhcp_option_force="252,http://${lan_ip}/wpad.dat"
uci commit dhcp
# Add wpad DNS entry
if ! grep -q "wpad" /etc/hosts 2>/dev/null; then
echo "${lan_ip} wpad wpad.lan" >> /etc/hosts
fi
# Restart dnsmasq
/etc/init.d/dnsmasq reload >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_add_string "message" "WPAD enabled"
else
# Remove WPAD file
rm -f /www/wpad.dat
# Remove DHCP option
uci delete dhcp.lan.dhcp_option_force 2>/dev/null
uci commit dhcp
# Remove wpad DNS entry
sed -i '/wpad/d' /etc/hosts 2>/dev/null
# Restart dnsmasq
/etc/init.d/dnsmasq reload >/dev/null 2>&1
json_init
json_add_boolean "success" 1
json_add_string "message" "WPAD disabled"
fi
json_dump
;;
getProxyStatus)
# Get comprehensive proxy status (CDN Cache + WPAD)
json_init
json_add_boolean "success" 1
# CDN Cache status
cdn_running=0
cdn_enabled=0
cdn_port=3128
if [ -x /etc/init.d/cdn-cache ]; then
/etc/init.d/cdn-cache running >/dev/null 2>&1 && cdn_running=1
cdn_enabled=$(uci get cdn-cache.main.enabled 2>/dev/null)
[ -z "$cdn_enabled" ] && cdn_enabled=0
cdn_port=$(uci get cdn-cache.main.listen_port 2>/dev/null)
[ -z "$cdn_port" ] && cdn_port=3128
fi
# Check if port is actually listening
cdn_listening=0
if netstat -tln 2>/dev/null | grep -q ":${cdn_port}"; then
cdn_listening=1
fi
json_add_object "cdn_cache"
json_add_boolean "installed" "$([ -x /etc/init.d/cdn-cache ] && echo 1 || echo 0)"
json_add_boolean "enabled" "$cdn_enabled"
json_add_boolean "running" "$cdn_running"
json_add_boolean "listening" "$cdn_listening"
json_add_int "port" "$cdn_port"
json_close_object
# WPAD status
wpad_enabled=0
[ -f /www/wpad.dat ] && wpad_enabled=1
dhcp_wpad=0
wpad_url=""
config_load dhcp
wpad_opt=$(uci get dhcp.lan.dhcp_option_force 2>/dev/null)
if echo "$wpad_opt" | grep -q "252"; then
dhcp_wpad=1
wpad_url=$(echo "$wpad_opt" | sed 's/252,//')
fi
json_add_object "wpad"
json_add_boolean "enabled" "$wpad_enabled"
json_add_boolean "dhcp_configured" "$dhcp_wpad"
json_add_string "url" "$wpad_url"
json_close_object
json_dump
;;
*) *)
json_init json_init
json_add_boolean "success" 0 json_add_boolean "success" 0

View File

@ -1,7 +1,7 @@
{ {
"admin/secubox/network/tweaks": { "admin/services/network-tweaks": {
"title": "Network Tweaks", "title": "Network Tweaks",
"order": 50, "order": 80,
"action": { "action": {
"type": "view", "type": "view",
"path": "network-tweaks/overview" "path": "network-tweaks/overview"

View File

@ -3,15 +3,17 @@
"description": "Grant access to Network Tweaks", "description": "Grant access to Network Tweaks",
"read": { "read": {
"ubus": { "ubus": {
"luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact"] "luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact", "getWpadStatus", "getProxyStatus"],
"luci.cdn-cache": ["status", "stats"]
}, },
"uci": ["network_tweaks", "vhosts"] "uci": ["network_tweaks", "vhosts", "cdn-cache", "dhcp"]
}, },
"write": { "write": {
"ubus": { "ubus": {
"luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled"] "luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled", "setWpadEnabled"],
"luci.cdn-cache": ["set_enabled", "restart"]
}, },
"uci": ["network_tweaks"] "uci": ["network_tweaks", "dhcp"]
} }
} }
} }

View File

@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox-security-threats PKG_NAME:=luci-app-secubox-security-threats
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_ARCH:=all PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr> PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -321,7 +321,12 @@ get_security_stats() {
# HAProxy connections (if running in LXC) # HAProxy connections (if running in LXC)
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
haproxy_conns=$(lxc-attach -n haproxy -- sh -c 'echo "show stat" | socat stdio /var/run/haproxy/admin.sock 2>/dev/null | tail -n+2 | awk -F, "{sum+=\$8} END {print sum}"' 2>/dev/null || echo 0) # Try multiple socket paths (chroot-relative and absolute)
haproxy_conns=$(lxc-attach -n haproxy -- sh -c '
for sock in /stats /run/haproxy.sock /var/lib/haproxy/stats /var/run/haproxy/admin.sock; do
[ -S "$sock" ] && echo "show stat" | socat stdio "$sock" 2>/dev/null && break
done | tail -n+2 | awk -F, "{sum+=\$5} END {print sum+0}"
' 2>/dev/null || echo 0)
fi fi
haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n') haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n')
haproxy_conns=${haproxy_conns:-0} haproxy_conns=${haproxy_conns:-0}

View File

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-tor-shield PKG_NAME:=luci-app-tor-shield
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=3 PKG_RELEASE:=4
PKG_ARCH:=all PKG_ARCH:=all
PKG_LICENSE:=MIT PKG_LICENSE:=MIT

View File

@ -11,7 +11,9 @@
"bandwidth", "bandwidth",
"presets", "presets",
"bridges", "bridges",
"settings" "settings",
"excluded_destinations",
"refresh_ips"
], ],
"system": [ "info", "board" ], "system": [ "info", "board" ],
"file": [ "read", "stat", "exec" ] "file": [ "read", "stat", "exec" ]
@ -35,7 +37,10 @@
"add_hidden_service", "add_hidden_service",
"remove_hidden_service", "remove_hidden_service",
"set_bridges", "set_bridges",
"save_settings" "save_settings",
"add_excluded_destination",
"remove_excluded_destination",
"apply_exclusions"
] ]
}, },
"uci": [ "tor-shield" ], "uci": [ "tor-shield" ],