mirror of
https://github.com/billz/raspap-webgui.git
synced 2025-12-27 07:31:09 +01:00
Merge pull request #1828 from RaspAP/feat/net-activity-led
Feature: Network activity LED indicator
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
locale/**/*.mo
|
||||
app/net_activity
|
||||
|
||||
@@ -661,3 +661,13 @@ a.inactive:focus {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.led-pulse {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
.hostapd-led {
|
||||
color: #28a745;
|
||||
opacity: 1;
|
||||
transition: opacity 0.05s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,6 +1022,31 @@ function disableValidation(form) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateActivityLED() {
|
||||
const threshold_bytes = 300;
|
||||
fetch('/app/net_activity')
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
const activity = parseInt(data.trim());
|
||||
const leds = document.querySelectorAll('.hostapd-led');
|
||||
|
||||
if (!isNaN(activity)) {
|
||||
leds.forEach(led => {
|
||||
if (activity > threshold_bytes) {
|
||||
led.classList.add('led-pulse');
|
||||
setTimeout(() => {
|
||||
led.classList.remove('led-pulse');
|
||||
}, 50);
|
||||
} else {
|
||||
led.classList.remove('led-pulse');
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => { /* ignore fetch errors */ });
|
||||
}
|
||||
setInterval(updateActivityLED, 100);
|
||||
|
||||
$(document).ready(function() {
|
||||
const $htmlElement = $('html');
|
||||
const $modeswitch = $('#night-mode');
|
||||
|
||||
@@ -971,7 +971,7 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
|
||||
<div class="col ml-2">
|
||||
<div class="ml-1 sb-status">Status</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
<i class="fas fa-circle hostapd-led <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($memused_led); ?>"></i></span> <?php echo _("Mem Use").': '. htmlspecialchars(strval($memused), ENT_QUOTES); ?>%
|
||||
|
||||
@@ -57,11 +57,17 @@ function DisplayHostAPDConfig()
|
||||
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
|
||||
$status->addMessage('Attempting to start hotspot', 'info');
|
||||
if ($arrHostapdConf['BridgedEnable'] == 1) {
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface br0 --seconds 2', $return);
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface br0 --seconds 1', $return);
|
||||
} elseif ($arrHostapdConf['WifiAPEnable'] == 1) {
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 2', $return);
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface uap0 --seconds 1', $return);
|
||||
} else {
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --seconds 2', $return);
|
||||
// systemctl expects a unit name like raspap-network-activity@wlan0.service, no extra quotes
|
||||
$iface_nonescaped = $_POST['interface'];
|
||||
if (preg_match('/^[a-zA-Z0-9_-]+$/', $iface_nonescaped)) { // validate interface name
|
||||
exec('sudo '.RASPI_CONFIG.'/hostapd/servicestart.sh --interface ' .$iface_nonescaped. ' --seconds 1', $return);
|
||||
} else {
|
||||
throw new \Exception('Invalid network interface');
|
||||
}
|
||||
}
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
@@ -69,6 +75,7 @@ function DisplayHostAPDConfig()
|
||||
} elseif (isset($_POST['StopHotspot'])) {
|
||||
$status->addMessage('Attempting to stop hotspot', 'info');
|
||||
exec('sudo /bin/systemctl stop hostapd.service', $return);
|
||||
exec('sudo systemctl stop "raspap-network-activity@*.service"');
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ function _update_raspap() {
|
||||
_download_latest_files
|
||||
_change_file_ownership
|
||||
_patch_system_files
|
||||
_enable_network_activity_monitor
|
||||
_create_plugin_scripts
|
||||
_install_complete
|
||||
}
|
||||
@@ -818,9 +819,38 @@ function _configure_networking() {
|
||||
echo -e
|
||||
_enable_raspap_daemon
|
||||
fi
|
||||
|
||||
# Enable RaspAP network activity monitor
|
||||
_enable_network_activity_monitor
|
||||
|
||||
_install_status 0
|
||||
}
|
||||
|
||||
# Install and enable RaspAP network activity monitor
|
||||
function _enable_network_activity_monitor() {
|
||||
_install_log "Enabling RaspAP network activity monitor"
|
||||
echo "Compiling raspap-network-monitor.c to /usr/local/bin/"
|
||||
if ! command -v gcc >/dev/null 2>&1; then
|
||||
echo "gcc not found, installing..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc || _install_status 1 "Failed to install gcc"
|
||||
fi
|
||||
sudo gcc -O2 -o /usr/local/bin/raspap-network-monitor $webroot_dir/installers/raspap-network-monitor.c || _install_status 1 "Failed to compile raspap-network-monitor.c"
|
||||
echo "Copying raspap-network-activity@.service to /lib/systemd/system/"
|
||||
sudo cp $webroot_dir/installers/raspap-network-activity@.service /lib/systemd/system/ || _install_status 1 "Unable to move raspap-network-activity.service file"
|
||||
sudo systemctl daemon-reload
|
||||
echo "Enabling raspap-network-activity@wlan0.service"
|
||||
sudo systemctl enable raspap-network-activity@wlan0.service || _install_status 1 "Failed to enable raspap-network-activity.service"
|
||||
echo "Starting raspap-network-activity@wlan0.service"
|
||||
sudo systemctl start raspap-network-activity@wlan0.service || _install_status 1 "Failed to start raspap-network-activity.service"
|
||||
sleep 0.5
|
||||
echo "Symlinking /dev/shm/net_activity to $webroot_dir/app/net_activity"
|
||||
sudo ln -sf /dev/shm/net_activity $webroot_dir/app/net_activity || _install_status 1 "Failed to link net_activity to ${webroot_dir}/app"
|
||||
echo "Setting ownership for ${raspap_user} on ${webroot_dir}/app/net_activity"
|
||||
sudo chown -R $raspap_user:$raspap_user $webroot_dir/app/net_activity || _install_status 1 "Unable to set ownership of ${webroot_dir}/app/net_activity"
|
||||
echo "Network activity monitor enabled"
|
||||
}
|
||||
|
||||
# Prompt to configure TCP BBR option
|
||||
function _prompt_configure_tcp_bbr() {
|
||||
_install_log "Configure TCP BBR congestion control"
|
||||
|
||||
15
installers/raspap-network-activity@.service
Normal file
15
installers/raspap-network-activity@.service
Normal file
@@ -0,0 +1,15 @@
|
||||
# Author: BillZ <billzimmerman@gmail.com>
|
||||
|
||||
[Unit]
|
||||
Description=RaspAP Network Activity Monitor for %I
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/raspap-network-monitor %i
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
106
installers/raspap-network-monitor.c
Normal file
106
installers/raspap-network-monitor.c
Normal file
@@ -0,0 +1,106 @@
|
||||
// raspap-network-monitor.c
|
||||
|
||||
/*
|
||||
RaspAP Network Activity Monitor
|
||||
Author: @billz <billzimmerman@gmail.com>
|
||||
Author URI: https://github.com/billz/
|
||||
License: GNU General Public License v3.0
|
||||
License URI: https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
|
||||
Usage: raspap-network-monitor [interface]
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TMPFILE "/dev/shm/net_activity"
|
||||
#define POLL_INTERVAL_MS 100 // 100 milliseconds
|
||||
|
||||
unsigned long read_interface_bytes(const char *iface) {
|
||||
FILE *fp = fopen("/proc/net/dev", "r");
|
||||
if (!fp) return 0;
|
||||
|
||||
char line[512];
|
||||
unsigned long rx = 0, tx = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (strstr(line, iface)) {
|
||||
char *ptr = strchr(line, ':');
|
||||
if (ptr) {
|
||||
sscanf(ptr + 1, "%lu %*u %*u %*u %*u %*u %*u %*u %lu", &rx, &tx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return rx + tx;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <interface>\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char *iface = argv[1];
|
||||
unsigned long prev_total = read_interface_bytes(iface);
|
||||
|
||||
// create a timerfd
|
||||
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
|
||||
if (tfd == -1) {
|
||||
perror("timerfd_create");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct itimerspec timer;
|
||||
timer.it_interval.tv_sec = 0;
|
||||
timer.it_interval.tv_nsec = POLL_INTERVAL_MS * 1000000; // interval
|
||||
timer.it_value.tv_sec = 0;
|
||||
timer.it_value.tv_nsec = POLL_INTERVAL_MS * 1000000; // initial expiration
|
||||
|
||||
if (timerfd_settime(tfd, 0, &timer, NULL) == -1) {
|
||||
perror("timerfd_settime");
|
||||
close(tfd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct pollfd fds;
|
||||
fds.fd = tfd;
|
||||
fds.events = POLLIN;
|
||||
|
||||
for (;;) {
|
||||
int ret = poll(&fds, 1, -1);
|
||||
if (ret == -1) {
|
||||
perror("poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLIN) {
|
||||
uint64_t expirations;
|
||||
read(tfd, &expirations, sizeof(expirations)); // clear timer
|
||||
|
||||
unsigned long curr_total = read_interface_bytes(iface);
|
||||
unsigned long diff = (curr_total >= prev_total) ? (curr_total - prev_total) : 0;
|
||||
prev_total = curr_total;
|
||||
|
||||
FILE *out = fopen(TMPFILE, "w");
|
||||
if (out) {
|
||||
fprintf(out, "%lu\n", diff);
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(tfd);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -80,3 +80,5 @@ www-data ALL=(ALL) NOPASSWD:/bin/truncate -s 0 /tmp/*.log,/bin/truncate -s 0 /va
|
||||
www-data ALL=(ALL) NOPASSWD:/usr/bin/vnstat *
|
||||
www-data ALL=(ALL) NOPASSWD:/usr/sbin/visudo -cf *
|
||||
www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh
|
||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl start raspap-network-activity@*.service
|
||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl stop raspap-network-activity@*.service
|
||||
|
||||
@@ -16,7 +16,7 @@ After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash /etc/raspap/hostapd/servicestart.sh --interface uap0 --seconds 3
|
||||
ExecStart=/bin/bash /etc/raspap/hostapd/servicestart.sh --seconds 1
|
||||
RemainAfterExit=no
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -34,6 +34,20 @@ esac
|
||||
done
|
||||
set -- "${positional[@]}"
|
||||
|
||||
# Load config file into associative array
|
||||
declare -A config
|
||||
if [ -r "$CONFIGFILE" ]; then
|
||||
while IFS=" = " read -r key value; do
|
||||
config["$key"]="$value"
|
||||
done < "$CONFIGFILE"
|
||||
fi
|
||||
|
||||
# Set interface from config if not set by parameter
|
||||
if [ -z "$interface" ] && [ -n "${config[WifiInterface]}" ]; then
|
||||
interface="${config[WifiInterface]}"
|
||||
echo "Interface not provided. Using interface from config: $interface"
|
||||
fi
|
||||
|
||||
echo "Stopping network services..."
|
||||
if [ $OPENVPNENABLED -eq 1 ]; then
|
||||
systemctl stop openvpn-client@client
|
||||
@@ -42,64 +56,58 @@ systemctl stop systemd-networkd
|
||||
systemctl stop hostapd.service
|
||||
systemctl stop dnsmasq.service
|
||||
systemctl stop dhcpcd.service
|
||||
systemctl stop 'raspap-network-activity@*.service'
|
||||
|
||||
if [ "${action}" = "stop" ]; then
|
||||
echo "Services stopped. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -f "$DAEMONPATH" ] && [ ! -z "$interface" ]; then
|
||||
if [ -f "$DAEMONPATH" ] && [ -n "$interface" ]; then
|
||||
echo "Changing RaspAP Daemon --interface to $interface"
|
||||
sed -i "s/\(--interface \)[[:alnum:]]*/\1$interface/" "$DAEMONPATH"
|
||||
fi
|
||||
|
||||
if [ -r "$CONFIGFILE" ]; then
|
||||
declare -A config
|
||||
while IFS=" = " read -r key value; do
|
||||
config["$key"]="$value"
|
||||
done < "$CONFIGFILE"
|
||||
if [ "${config[BridgedEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "br0" ]; then
|
||||
echo "Stopping systemd-networkd"
|
||||
systemctl stop systemd-networkd
|
||||
|
||||
if [ "${config[BridgedEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "br0" ]; then
|
||||
echo "Stopping systemd-networkd"
|
||||
systemctl stop systemd-networkd
|
||||
echo "Restarting eth0 interface..."
|
||||
ip link set down eth0
|
||||
ip link set up eth0
|
||||
|
||||
echo "Restarting eth0 interface..."
|
||||
ip link set down eth0
|
||||
ip link set up eth0
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
echo "Enabling systemd-networkd"
|
||||
systemctl start systemd-networkd
|
||||
systemctl enable systemd-networkd
|
||||
fi
|
||||
else
|
||||
echo "Disabling systemd-networkd"
|
||||
systemctl disable systemd-networkd
|
||||
|
||||
echo "Enabling systemd-networkd"
|
||||
systemctl start systemd-networkd
|
||||
systemctl enable systemd-networkd
|
||||
fi
|
||||
else
|
||||
echo "Disabling systemd-networkd"
|
||||
systemctl disable systemd-networkd
|
||||
ip link ls up | grep -q 'br0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing br0 interface..."
|
||||
ip link set down br0
|
||||
ip link del dev br0
|
||||
fi
|
||||
|
||||
ip link ls up | grep -q 'br0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing br0 interface..."
|
||||
ip link set down br0
|
||||
ip link del dev br0
|
||||
fi
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "uap0" ]; then
|
||||
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
if [ "${interface}" = "uap0" ]; then
|
||||
|
||||
ip link ls up | grep -q 'uap0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
fi
|
||||
|
||||
echo "Adding uap0 interface to ${config[WifiManaged]}"
|
||||
iw dev ${config[WifiManaged]} interface add uap0 type __ap
|
||||
# Bring up uap0 interface
|
||||
ifconfig uap0 up
|
||||
ip link ls up | grep -q 'uap0' &> /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
echo "Removing uap0 interface..."
|
||||
iw dev uap0 del
|
||||
fi
|
||||
|
||||
echo "Adding uap0 interface to ${config[WifiManaged]}"
|
||||
iw dev ${config[WifiManaged]} interface add uap0 type __ap
|
||||
# Bring up uap0 interface
|
||||
ifconfig uap0 up
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -114,12 +122,13 @@ sleep "${seconds}"
|
||||
|
||||
systemctl start dnsmasq.service
|
||||
|
||||
echo "Starting raspap-network-activity@${interface}.service"
|
||||
systemctl start raspap-network-activity@${interface}.service
|
||||
|
||||
if [ $OPENVPNENABLED -eq 1 ]; then
|
||||
systemctl start openvpn-client@client
|
||||
fi
|
||||
|
||||
# @mp035 found that the wifi client interface would stop every 8 seconds
|
||||
# for about 16 seconds. Reassociating seems to solve this
|
||||
if [ "${config[WifiAPEnable]}" = 1 ]; then
|
||||
echo "Reassociating wifi client interface..."
|
||||
sleep "${seconds}"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
|
||||
<span class="icon"><i class="fas fa-circle service-status-<?php echo $state ?>"></i></span>
|
||||
<span class="icon"><i class="fas fa-circle hostapd-led service-status-<?php echo $state ?>"></i></span>
|
||||
<span class="text service-status"><?php echo strtolower($interface) .' '. _($state) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-light btn-icon-split btn-sm service-status float-end">
|
||||
<span class="icon text-gray-600"><i class="fas fa-circle service-status-<?php echo $serviceStatus ?>"></i></span>
|
||||
<span class="icon text-gray-600"><i class="fas fa-circle hostapd-led service-status-<?php echo $serviceStatus ?>"></i></span>
|
||||
<span class="text service-status">hostapd <?php echo _($serviceStatus) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user