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_VERSION:=1.0.0
PKG_RELEASE:=3
PKG_RELEASE:=4
PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=Network Tweaks - Auto Proxy DNS & Hosts
LUCI_DESCRIPTION:=Automatically generates DNS and hosts entries from enabled vhosts for seamless local domain resolution
LUCI_TITLE:=Network Tweaks Dashboard
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_PKGARCH:=all

View File

@ -36,6 +36,32 @@ var callGetCumulativeImpact = rpc.declare({
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({
object: 'luci.network-tweaks',
method: 'setComponentEnabled',
@ -43,7 +69,14 @@ var callSetComponentEnabled = rpc.declare({
expect: { }
});
var callGetProxyStatus = rpc.declare({
object: 'luci.network-tweaks',
method: 'getProxyStatus',
expect: { }
});
return view.extend({
proxyStatusData: {},
componentsData: [],
cumulativeData: {},
networkModeData: {},
@ -60,7 +93,8 @@ return view.extend({
return Promise.all([
callNetworkTweaksStatus(),
callGetNetworkComponents(),
callGetCumulativeImpact()
callGetCumulativeImpact(),
callGetProxyStatus()
]);
},
@ -68,10 +102,12 @@ return view.extend({
var status = data[0] || {};
var componentsResponse = data[1] || {};
var cumulativeResponse = data[2] || {};
var proxyStatus = data[3] || {};
this.componentsData = componentsResponse.components || [];
this.cumulativeData = cumulativeResponse || {};
this.networkModeData = componentsResponse.network_mode || {};
this.proxyStatusData = proxyStatus || {};
var m, s, o;
@ -129,6 +165,7 @@ return view.extend({
return E('div', { 'class': 'network-tweaks-dashboard' }, [
this.renderCumulativeImpact(),
this.renderNetworkModeStatus(),
this.renderProxySettings(),
this.renderComponentsGrid(),
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() {
var components = this.componentsData;
@ -362,14 +530,17 @@ return view.extend({
refreshData: function() {
return Promise.all([
callGetNetworkComponents(),
callGetCumulativeImpact()
callGetCumulativeImpact(),
callGetProxyStatus()
]).then(L.bind(function(data) {
var componentsResponse = data[0] || {};
var cumulativeResponse = data[1] || {};
var proxyStatus = data[2] || {};
this.componentsData = componentsResponse.components || [];
this.cumulativeData = cumulativeResponse || {};
this.networkModeData = componentsResponse.network_mode || {};
this.proxyStatusData = proxyStatus || {};
this.updateDisplay();
}, this));
@ -383,6 +554,7 @@ return view.extend({
dom.content(dashboard, [
this.renderCumulativeImpact(),
this.renderNetworkModeStatus(),
this.renderProxySettings(),
this.renderComponentsGrid(),
this.renderSyncSection()
]);

View File

@ -284,6 +284,13 @@ case "$1" in
json_add_string "app_id" "string"
json_add_string "enabled" "boolean"
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
;;
@ -465,6 +472,166 @@ case "$1" in
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_add_boolean "success" 0

View File

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

View File

@ -3,15 +3,17 @@
"description": "Grant access to Network Tweaks",
"read": {
"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": {
"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_VERSION:=1.0.0
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -321,7 +321,12 @@ get_security_stats() {
# HAProxy connections (if running in LXC)
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
haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n')
haproxy_conns=${haproxy_conns:-0}

View File

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

View File

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