From 4b9dd87648c7161cc9c3b9d52130fa7baaf00861 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Tue, 4 Mar 2025 00:59:48 -0800 Subject: [PATCH 1/6] Fixes and Improvements 1) IMPROVED: Modified all SQLite3 calls to capture and log errors in the system log. 2) IMPROVED: Modified SQLite3 configuration parameters to improve the trimming of records from the database and then perform "garbage collection" of deleted entries to reclaim unused space & avoid excessive fragmentation. 3) IMPROVED: Modified SQLite3 configuration parameters to improve the processing of database records. 4) IMPROVED: Modified code to set the corresponding priority level of log entries when calling the built-in logger utility. 5) IMPROVED: Modified the startup call made in the post-mount script to check if the USB-attached disk partition passed as argument has indeed Entware installed. 6) IMPROVED: Added code to show the current database file size information on the CLI menu and the webGUI page. 7) IMPROVED: Added code to show the "JFFS Available" space information for the "Data Storage Location" option on the CLI menu and the webGUI page. 8) IMPROVED: Added code to check if sufficient JFFS storage space is available before moving database-related files/folders from USB location to JFFS partition. An error message is reported if not enough space is available, and the move request is aborted. 9) IMPROVED: Added code to check if the available JFFS storage space falls below 20% of total space or 10MB (whichever is lower) and report a warning when it does. A warning message is also shown on the SSH CLI menu and WebGUI page. 10) IMPROVED: Added and modified code so that every time the SSH CLI menu is run, it checks if the WebGUI page has already been mounted. If not found mounted, the script will run the code to remount the WebGUI. 11) IMPROVED: Improved code that creates (during installation) and removes (during uninstallation) the "AddOns" menu tab entry for the WebGUI to make sure it checks for and takes into account other add-ons that may have been installed before or were later installed after the initial installation. 12) IMPROVED: Added "export PATH" statement to give the built-in binaries higher priority than the equivalent Entware binaries. 13) Miscellaneous code improvements & fine-tuning. --- README.md | 2 +- spdmerlin.sh | 4355 +++++++++++++++++++++++++++++++--------------- spdstats_www.asp | 67 +- spdstats_www.js | 820 +++++---- 4 files changed, 3561 insertions(+), 1683 deletions(-) diff --git a/README.md b/README.md index 9749061..7835723 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Shellcheck](https://github.com/jackyaz/spdMerlin/actions/workflows/shellcheck.yml/badge.svg) ## v4.4.6 -### Updated on 2024-Aug-18 +### Updated on 2025-Mar-03 ## About spdMerlin is an internet speedtest and monitoring tool for AsusWRT Merlin with charts for daily, weekly and monthly summaries. It tracks download/upload bandwidth as well as latency, jitter and packet loss. diff --git a/spdmerlin.sh b/spdmerlin.sh index 695d705..c2df6e7 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -13,7 +13,7 @@ ## https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2024-Jul-15 +# Last Modified: 2025-Mar-03 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -27,6 +27,10 @@ # shellcheck disable=SC2059 # shellcheck disable=SC2086 # shellcheck disable=SC2155 +# shellcheck disable=SC2174 +# shellcheck disable=SC3018 +# shellcheck disable=SC3037 +# shellcheck disable=SC3043 # shellcheck disable=SC3045 ############################################################## @@ -37,16 +41,49 @@ readonly SCRIPT_VERSION="v4.4.6" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://jackyaz.io/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" -readonly SCRIPT_WEBPAGE_DIR="$(readlink /www/user)" +readonly SCRIPT_WEBPAGE_DIR="$(readlink -f /www/user)" readonly SCRIPT_WEB_DIR="$SCRIPT_WEBPAGE_DIR/$SCRIPT_NAME_LOWER" readonly SHARED_DIR="/jffs/addons/shared-jy" readonly SHARED_REPO="https://jackyaz.io/shared-jy/master" readonly SHARED_WEB_DIR="$SCRIPT_WEBPAGE_DIR/shared-jy" +readonly TEMP_MENU_TREE="/tmp/menuTree.js" readonly HOME_DIR="/$(readlink "$HOME")" readonly OOKLA_DIR="$SCRIPT_DIR/ookla" readonly OOKLA_LICENSE_DIR="$SCRIPT_DIR/ooklalicense" readonly OOKLA_HOME_DIR="$HOME_DIR/.config/ookla" +readonly FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +readonly scriptVersRegExp="v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})" +readonly webPageMenuAddons="menuName: \"Addons\"," +readonly webPageHelpSupprt="tabName: \"Help & Support\"}," +readonly webPageFileRegExp="user([1-9]|[1-2][0-9])[.]asp" +readonly webPageLineTabExp="\{url: \"$webPageFileRegExp\", tabName: " +readonly webPageLineRegExp="${webPageLineTabExp}\"$SCRIPT_NAME\"\}," +readonly BEGIN_MenuAddOnsTag="/\*\*BEGIN:_AddOns_\*\*/" +readonly ENDIN_MenuAddOnsTag="/\*\*ENDIN:_AddOns_\*\*/" + +# For daily CRON job to trim database # +readonly defTrimDB_Hour=3 +readonly defTrimDB_Mins=5 + +readonly oneHrSec=3600 +readonly _12Hours=43200 +readonly _24Hours=86400 +readonly _36Hours=129600 +readonly oneKByte=1024 +readonly oneMByte=1048576 +readonly ei8MByte=8388608 +readonly ni9MByte=9437184 +readonly tenMByte=10485760 +readonly oneGByte=1073741824 +readonly SHARE_TEMP_DIR="/opt/share/tmp" + +# Give priority to built-in binaries # +export PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH" [ -z "$(nvram get odmpid)" ] && ROUTER_MODEL="$(nvram get productid)" || ROUTER_MODEL="$(nvram get odmpid)" [ -f /opt/bin/sqlite3 ] && SQLITE3_PATH=/opt/bin/sqlite3 || SQLITE3_PATH=/usr/sbin/sqlite3 @@ -66,6 +103,20 @@ readonly PASS="\\e[32m" readonly BOLD="\\e[1m" readonly SETTING="${BOLD}\\e[36m" readonly CLEARFORMAT="\\e[0m" + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-15] ## +##-------------------------------------## +readonly CLRct="\e[0m" +readonly REDct="\e[1;31m" +readonly GRNct="\e[1;32m" +readonly CritIREDct="\e[41m" +readonly CritBREDct="\e[30;101m" +readonly PassBGRNct="\e[30;102m" +readonly WarnBYLWct="\e[30;103m" +readonly WarnIMGNct="\e[45m" +readonly WarnBMGNct="\e[30;105m" + ### End of output format variables ### ### Start of Speedtest Server Variables ### @@ -73,15 +124,33 @@ serverno="" servername="" ### End of Speedtest Server Variables ### +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## # $1 = print to syslog, $2 = message to print, $3 = log level -Print_Output(){ - if [ "$1" = "true" ]; then - logger -t "$SCRIPT_NAME" "$2" +Print_Output() +{ + local prioStr prioNum + if [ $# -gt 2 ] && [ -n "$3" ] + then prioStr="$3" + else prioStr="NOTICE" + fi + if [ "$1" = "true" ] + then + case "$prioStr" in + "$CRIT") prioNum=2 ;; + "$ERR") prioNum=3 ;; + "$WARN") prioNum=4 ;; + "$PASS") prioNum=6 ;; #INFO# + *) prioNum=5 ;; #NOTICE# + esac + logger -t "$SCRIPT_NAME" -p $prioNum "$2" fi - printf "${BOLD}${3}%s${CLEARFORMAT}\\n\\n" "$2" + printf "${BOLD}${3}%s${CLEARFORMAT}\n\n" "$2" } -Firmware_Version_Check(){ +Firmware_Version_Check() +{ if nvram get rc_support | grep -qF "am_addons"; then return 0 else @@ -90,18 +159,22 @@ Firmware_Version_Check(){ } ### Code for these functions inspired by https://github.com/Adamm00 - credit to @Adamm ### -Check_Lock(){ - if [ -f "/tmp/$SCRIPT_NAME.lock" ]; then - ageoflock=$(($(date +%s) - $(date +%s -r /tmp/$SCRIPT_NAME.lock))) - if [ "$ageoflock" -gt 600 ]; then +Check_Lock() +{ + if [ -f "/tmp/$SCRIPT_NAME.lock" ] + then + ageoflock="$(($(/bin/date "+%s") - $(/bin/date "+%s" -r "/tmp/$SCRIPT_NAME.lock")))" + if [ "$ageoflock" -gt 600 ] #10 minutes# + then Print_Output true "Stale lock file found (>600 seconds old) - purging lock" "$ERR" - kill "$(sed -n '1p' /tmp/$SCRIPT_NAME.lock)" >/dev/null 2>&1 + kill "$(sed -n '1p' "/tmp/$SCRIPT_NAME.lock")" >/dev/null 2>&1 Clear_Lock echo "$$" > "/tmp/$SCRIPT_NAME.lock" return 0 else Print_Output true "Lock file found (age: $ageoflock seconds) - stopping to prevent duplicate runs" "$ERR" - if [ -z "$1" ]; then + if [ $# -eq 0 ] || [ -z "$1" ] + then exit 1 else if [ "$1" = "webui" ]; then @@ -117,7 +190,8 @@ Check_Lock(){ fi } -Clear_Lock(){ +Clear_Lock() +{ rm -f "/tmp/$SCRIPT_NAME.lock" 2>/dev/null return 0 } @@ -126,16 +200,21 @@ Check_Swap(){ if [ "$(wc -l < /proc/swaps)" -ge 2 ]; then return 0; else return 1; fi } -############################################################################ - -Set_Version_Custom_Settings(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-02] ## +##----------------------------------------## +Set_Version_Custom_Settings() +{ SETTINGSFILE="/jffs/addons/custom_settings.txt" case "$1" in local) - if [ -f "$SETTINGSFILE" ]; then - if [ "$(grep -c "spdmerlin_version_local" $SETTINGSFILE)" -gt 0 ]; then - if [ "$2" != "$(grep "spdmerlin_version_local" /jffs/addons/custom_settings.txt | cut -f2 -d' ')" ]; then - sed -i "s/spdmerlin_version_local.*/spdmerlin_version_local $2/" "$SETTINGSFILE" + if [ -f "$SETTINGSFILE" ] + then + if [ "$(grep -c "^spdmerlin_version_local" "$SETTINGSFILE")" -gt 0 ] + then + if [ "$2" != "$(grep "^spdmerlin_version_local" "$SETTINGSFILE" | cut -f2 -d' ')" ] + then + sed -i "s/^spdmerlin_version_local.*/spdmerlin_version_local $2/" "$SETTINGSFILE" fi else echo "spdmerlin_version_local $2" >> "$SETTINGSFILE" @@ -145,10 +224,13 @@ Set_Version_Custom_Settings(){ fi ;; server) - if [ -f "$SETTINGSFILE" ]; then - if [ "$(grep -c "spdmerlin_version_server" $SETTINGSFILE)" -gt 0 ]; then - if [ "$2" != "$(grep "spdmerlin_version_server" /jffs/addons/custom_settings.txt | cut -f2 -d' ')" ]; then - sed -i "s/spdmerlin_version_server.*/spdmerlin_version_server $2/" "$SETTINGSFILE" + if [ -f "$SETTINGSFILE" ] + then + if [ "$(grep -c "^spdmerlin_version_server" "$SETTINGSFILE")" -gt 0 ] + then + if [ "$2" != "$(grep "^spdmerlin_version_server" "$SETTINGSFILE" | cut -f2 -d' ')" ] + then + sed -i "s/^spdmerlin_version_server.*/spdmerlin_version_server $2/" "$SETTINGSFILE" fi else echo "spdmerlin_version_server $2" >> "$SETTINGSFILE" @@ -160,20 +242,28 @@ Set_Version_Custom_Settings(){ esac } -Update_Check(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Update_Check() +{ echo 'var updatestatus = "InProgress";' > "$SCRIPT_WEB_DIR/detect_update.js" doupdate="false" - localver=$(grep "SCRIPT_VERSION=" "/jffs/scripts/$SCRIPT_NAME_LOWER" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') - /usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/404/$SCRIPT_NAME_LOWER.sh" | grep -qF "jackyaz" || { Print_Output true "404 error detected - stopping update" "$ERR"; return 1; } - serverver=$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') - if [ "$localver" != "$serverver" ]; then + localver="$(grep "SCRIPT_VERSION=" "/jffs/scripts/$SCRIPT_NAME_LOWER" | grep -m1 -oE "$scriptVersRegExp")" + [ -n "$localver" ] && Set_Version_Custom_Settings local "$localver" + curl -fsL --retry 4 --retry-delay 5 "$SCRIPT_REPO/404/$SCRIPT_NAME_LOWER.sh" | grep -qF "jackyaz" || \ + { Print_Output true "404 error detected - stopping update" "$ERR"; return 1; } + serverver="$(curl -fsL --retry 4 --retry-delay 5 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE "$scriptVersRegExp")" + if [ "$localver" != "$serverver" ] + then doupdate="version" Set_Version_Custom_Settings server "$serverver" echo 'var updatestatus = "'"$serverver"'";' > "$SCRIPT_WEB_DIR/detect_update.js" else localmd5="$(md5sum "/jffs/scripts/$SCRIPT_NAME_LOWER" | awk '{print $1}')" - remotemd5="$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/md5/$SCRIPT_NAME_LOWER.sh" | md5sum | awk '{print $1}')" - if [ "$localmd5" != "$remotemd5" ]; then + remotemd5="$(curl -fsL --retry 4 --retry-delay 5 "$SCRIPT_REPO/md5/$SCRIPT_NAME_LOWER.sh" | md5sum | awk '{print $1}')" + if [ "$localmd5" != "$remotemd5" ] + then doupdate="md5" Set_Version_Custom_Settings server "$serverver-hotfix" echo 'var updatestatus = "'"$serverver-hotfix"'";' > "$SCRIPT_WEB_DIR/detect_update.js" @@ -185,8 +275,13 @@ Update_Check(){ echo "$doupdate,$localver,$serverver" } -Update_Version(){ - if [ -z "$1" ]; then +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Update_Version() +{ + if [ $# -eq 0 ] || [ -z "$1" ] + then updatecheckresult="$(Update_Check)" isupdate="$(echo "$updatecheckresult" | cut -f1 -d',')" localver="$(echo "$updatecheckresult" | cut -f2 -d',')" @@ -198,19 +293,20 @@ Update_Version(){ Print_Output true "MD5 hash of $SCRIPT_NAME does not match - hotfix available - $serverver" "$PASS" fi - if [ "$isupdate" != "false" ]; then - printf "\\n${BOLD}Do you want to continue with the update? (y/n)${CLEARFORMAT} " + if [ "$isupdate" != "false" ] + then + printf "\n${BOLD}Do you want to continue with the update? (y/n)${CLEARFORMAT} " read -r confirm case "$confirm" in y|Y) - printf "\\n" + printf "\n" Update_File README.md Update_File LICENSE Update_File "$ARCH.tar.gz" Update_File spdstats_www.asp Update_File shared-jy.tar.gz - - Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && Print_Output true "$SCRIPT_NAME successfully updated" + Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && \ + Print_Output true "$SCRIPT_NAME successfully updated" "$PASS" chmod 0755 "/jffs/scripts/$SCRIPT_NAME_LOWER" Set_Version_Custom_Settings local "$serverver" Set_Version_Custom_Settings server "$serverver" @@ -220,7 +316,7 @@ Update_Version(){ exit 0 ;; *) - printf "\\n" + printf "\n" Clear_Lock return 1 ;; @@ -231,39 +327,49 @@ Update_Version(){ Clear_Lock fi fi - - if [ "$1" = "force" ]; then - serverver=$(/usr/sbin/curl -fsL --retry 3 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE 'v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})') + + if [ "$1" = "force" ] + then + serverver="$(curl -fsL --retry 4 --retry-delay 5 "$SCRIPT_REPO/version/$SCRIPT_NAME_LOWER.sh" | grep "SCRIPT_VERSION=" | grep -m1 -oE "$scriptVersRegExp")" Print_Output true "Downloading latest version ($serverver) of $SCRIPT_NAME" "$PASS" Update_File README.md Update_File LICENSE Update_File "$ARCH.tar.gz" Update_File spdstats_www.asp Update_File shared-jy.tar.gz - Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && Print_Output true "$SCRIPT_NAME successfully updated" + Download_File "$SCRIPT_REPO/update/$SCRIPT_NAME_LOWER.sh" "/jffs/scripts/$SCRIPT_NAME_LOWER" && \ + Print_Output true "$SCRIPT_NAME successfully updated" "$PASS" chmod 0755 "/jffs/scripts/$SCRIPT_NAME_LOWER" Set_Version_Custom_Settings local "$serverver" Set_Version_Custom_Settings server "$serverver" Clear_Lock - if [ -z "$2" ]; then + if [ $# -lt 2 ] || [ -z "$2" ] + then PressEnter exec "$0" - elif [ "$2" = "unattended" ]; then + elif [ "$2" = "unattended" ] + then exec "$0" postupdate fi exit 0 fi } -Update_File(){ - if [ "$1" = "$ARCH.tar.gz" ]; then +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Update_File() +{ + if [ "$1" = "$ARCH.tar.gz" ] + then tmpfile="/tmp/$1" Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" tar -xzf "$tmpfile" -C "/tmp" rm -f "$tmpfile" localmd5="$(md5sum "$OOKLA_DIR/speedtest" | awk '{print $1}')" tmpmd5="$(md5sum /tmp/speedtest | awk '{print $1}')" - if [ "$localmd5" != "$tmpmd5" ]; then + if [ "$localmd5" != "$tmpmd5" ] + then rm -f "$OOKLA_DIR"/* Download_File "$SCRIPT_REPO/files/$1" "$OOKLA_DIR/$1" tar -xzf "$OOKLA_DIR/$1" -C "$OOKLA_DIR" @@ -272,39 +378,50 @@ Update_File(){ Print_Output true "New version of Speedtest CLI downloaded to $OOKLA_DIR/speedtest" "$PASS" fi rm -f "/tmp/speedtest*" - elif [ "$1" = "spdstats_www.asp" ]; then + elif [ "$1" = "spdstats_www.asp" ] + then tmpfile="/tmp/$1" - Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" - if ! diff -q "$tmpfile" "$SCRIPT_DIR/$1" >/dev/null 2>&1; then - if [ -f "$SCRIPT_DIR/$1" ]; then + if [ -f "$SCRIPT_DIR/$1" ] + then + Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" + if ! diff -q "$tmpfile" "$SCRIPT_DIR/$1" >/dev/null 2>&1 + then Get_WebUI_Page "$SCRIPT_DIR/$1" - sed -i "\\~$MyPage~d" /tmp/menuTree.js - rm -f "$SCRIPT_WEBPAGE_DIR/$MyPage" 2>/dev/null + sed -i "\\~$MyWebPage~d" "$TEMP_MENU_TREE" + rm -f "$SCRIPT_WEBPAGE_DIR/$MyWebPage" 2>/dev/null + Download_File "$SCRIPT_REPO/files/$1" "$SCRIPT_DIR/$1" + Print_Output true "New version of $1 downloaded" "$PASS" + Mount_WebUI fi + rm -f "$tmpfile" + else Download_File "$SCRIPT_REPO/files/$1" "$SCRIPT_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" Mount_WebUI fi - rm -f "$tmpfile" - elif [ "$1" = "shared-jy.tar.gz" ]; then - if [ ! -f "$SHARED_DIR/$1.md5" ]; then + elif [ "$1" = "shared-jy.tar.gz" ] + then + if [ ! -f "$SHARED_DIR/${1}.md5" ] + then Download_File "$SHARED_REPO/$1" "$SHARED_DIR/$1" - Download_File "$SHARED_REPO/$1.md5" "$SHARED_DIR/$1.md5" + Download_File "$SHARED_REPO/${1}.md5" "$SHARED_DIR/${1}.md5" tar -xzf "$SHARED_DIR/$1" -C "$SHARED_DIR" rm -f "$SHARED_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" else - localmd5="$(cat "$SHARED_DIR/$1.md5")" - remotemd5="$(/usr/sbin/curl -fsL --retry 3 "$SHARED_REPO/$1.md5")" - if [ "$localmd5" != "$remotemd5" ]; then + localmd5="$(cat "$SHARED_DIR/${1}.md5")" + remotemd5="$(curl -fsL --retry 4 --retry-delay 5 "$SHARED_REPO/${1}.md5")" + if [ "$localmd5" != "$remotemd5" ] + then Download_File "$SHARED_REPO/$1" "$SHARED_DIR/$1" - Download_File "$SHARED_REPO/$1.md5" "$SHARED_DIR/$1.md5" + Download_File "$SHARED_REPO/${1}.md5" "$SHARED_DIR/${1}.md5" tar -xzf "$SHARED_DIR/$1" -C "$SHARED_DIR" rm -f "$SHARED_DIR/$1" Print_Output true "New version of $1 downloaded" "$PASS" fi fi - elif [ "$1" = "README.md" ] || [ "$1" = "LICENSE" ]; then + elif [ "$1" = "README.md" ] || [ "$1" = "LICENSE" ] + then tmpfile="/tmp/$1" Download_File "$SCRIPT_REPO/files/$1" "$tmpfile" if ! diff -q "$tmpfile" "$SCRIPT_DIR/$1" >/dev/null 2>&1; then @@ -316,7 +433,8 @@ Update_File(){ fi } -Validate_Bandwidth(){ +Validate_Bandwidth() +{ if echo "$1" | /bin/grep -oq "^[0-9]*\.\?[0-9]*$"; then return 0 else @@ -324,7 +442,8 @@ Validate_Bandwidth(){ fi } -Validate_Number(){ +Validate_Number() +{ if [ "$1" -eq "$1" ] 2>/dev/null; then return 0 else @@ -332,8 +451,11 @@ Validate_Number(){ fi } - -Create_Dirs(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Create_Dirs() +{ if [ ! -d "$SCRIPT_DIR" ]; then mkdir -p "$SCRIPT_DIR" fi @@ -369,120 +491,158 @@ Create_Dirs(){ if [ ! -d "$SCRIPT_WEB_DIR" ]; then mkdir -p "$SCRIPT_WEB_DIR" fi + + if [ ! -d "$SHARE_TEMP_DIR" ] + then + mkdir -m 777 -p "$SHARE_TEMP_DIR" + export SQLITE_TMPDIR TMPDIR + fi } -Create_Symlinks(){ - printf "WAN\\n" > "$SCRIPT_INTERFACES" - - for index in 1 2 3 4 5; do +Create_Symlinks() +{ + printf "WAN\n" > "$SCRIPT_INTERFACES" + + for index in 1 2 3 4 5 + do comment="" - if [ ! -f "/sys/class/net/tun1$index/operstate" ] || [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/tun1$index/operstate" ] || \ + [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ] + then comment=" #excluded - interface not up#" fi if [ "$index" -lt 5 ]; then - printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "VPNC%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" else - printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "VPNC%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" fi done - for index in 1 2 3 4 5; do + for index in 1 2 3 4 5 + do comment="" - if [ ! -f "/sys/class/net/wgc$index/operstate" ] || [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/wgc$index/operstate" ] || \ + [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ] + then comment=" #excluded - interface not up#" fi if [ "$index" -lt 5 ]; then - printf "WGVPN%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" else - printf "WGVPN%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" fi done - - if [ "$1" = "force" ]; then + + if [ $# -gt 0 ] && [ "$1" = "force" ]; then rm -f "$SCRIPT_INTERFACES_USER" fi - + if [ ! -f "$SCRIPT_INTERFACES_USER" ]; then touch "$SCRIPT_INTERFACES_USER" fi - - while IFS='' read -r line || [ -n "$line" ]; do - if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ]; then - printf "%s\\n" "$line" >> "$SCRIPT_INTERFACES_USER" + + while IFS='' read -r line || [ -n "$line" ] + do + if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ] + then + printf "%s\n" "$line" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" - + interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 - until [ $COUNTER -gt "$interfacecount" ]; do + until [ "$COUNTER" -gt "$interfacecount" ] + do Set_Interface_State "$COUNTER" - COUNTER=$((COUNTER + 1)) + COUNTER="$((COUNTER + 1))" done - + rm -rf "${SCRIPT_WEB_DIR:?}/"* 2>/dev/null - + ln -s /tmp/spd-stats.txt "$SCRIPT_WEB_DIR/spd-stats.htm" 2>/dev/null ln -s /tmp/spd-result.txt "$SCRIPT_WEB_DIR/spd-result.htm" 2>/dev/null ln -s /tmp/detect_spdtest.js "$SCRIPT_WEB_DIR/detect_spdtest.js" 2>/dev/null ln -s /tmp/spdmerlin-binary "$SCRIPT_WEB_DIR/spd-binary.htm" 2>/dev/null ln -s "$SCRIPT_STORAGE_DIR/.autobwoutfile" "$SCRIPT_WEB_DIR/autobwoutfile.htm" 2>/dev/null - + ln -s "$SCRIPT_CONF" "$SCRIPT_WEB_DIR/config.htm" 2>/dev/null ln -s "$SCRIPT_INTERFACES_USER" "$SCRIPT_WEB_DIR/interfaces_user.htm" 2>/dev/null ln -s "$SCRIPT_STORAGE_DIR/spdtitletext.js" "$SCRIPT_WEB_DIR/spdtitletext.js" 2>/dev/null - - FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" - for IFACE_NAME in $FULL_IFACELIST; do + + for IFACE_NAME in $FULL_IFACELIST + do ln -s "$SCRIPT_STORAGE_DIR/lastx_${IFACE_NAME}.csv" "$SCRIPT_WEB_DIR/lastx_${IFACE_NAME}.htm" done - + ln -s "$CSV_OUTPUT_DIR" "$SCRIPT_WEB_DIR/csv" 2>/dev/null - + if [ ! -d "$SHARED_WEB_DIR" ]; then ln -s "$SHARED_DIR" "$SHARED_WEB_DIR" 2>/dev/null fi } -Conf_FromSettings(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +Conf_FromSettings() +{ SETTINGSFILE="/jffs/addons/custom_settings.txt" TMPFILE="/tmp/spdmerlin_settings.txt" - if [ -f "$SETTINGSFILE" ]; then - if [ "$(grep "spdmerlin_" $SETTINGSFILE | grep -v "version" -c)" -gt 0 ]; then + + if [ -f "$SETTINGSFILE" ] + then + if [ "$(grep "^spdmerlin_" $SETTINGSFILE | grep -v "version" -c)" -gt 0 ] + then Print_Output true "Updated settings from WebUI found, merging into $SCRIPT_CONF" "$PASS" - cp -a "$SCRIPT_CONF" "$SCRIPT_CONF.bak" - grep "spdmerlin_" "$SETTINGSFILE" | grep -v "version" > "$TMPFILE" - sed -i "s/spdmerlin_//g;s/ /=/g" "$TMPFILE" - while IFS='' read -r line || [ -n "$line" ]; do - SETTINGNAME="$(echo "$line" | cut -f1 -d'=' | awk '{ print toupper($1) }')" + cp -a "$SCRIPT_CONF" "${SCRIPT_CONF}.bak" + grep "^spdmerlin_" "$SETTINGSFILE" | grep -v "version" > "$TMPFILE" + sed -i "s/^spdmerlin_//g;s/ /=/g" "$TMPFILE" + while IFS='' read -r line || [ -n "$line" ] + do + SETTINGNAME="$(echo "$line" | cut -f1 -d'=' | awk '{print toupper($1)}')" SETTINGVALUE="$(echo "$line" | cut -f2- -d'=' | sed "s/=/ /g")" sed -i "s~$SETTINGNAME=.*~$SETTINGNAME=$SETTINGVALUE~" "$SCRIPT_CONF" done < "$TMPFILE" - grep 'spdmerlin_version' "$SETTINGSFILE" > "$TMPFILE" + + grep '^spdmerlin_version' "$SETTINGSFILE" > "$TMPFILE" sed -i "\\~spdmerlin_~d" "$SETTINGSFILE" - mv "$SETTINGSFILE" "$SETTINGSFILE.bak" - cat "$SETTINGSFILE.bak" "$TMPFILE" > "$SETTINGSFILE" + mv -f "$SETTINGSFILE" "${SETTINGSFILE}.bak" + cat "${SETTINGSFILE}.bak" "$TMPFILE" > "$SETTINGSFILE" rm -f "$TMPFILE" - rm -f "$SETTINGSFILE.bak" - - ScriptStorageLocation "$(ScriptStorageLocation check)" - Create_Symlinks - - if AutomaticMode check; then - Auto_Cron delete 2>/dev/null - Auto_Cron create 2>/dev/null - else + rm -f "${SETTINGSFILE}.bak" + + if diff "$SCRIPT_CONF" "${SCRIPT_CONF}.bak" | grep -q "STORAGELOCATION=" + then + STORAGEtype="$(ScriptStorageLocation check)" + if [ "$STORAGEtype" = "jffs" ] + then + ## Check if enough free space is available in JFFS ## + if _Check_JFFS_SpaceAvailable_ "$SCRIPT_STORAGE_DIR" + then ScriptStorageLocation jffs + else ScriptStorageLocation usb + fi + elif [ "$STORAGEtype" = "usb" ] + then + ScriptStorageLocation usb + fi + Create_Symlinks + fi + if diff "$SCRIPT_CONF" "${SCRIPT_CONF}.bak" | grep -qE "(SCHDAYS|SCHHOUR|SCHMINS|AUTOMATICMODE=)" + then Auto_Cron delete 2>/dev/null + AutomaticMode check && Auto_Cron create 2>/dev/null fi - - if [ "$(AutoBWEnable check)" = "true" ]; then + if [ "$(AutoBWEnable check)" = "true" ] + then if [ "$(ExcludeFromQoS check)" = "false" ]; then Print_Output true "Enabling Exclude from QoS (required for AutoBW)" ExcludeFromQoS enable fi fi - - Generate_CSVs - + if diff "$SCRIPT_CONF" "${SCRIPT_CONF}.bak" | grep -qE "(STORERESULTURL=|OUTPUTTIMEMODE=|DAYSTOKEEP=|LASTXRESULTS=)" + then + Generate_CSVs + fi Print_Output true "Merge of updated settings from WebUI completed successfully" "$PASS" else Print_Output true "No updated settings from WebUI found, no merge into $SCRIPT_CONF necessary" "$PASS" @@ -490,55 +650,74 @@ Conf_FromSettings(){ fi } -Interfaces_FromSettings(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-02] ## +##----------------------------------------## +Interfaces_FromSettings() +{ SETTINGSFILE="/jffs/addons/custom_settings.txt" - if [ -f "$SETTINGSFILE" ]; then - if grep -q "spdmerlin_ifaces_enabled" "$SETTINGSFILE"; then + if [ -f "$SETTINGSFILE" ] + then + if grep -q "spdmerlin_ifaces_enabled" "$SETTINGSFILE" + then Print_Output true "Updated interfaces from WebUI found, merging into $SCRIPT_INTERFACES_USER" "$PASS" - cp -a "$SCRIPT_INTERFACES_USER" "$SCRIPT_INTERFACES_USER.bak" + cp -a "$SCRIPT_INTERFACES_USER" "${SCRIPT_INTERFACES_USER}.bak" SETTINGVALUE="$(grep "spdmerlin_ifaces_enabled" "$SETTINGSFILE" | cut -f2 -d' ')" sed -i "\\~spdmerlin_ifaces_enabled~d" "$SETTINGSFILE" - + printf "WAN #excluded#\\n" > "$SCRIPT_INTERFACES" - - for index in 1 2 3 4 5; do + + for index in 1 2 3 4 5 + do comment=" #excluded#" - if [ ! -f "/sys/class/net/tun1$index/operstate" ] || [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/tun1$index/operstate" ] || \ + [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ] + then comment=" #excluded - interface not up#" fi printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" done - for index in 1 2 3 4 5; do + for index in 1 2 3 4 5 + do comment=" #excluded#" - if [ ! -f "/sys/class/net/wgc$index/operstate" ] || [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/wgc$index/operstate" ] || \ + [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ] + then comment=" #excluded - interface not up#" fi printf "WGVPN%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" done - + echo "" > "$SCRIPT_INTERFACES_USER" - - while IFS='' read -r line || [ -n "$line" ]; do - if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ]; then - printf "%s\\n" "$line" >> "$SCRIPT_INTERFACES_USER" + + while IFS='' read -r line || [ -n "$line" ] + do + if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ] + then + printf "%s\n" "$line" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" - + interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 - until [ $COUNTER -gt "$interfacecount" ]; do + until [ "$COUNTER" -gt "$interfacecount" ] + do Set_Interface_State "$COUNTER" - COUNTER=$((COUNTER + 1)) + COUNTER="$((COUNTER + 1))" done - - for iface in $(echo "$SETTINGVALUE" | sed "s/,/ /g"); do + + for iface in $(echo "$SETTINGVALUE" | sed "s/,/ /g") + do ifacelinenumber="$(grep -n "$iface" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" interfaceline="$(sed "$ifacelinenumber!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - - if echo "$interfaceline" | grep -q "#excluded" ; then + + if echo "$interfaceline" | grep -q "#excluded" + then IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then sed -i "${ifacelinenumber}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" else sed -i "${ifacelinenumber}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" @@ -546,15 +725,17 @@ Interfaces_FromSettings(){ fi else IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then sed -i "$ifacelinenumber"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" fi fi done - + awk 'NF' "$SCRIPT_INTERFACES_USER" > /tmp/spd-interfaces - mv /tmp/spd-interfaces "$SCRIPT_INTERFACES_USER" - + mv -f /tmp/spd-interfaces "$SCRIPT_INTERFACES_USER" + Print_Output true "Merge of updated interfaces from WebUI completed successfully" "$PASS" else Print_Output true "No updated interfaces from WebUI found, no merge into $SCRIPT_INTERFACES_USER necessary" "$PASS" @@ -562,93 +743,164 @@ Interfaces_FromSettings(){ fi } -Conf_Exists(){ - if [ -f "$SCRIPT_CONF" ]; then +##-------------------------------------## +## Added by Martinski W. [2024-Nov-15] ## +##-------------------------------------## +_GetDefaultSpeedTestBinary_() +{ + if [ -f /usr/sbin/ookla ] + then echo "builtin" + else echo "external" + fi +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-02] ## +##----------------------------------------## +Conf_Exists() +{ + local AUTOMATEDopt + + if [ -f "$SCRIPT_CONF" ] + then dos2unix "$SCRIPT_CONF" chmod 0644 "$SCRIPT_CONF" sed -i -e 's/"//g' "$SCRIPT_CONF" - if grep -q "SCHEDULESTART" "$SCRIPT_CONF"; then + if grep -q "^AUTOMATED=.*" "$SCRIPT_CONF" + then + AUTOMATEDopt="$(grep "^AUTOMATED=" "$SCRIPT_CONF" | cut -f2 -d'=')" + sed -i 's/^AUTOMATED=.*$/AUTOMATICMODE='"$AUTOMATEDopt"'/' "$SCRIPT_CONF" + fi + if ! grep -q "^AUTOMATICMODE=" "$SCRIPT_CONF"; then + echo "AUTOMATICMODE=true" >> "$SCRIPT_CONF" + fi + if grep -q "SCHEDULESTART" "$SCRIPT_CONF" + then { - echo "SCHDAYS=*"; - echo "SCHHOURS=*"; - echo "SCHMINS=12,42"; + echo "SCHDAYS=*"; + echo "SCHHOURS=*"; + echo "SCHMINS=12,42"; } >> "$SCRIPT_CONF" sed -i '/SCHEDULESTART/d;/SCHEDULEEND/d;/MINUTE/d;/TESTFREQUENCY/d' "$SCRIPT_CONF" - if AutomaticMode check; then - Auto_Cron delete 2>/dev/null - Auto_Cron create 2>/dev/null - else - Auto_Cron delete 2>/dev/null - fi + Auto_Cron delete 2>/dev/null fi - if ! grep -q "AUTOBW_AVERAGE_CALC" "$SCRIPT_CONF"; then + if ! grep -q "^AUTOBW_AVERAGE_CALC=" "$SCRIPT_CONF"; then echo "AUTOBW_AVERAGE_CALC=10" >> "$SCRIPT_CONF" fi if grep -q "OUTPUTDATAMODE" "$SCRIPT_CONF"; then sed -i '/OUTPUTDATAMODE/d;' "$SCRIPT_CONF" fi - if ! grep -q "DAYSTOKEEP" "$SCRIPT_CONF"; then + if ! grep -q "^OUTPUTTIMEMODE=" "$SCRIPT_CONF"; then + echo "OUTPUTTIMEMODE=unix" >> "$SCRIPT_CONF" + fi + if ! grep -q "^DAYSTOKEEP=" "$SCRIPT_CONF"; then echo "DAYSTOKEEP=30" >> "$SCRIPT_CONF" fi - if ! grep -q "LASTXRESULTS" "$SCRIPT_CONF"; then + if ! grep -q "^LASTXRESULTS=" "$SCRIPT_CONF"; then echo "LASTXRESULTS=10" >> "$SCRIPT_CONF" fi - if ! grep -q "PREFERREDSERVER_WGVPN" "$SCRIPT_CONF"; then - for index in 1 2 3 4 5; do - { echo "PREFERREDSERVER_WGVPN$index=0|None configured"; echo "USEPREFERRED_WGVPN$index=false"; } >> "$SCRIPT_CONF" - done + if ! grep -q "^STORAGELOCATION=" "$SCRIPT_CONF"; then + echo "STORAGELOCATION=jffs" >> "$SCRIPT_CONF" fi - if ! grep -q "SPEEDTESTBINARY" "$SCRIPT_CONF"; then - if [ -f /usr/sbin/ookla ]; then - echo "SPEEDTESTBINARY=builtin" >> "$SCRIPT_CONF" - else - echo "SPEEDTESTBINARY=external" >> "$SCRIPT_CONF" - fi + if ! grep -q "^JFFS_MSGLOGTIME=" "$SCRIPT_CONF"; then + echo "JFFS_MSGLOGTIME=0" >> "$SCRIPT_CONF" + fi + if ! grep -q "^EXCLUDEFROMQOS=" "$SCRIPT_CONF"; then + echo "EXCLUDEFROMQOS=true" >> "$SCRIPT_CONF" + fi + if ! grep -q "^SPEEDTESTBINARY=" "$SCRIPT_CONF" + then + echo "SPEEDTESTBINARY=$(_GetDefaultSpeedTestBinary_)" >> "$SCRIPT_CONF" + fi + if ! grep -q "^USEPREFERRED_WAN=" "$SCRIPT_CONF"; then + echo "USEPREFERRED_WAN=false" >> "$SCRIPT_CONF" + fi + if ! grep -q "^PREFERREDSERVER_WAN=" "$SCRIPT_CONF"; then + echo "PREFERREDSERVER_WAN=0|None configured" >> "$SCRIPT_CONF" + fi + if ! grep -q "^PREFERREDSERVER_WGVPN" "$SCRIPT_CONF" + then + for index in 1 2 3 4 5 + do + { + echo "USEPREFERRED_WGVPN$index=false"; + echo "PREFERREDSERVER_WGVPN$index=0|None configured" + } >> "$SCRIPT_CONF" + done fi return 0 else - { echo "PREFERREDSERVER_WAN=0|None configured"; echo "USEPREFERRED_WAN=false"; echo "AUTOMATED=true" ; echo "OUTPUTTIMEMODE=unix"; echo "STORAGELOCATION=jffs"; } >> "$SCRIPT_CONF" - for index in 1 2 3 4 5; do - { echo "PREFERREDSERVER_VPNC$index=0|None configured"; echo "USEPREFERRED_VPNC$index=false"; } >> "$SCRIPT_CONF" + { + echo "PREFERREDSERVER_WAN=0|None configured" + echo "USEPREFERRED_WAN=false"; echo "AUTOMATICMODE=true" + echo "OUTPUTTIMEMODE=unix"; echo "STORAGELOCATION=jffs" + echo "SCHDAYS=*" ; echo "SCHHOURS=*" ; echo "SCHMINS=12,42" + echo "DAYSTOKEEP=30" ; echo "LASTXRESULTS=10" ; echo "EXCLUDEFROMQOS=true" + echo "JFFS_MSGLOGTIME=0" + } > "$SCRIPT_CONF" + for index in 1 2 3 4 5 + do + { + echo "USEPREFERRED_VPNC${index}=false" + echo "PREFERREDSERVER_VPNC${index}=0|None configured" + } >> "$SCRIPT_CONF" done - for index in 1 2 3 4 5; do - { echo "PREFERREDSERVER_WGVPN$index=0|None configured"; echo "USEPREFERRED_WGVPN$index=false"; } >> "$SCRIPT_CONF" + for index in 1 2 3 4 5 + do + { + echo "USEPREFERRED_WGVPN$index=false" + echo "PREFERREDSERVER_WGVPN$index=0|None configured"; + } >> "$SCRIPT_CONF" done - { echo "AUTOBW_ENABLED=false"; echo "AUTOBW_SF_DOWN=95"; echo "AUTOBW_SF_UP=95"; echo "AUTOBW_ULIMIT_DOWN=0"; echo "AUTOBW_LLIMIT_DOWN=0"; echo "AUTOBW_ULIMIT_UP=0"; echo "AUTOBW_LLIMIT_UP=0"; echo "AUTOBW_THRESHOLD_UP=10"; echo "AUTOBW_THRESHOLD_DOWN=10"; echo "AUTOBW_AVERAGE_CALC=10"; echo "STORERESULTURL=true"; echo "EXCLUDEFROMQOS=true"; echo "SCHDAYS=*"; echo "SCHHOURS=*"; echo "SCHMINS=12,42"; echo "DAYSTOKEEP=30"; echo "LASTXRESULTS=10";} >> "$SCRIPT_CONF" - if [ -f /usr/sbin/ookla ]; then - echo "SPEEDTESTBINARY=builtin" >> "$SCRIPT_CONF" - else - echo "SPEEDTESTBINARY=external" >> "$SCRIPT_CONF" - fi + { + echo "AUTOBW_ENABLED=false" + echo "AUTOBW_SF_DOWN=95" ; echo "AUTOBW_SF_UP=95" + echo "AUTOBW_ULIMIT_DOWN=0" ; echo "AUTOBW_LLIMIT_DOWN=0" + echo "AUTOBW_ULIMIT_UP=0" ; echo "AUTOBW_LLIMIT_UP=0" + echo "AUTOBW_THRESHOLD_UP=10" ; echo "AUTOBW_THRESHOLD_DOWN=10" + echo "AUTOBW_AVERAGE_CALC=10" ; echo "STORERESULTURL=true" + } >> "$SCRIPT_CONF" + echo "SPEEDTESTBINARY=$(_GetDefaultSpeedTestBinary_)" >> "$SCRIPT_CONF" return 1 fi } -Auto_ServiceEvent(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +Auto_ServiceEvent() +{ + local theScriptFilePath="/jffs/scripts/$SCRIPT_NAME_LOWER" case $1 in create) - if [ -f /jffs/scripts/service-event ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event) - STARTUPLINECOUNTEX=$(grep -cx 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" /jffs/scripts/service-event) - - if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ]; }; then + if [ -f /jffs/scripts/service-event ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event)" + STARTUPLINECOUNTEX="$(grep -cx 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" /jffs/scripts/service-event)" + + if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ] ; } + then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/service-event fi - - if [ "$STARTUPLINECOUNTEX" -eq 0 ]; then - echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" >> /jffs/scripts/service-event + if [ "$STARTUPLINECOUNTEX" -eq 0 ] + then + { + echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" + } >> /jffs/scripts/service-event fi else - echo "#!/bin/sh" > /jffs/scripts/service-event - echo "" >> /jffs/scripts/service-event - echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { /jffs/scripts/'"$SCRIPT_NAME_LOWER"' service_event "$@" & }; fi # '"$SCRIPT_NAME" >> /jffs/scripts/service-event + { + echo "#!/bin/sh" ; echo + echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" + echo + } > /jffs/scripts/service-event chmod 0755 /jffs/scripts/service-event fi ;; delete) - if [ -f /jffs/scripts/service-event ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event) - + if [ -f /jffs/scripts/service-event ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/service-event)" if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/service-event fi @@ -657,47 +909,57 @@ Auto_ServiceEvent(){ esac } -Auto_Startup(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +Auto_Startup() +{ + local theScriptFilePath="/jffs/scripts/$SCRIPT_NAME_LOWER" case $1 in create) - if [ -f /jffs/scripts/services-start ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start) - + if [ -f /jffs/scripts/services-start ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start)" if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/services-start fi fi - - if [ -f /jffs/scripts/post-mount ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount) - STARTUPLINECOUNTEX=$(grep -cx "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" /jffs/scripts/post-mount) - - if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ]; }; then + if [ -f /jffs/scripts/post-mount ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount)" + STARTUPLINECOUNTEX="$(grep -cx '\[ -x "${1}/entware/bin/opkg" \] && \[ -x '"$theScriptFilePath"' \] && '"$theScriptFilePath"' startup "$@" & # '"$SCRIPT_NAME" /jffs/scripts/post-mount)" + + if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ] ; } + then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/post-mount + STARTUPLINECOUNT=0 fi - - if [ "$STARTUPLINECOUNTEX" -eq 0 ]; then - echo "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" >> /jffs/scripts/post-mount + if [ "$STARTUPLINECOUNTEX" -eq 0 ] + then + { + echo '[ -x "${1}/entware/bin/opkg" ] && [ -x '"$theScriptFilePath"' ] && '"$theScriptFilePath"' startup "$@" & # '"$SCRIPT_NAME" + } >> /jffs/scripts/post-mount fi else - echo "#!/bin/sh" > /jffs/scripts/post-mount - echo "" >> /jffs/scripts/post-mount - echo "/jffs/scripts/$SCRIPT_NAME_LOWER startup"' "$@" & # '"$SCRIPT_NAME" >> /jffs/scripts/post-mount + { + echo "#!/bin/sh" ; echo + echo '[ -x "${1}/entware/bin/opkg" ] && [ -x '"$theScriptFilePath"' ] && '"$theScriptFilePath"' startup "$@" & # '"$SCRIPT_NAME" + echo + } > /jffs/scripts/post-mount chmod 0755 /jffs/scripts/post-mount fi ;; delete) - if [ -f /jffs/scripts/services-start ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start) - + if [ -f /jffs/scripts/services-start ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/services-start)" if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/services-start fi fi - - if [ -f /jffs/scripts/post-mount ]; then - STARTUPLINECOUNT=$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount) - + if [ -f /jffs/scripts/post-mount ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/post-mount)" if [ "$STARTUPLINECOUNT" -gt 0 ]; then sed -i -e '/# '"$SCRIPT_NAME"'/d' /jffs/scripts/post-mount fi @@ -706,29 +968,66 @@ Auto_Startup(){ esac } -Auto_Cron(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +Auto_Cron() +{ + local theScriptFilePath="/jffs/scripts/$SCRIPT_NAME_LOWER" case $1 in create) - STARTUPLINECOUNT=$(cru l | grep -c "$SCRIPT_NAME") - - if [ "$STARTUPLINECOUNT" -eq 0 ]; then - CRU_DAYNUMBERS="$(grep "SCHDAYS" "$SCRIPT_CONF" | cut -f2 -d"=" | sed 's/Sun/0/;s/Mon/1/;s/Tues/2/;s/Wed/3/;s/Thurs/4/;s/Fri/5/;s/Sat/6/;')" - CRU_HOURS="$(grep "SCHHOURS" "$SCRIPT_CONF" | cut -f2 -d"=")" - CRU_MINUTES="$(grep "SCHMINS" "$SCRIPT_CONF" | cut -f2 -d"=")" - cru a "$SCRIPT_NAME" "$CRU_MINUTES $CRU_HOURS * * $CRU_DAYNUMBERS /jffs/scripts/$SCRIPT_NAME_LOWER generate" + STARTUPLINECOUNT="$(cru l | grep -c "#${SCRIPT_NAME}#")" + if [ "$STARTUPLINECOUNT" -gt 0 ]; then + cru d "${SCRIPT_NAME}" + fi + STARTUPLINECOUNTGEN="$(cru l | grep -c "${SCRIPT_NAME}_generate")" + CRU_SCHHOUR="$(grep "^SCHHOURS=" "$SCRIPT_CONF" | cut -f2 -d"=")" + CRU_SCHMINS="$(grep "^SCHMINS=" "$SCRIPT_CONF" | cut -f2 -d"=")" + STARTUPLINECOUNTEXGEN="$(cru l | grep "${SCRIPT_NAME}_generate" | grep -c "^$CRU_SCHMINS $CRU_SCHHOUR [*] [*]")" + if [ "$STARTUPLINECOUNTGEN" -gt 0 ] && [ "$STARTUPLINECOUNTEXGEN" -eq 0 ] + then + cru d "${SCRIPT_NAME}_generate" + STARTUPLINECOUNTGEN="$(cru l | grep -c "${SCRIPT_NAME}_generate")" + fi + if [ "$STARTUPLINECOUNTGEN" -eq 0 ] + then + CRU_SCHDAYS="$(grep "^SCHDAYS=" "$SCRIPT_CONF" | cut -f2 -d"=" | sed 's/Sun/0/;s/Mon/1/;s/Tues/2/;s/Wed/3/;s/Thurs/4/;s/Fri/5/;s/Sat/6/;')" + cru a "${SCRIPT_NAME}_generate" "$CRU_SCHMINS $CRU_SCHHOUR * * $CRU_SCHDAYS $theScriptFilePath generate" + fi + + STARTUPLINECOUNTTRIM="$(cru l | grep -c "${SCRIPT_NAME}_trimDB")" + STARTUPLINECOUNTEXTRIM="$(cru l | grep "${SCRIPT_NAME}_trimDB" | grep -c "^$defTrimDB_Mins $defTrimDB_Hour [*] [*]")" + if [ "$STARTUPLINECOUNTTRIM" -gt 0 ] && [ "$STARTUPLINECOUNTEXTRIM" -eq 0 ] + then + cru d "${SCRIPT_NAME}_trimDB" + STARTUPLINECOUNTTRIM="$(cru l | grep -c "${SCRIPT_NAME}_trimDB")" + fi + if [ "$STARTUPLINECOUNTTRIM" -eq 0 ]; then + cru a "${SCRIPT_NAME}_trimDB" "$defTrimDB_Mins $defTrimDB_Hour * * * $theScriptFilePath trimdb" fi ;; delete) - STARTUPLINECOUNT=$(cru l | grep -c "$SCRIPT_NAME") - + STARTUPLINECOUNT="$(cru l | grep -c "#${SCRIPT_NAME}#")" if [ "$STARTUPLINECOUNT" -gt 0 ]; then cru d "$SCRIPT_NAME" fi + STARTUPLINECOUNTGEN="$(cru l | grep -c "#${SCRIPT_NAME}_generate#")" + if [ "$STARTUPLINECOUNTGEN" -gt 0 ]; then + cru d "${SCRIPT_NAME}_generate" + fi + STARTUPLINECOUNTTRIM="$(cru l | grep -c "#${SCRIPT_NAME}_trimDB#")" + if [ "$STARTUPLINECOUNTTRIM" -gt 0 ]; then + cru d "${SCRIPT_NAME}_trimDB" + fi ;; esac } -Get_Interface_From_Name(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-02] ## +##----------------------------------------## +Get_Interface_From_Name() +{ IFACE="" case "$1" in WAN) @@ -740,47 +1039,35 @@ Get_Interface_From_Name(){ IFACE="$(nvram get wan0_ifname)" fi ;; - VPNC1) - IFACE="tun11" - ;; - VPNC2) - IFACE="tun12" - ;; - VPNC3) - IFACE="tun13" - ;; - VPNC4) - IFACE="tun14" - ;; - VPNC5) - IFACE="tun15" - ;; - WGVPN1) - IFACE="wgc1" - ;; - WGVPN2) - IFACE="wgc2" - ;; - WGVPN3) - IFACE="wgc3" - ;; - WGVPN4) - IFACE="wgc4" - ;; - WGVPN5) - IFACE="wgc5" - ;; + VPNC1) IFACE="tun11" ;; + VPNC2) IFACE="tun12" ;; + VPNC3) IFACE="tun13" ;; + VPNC4) IFACE="tun14" ;; + VPNC5) IFACE="tun15" ;; + WGVPN1) IFACE="wgc1" ;; + WGVPN2) IFACE="wgc2" ;; + WGVPN3) IFACE="wgc3" ;; + WGVPN4) IFACE="wgc4" ;; + WGVPN5) IFACE="wgc5" ;; esac - + echo "$IFACE" } -Set_Interface_State(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-02] ## +##----------------------------------------## +Set_Interface_State() +{ interfaceline="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - if echo "$interfaceline" | grep -q "VPN" ; then - if echo "$interfaceline" | grep -q "#excluded" ; then + if echo "$interfaceline" | grep -qE "^(VPN|WGVPN)" + then + if echo "$interfaceline" | grep -q "#excluded" + then IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then sed -i "$1"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else sed -i "$1"'s/ #excluded - interface not up#/ #excluded#/' "$SCRIPT_INTERFACES_USER" @@ -789,252 +1076,385 @@ Set_Interface_State(){ fi } -Generate_Interface_List(){ - ScriptHeader - goback="false" - printf "Retrieving list of interfaces...\\n\\n" - interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" - COUNTER=1 - until [ $COUNTER -gt "$interfacecount" ]; do - Set_Interface_State "$COUNTER" - interfaceline="$(sed "$COUNTER!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - printf "%s) %s\\n" "$COUNTER" "$interfaceline" - COUNTER=$((COUNTER + 1)) - done - - printf "\\ne) Go back\\n" - - while true; do - printf "\\n${BOLD}Please select a chart to toggle inclusion in %s (1-%s):${CLEARFORMAT} " "$SCRIPT_NAME" "$interfacecount" - read -r interface - - if [ "$interface" = "e" ]; then - goback="true" - break - elif ! Validate_Number "$interface"; then - printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$interfacecount" - else - if [ "$interface" -lt 1 ] || [ "$interface" -gt "$interfacecount" ]; then - printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$interfacecount" +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-03] ## +##----------------------------------------## +Generate_Interface_List() +{ + local ifaceCount ifaceEntryNum interfaceLine + printf "\nRetrieving list of interfaces...\n\n" + + _GenerateIFaceList_() + { + ifaceCount="$(wc -l < "$SCRIPT_INTERFACES_USER")" + COUNTER=1 + until [ "$COUNTER" -gt "$ifaceCount" ] + do + Set_Interface_State "$COUNTER" + ifaceLine="$(sed "$COUNTER!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + printf "%2d) %s\n" "$COUNTER" "$ifaceLine" + COUNTER="$((COUNTER + 1))" + done + printf " e) Exit to Main Menu\n" + } + + while true + do + ScriptHeader + _GenerateIFaceList_ + printf "\n${BOLD}Select an interface to toggle inclusion in %s [1-%s]:${CLEARFORMAT} " "$SCRIPT_NAME" "$ifaceCount" + read -r ifaceEntryNum + + if [ "$ifaceEntryNum" = "e" ] + then + break + elif ! Validate_Number "$ifaceEntryNum" + then + printf "\n${ERR}Please enter a valid number [1-${ifaceCount}].${CLEARFORMAT}\n" + PressEnter + elif [ "$ifaceEntryNum" -lt 1 ] || [ "$ifaceEntryNum" -gt "$ifaceCount" ] + then + printf "\n${ERR}Please enter a number between 1 and ${ifaceCount}.${CLEARFORMAT}\n" + PressEnter else - interfaceline="$(sed "$interface!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - if echo "$interfaceline" | grep -q "#excluded" ; then - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then - sed -i "$interface"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" + interfaceLine="$(sed "$ifaceEntryNum!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" + + if echo "$interfaceLine" | grep -q "#excluded" + then + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then + sed -i "$ifaceEntryNum"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else - sed -i "$interface"'s/ #excluded - interface not up#//' "$SCRIPT_INTERFACES_USER" - sed -i "$interface"'s/ #excluded#//' "$SCRIPT_INTERFACES_USER" + sed -i "$ifaceEntryNum"'s/ #excluded - interface not up#//' "$SCRIPT_INTERFACES_USER" + sed -i "$ifaceEntryNum"'s/ #excluded#//' "$SCRIPT_INTERFACES_USER" fi else - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then - sed -i "$interface"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then + sed -i "$ifaceEntryNum"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" else - sed -i "$interface"'s/$/ #excluded#/' "$SCRIPT_INTERFACES_USER" + sed -i "$ifaceEntryNum"'s/$/ #excluded#/' "$SCRIPT_INTERFACES_USER" fi fi - sed -i 's/ *$//' "$SCRIPT_INTERFACES_USER" - printf "\\n" - break + printf "\n" fi - fi done - - if [ "$goback" != "true" ]; then - Generate_Interface_List - fi } -Download_File(){ - /usr/sbin/curl -fsL --retry 3 "$1" -o "$2" +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Download_File() +{ /usr/sbin/curl -LSs --retry 4 --retry-delay 5 --retry-connrefused "$1" -o "$2" ; } + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_Check_WebGUI_Page_Exists_() +{ + local webPageStr webPageFile theWebPage + + if [ ! -f "$TEMP_MENU_TREE" ] + then echo "NONE" ; return 1 ; fi + + theWebPage="NONE" + webPageStr="$(grep -E -m1 "^$webPageLineRegExp" "$TEMP_MENU_TREE")" + if [ -n "$webPageStr" ] + then + webPageFile="$(echo "$webPageStr" | grep -owE "$webPageFileRegExp" | head -n1)" + if [ -n "$webPageFile" ] && [ -s "${SCRIPT_WEBPAGE_DIR}/$webPageFile" ] + then theWebPage="$webPageFile" ; fi + fi + echo "$theWebPage" } -Get_WebUI_Page(){ - MyPage="none" - for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do - page="/www/user/user$i.asp" - if [ -f "$page" ] && [ "$(md5sum < "$1")" = "$(md5sum < "$page")" ]; then - MyPage="user$i.asp" - return - elif [ "$MyPage" = "none" ] && [ ! -f "$page" ]; then - MyPage="user$i.asp" +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Get_WebUI_Page() +{ + local webPageFile webPagePath + + MyWebPage="$(_Check_WebGUI_Page_Exists_)" + + for indx in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + do + webPageFile="user${indx}.asp" + webPagePath="${SCRIPT_WEBPAGE_DIR}/$webPageFile" + + if [ -s "$webPagePath" ] && \ + [ "$(md5sum < "$1")" = "$(md5sum < "$webPagePath")" ] + then + MyWebPage="$webPageFile" + break + elif [ "$MyWebPage" = "NONE" ] && [ ! -s "$webPagePath" ] + then + MyWebPage="$webPageFile" fi done } ### function based on @dave14305's FlexQoS webconfigpage function ### -Get_WebUI_URL(){ - urlpage="" - urlproto="" - urldomain="" - urlport="" - - urlpage="$(sed -nE "/$SCRIPT_NAME/ s/.*url\: \"(user[0-9]+\.asp)\".*/\1/p" /tmp/menuTree.js)" +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Get_WebUI_URL() +{ + local urlPage urlProto urlDomain urlPort lanPort + + if [ ! -f "$TEMP_MENU_TREE" ] + then + echo "**ERROR**: WebUI page NOT mounted" + return 1 + fi + + urlPage="$(sed -nE "/$SCRIPT_NAME/ s/.*url\: \"(user[0-9]+\.asp)\".*/\1/p" "$TEMP_MENU_TREE")" + if [ "$(nvram get http_enable)" -eq 1 ]; then - urlproto="https" + urlProto="https" else - urlproto="http" + urlProto="http" fi if [ -n "$(nvram get lan_domain)" ]; then - urldomain="$(nvram get lan_hostname).$(nvram get lan_domain)" + urlDomain="$(nvram get lan_hostname).$(nvram get lan_domain)" else - urldomain="$(nvram get lan_ipaddr)" + urlDomain="$(nvram get lan_ipaddr)" fi - if [ "$(nvram get ${urlproto}_lanport)" -eq 80 ] || [ "$(nvram get ${urlproto}_lanport)" -eq 443 ]; then - urlport="" + + lanPort="$(nvram get ${urlProto}_lanport)" + if [ "$lanPort" -eq 80 ] || [ "$lanPort" -eq 443 ] + then + urlPort="" else - urlport=":$(nvram get ${urlproto}_lanport)" + urlPort=":$lanPort" fi - - if echo "$urlpage" | grep -qE "user[0-9]+\.asp"; then - echo "${urlproto}://${urldomain}${urlport}/${urlpage}" | tr "A-Z" "a-z" + + if echo "$urlPage" | grep -qE "^${webPageFileRegExp}$" && \ + [ -s "${SCRIPT_WEBPAGE_DIR}/$urlPage" ] + then + echo "${urlProto}://${urlDomain}${urlPort}/${urlPage}" | tr "A-Z" "a-z" else - echo "WebUI page not found" + echo "**ERROR**: WebUI page NOT found" fi } -### ### + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_CreateMenuAddOnsSection_() +{ + if grep -qE "^${webPageMenuAddons}$" "$TEMP_MENU_TREE" && \ + grep -qE "${webPageHelpSupprt}$" "$TEMP_MENU_TREE" + then return 0 ; fi + + lineinsBefore="$(($(grep -n "^exclude:" "$TEMP_MENU_TREE" | cut -f1 -d':') - 1))" + + sed -i "$lineinsBefore""i\ +${BEGIN_MenuAddOnsTag}\n\ +,\n{\n\ +${webPageMenuAddons}\n\ +index: \"menu_Addons\",\n\ +tab: [\n\ +{url: \"javascript:var helpwindow=window.open('\/ext\/shared-jy\/redirect.htm')\", ${webPageHelpSupprt}\n\ +{url: \"NULL\", tabName: \"__INHERIT__\"}\n\ +]\n}\n\ +${ENDIN_MenuAddOnsTag}" "$TEMP_MENU_TREE" +} ### locking mechanism code credit to Martineau (@MartineauUK) ### -Mount_WebUI(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Mount_WebUI() +{ Print_Output true "Mounting WebUI tab for $SCRIPT_NAME" "$PASS" LOCKFILE=/tmp/addonwebui.lock FD=386 eval exec "$FD>$LOCKFILE" flock -x "$FD" Get_WebUI_Page "$SCRIPT_DIR/spdstats_www.asp" - if [ "$MyPage" = "none" ]; then - Print_Output true "Unable to mount $SCRIPT_NAME WebUI page, exiting" "$CRIT" + if [ "$MyWebPage" = "NONE" ] + then + Print_Output true "**ERROR** Unable to mount $SCRIPT_NAME WebUI page, exiting" "$CRIT" flock -u "$FD" return 1 fi - cp -f "$SCRIPT_DIR/spdstats_www.asp" "$SCRIPT_WEBPAGE_DIR/$MyPage" - echo "$SCRIPT_NAME" > "$SCRIPT_WEBPAGE_DIR/$(echo $MyPage | cut -f1 -d'.').title" - - if [ "$(uname -o)" = "ASUSWRT-Merlin" ]; then + cp -fp "$SCRIPT_DIR/spdstats_www.asp" "$SCRIPT_WEBPAGE_DIR/$MyWebPage" + echo "$SCRIPT_NAME" > "$SCRIPT_WEBPAGE_DIR/$(echo "$MyWebPage" | cut -f1 -d'.').title" + + if [ "$(/bin/uname -o)" = "ASUSWRT-Merlin" ] + then if [ ! -f /tmp/index_style.css ]; then - cp -f /www/index_style.css /tmp/ + cp -fp /www/index_style.css /tmp/ fi - - if ! grep -q '.menu_Addons' /tmp/index_style.css ; then + + if ! grep -q '.menu_Addons' /tmp/index_style.css + then echo ".menu_Addons { background: url(ext/shared-jy/addons.png); }" >> /tmp/index_style.css fi - + umount /www/index_style.css 2>/dev/null mount -o bind /tmp/index_style.css /www/index_style.css - - if [ ! -f /tmp/menuTree.js ]; then - cp -f /www/require/modules/menuTree.js /tmp/ - fi - - sed -i "\\~$MyPage~d" /tmp/menuTree.js - - if ! grep -q 'menuName: "Addons"' /tmp/menuTree.js ; then - lineinsbefore="$(( $(grep -n "exclude:" /tmp/menuTree.js | cut -f1 -d':') - 1))" - sed -i "$lineinsbefore"'i,\n{\nmenuName: "Addons",\nindex: "menu_Addons",\ntab: [\n{url: "javascript:var helpwindow=window.open('"'"'/ext/shared-jy/redirect.htm'"'"')", tabName: "Help & Support"},\n{url: "NULL", tabName: "__INHERIT__"}\n]\n}' /tmp/menuTree.js + + if [ ! -f "$TEMP_MENU_TREE" ]; then + cp -fp /www/require/modules/menuTree.js "$TEMP_MENU_TREE" fi - - sed -i "/url: \"javascript:var helpwindow=window.open('\/ext\/shared-jy\/redirect.htm'/i {url: \"$MyPage\", tabName: \"$SCRIPT_NAME\"}," /tmp/menuTree.js - + sed -i "\\~$MyWebPage~d" "$TEMP_MENU_TREE" + + _CreateMenuAddOnsSection_ + + sed -i "/url: \"javascript:var helpwindow=window.open('\/ext\/shared-jy\/redirect.htm'/i {url: \"$MyWebPage\", tabName: \"$SCRIPT_NAME\"}," "$TEMP_MENU_TREE" + umount /www/require/modules/menuTree.js 2>/dev/null - mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js + mount -o bind "$TEMP_MENU_TREE" /www/require/modules/menuTree.js fi flock -u "$FD" - Print_Output true "Mounted $SCRIPT_NAME WebUI page as $MyPage" "$PASS" + Print_Output true "Mounted $SCRIPT_NAME WebUI page as $MyWebPage" "$PASS" } -AutomaticMode(){ +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_CheckFor_WebGUI_Page_() +{ + if [ "$(_Check_WebGUI_Page_Exists_)" = "NONE" ] + then Mount_WebUI ; fi +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +AutomaticMode() +{ case "$1" in enable) - sed -i 's/^AUTOMATED=.*$/AUTOMATED=true/' "$SCRIPT_CONF" + if AutomaticMode check + then + printf "\nAutomatic speedtests are already ${GRNct}ENABLED${CLRct}.\n\n" + return 0 + fi + sed -i 's/^AUTOMATICMODE=.*$/AUTOMATICMODE=true/' "$SCRIPT_CONF" Auto_Cron create 2>/dev/null + printf "Automatic speedtests are now ${GRNct}ENABLED${CLRct}.\n\n" ;; disable) - sed -i 's/^AUTOMATED=.*$/AUTOMATED=false/' "$SCRIPT_CONF" + if ! AutomaticMode check + then + printf "\nAutomatic speedtests are already ${REDct}DISABLED${CLRct}.\n\n" + return 0 + fi + sed -i 's/^AUTOMATICMODE=.*$/AUTOMATICMODE=false/' "$SCRIPT_CONF" Auto_Cron delete 2>/dev/null + printf "Automatic speedtests are now ${REDct}DISABLED${CLRct}.\n\n" ;; check) - AUTOMATED=$(grep "AUTOMATED" "$SCRIPT_CONF" | cut -f2 -d"=") - if [ "$AUTOMATED" = "true" ]; then return 0; else return 1; fi + AUTOMATICMODE="$(grep "^AUTOMATICMODE=" "$SCRIPT_CONF" | cut -f2 -d'=')" + if [ "${AUTOMATICMODE:=true}" = "true" ] + then return 0; else return 1; fi ;; esac } -TestSchedule(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +CronTestSchedule() +{ case "$1" in update) sed -i 's/^SCHDAYS=.*$/SCHDAYS='"$(echo "$2" | sed 's/0/Sun/;s/1/Mon/;s/2/Tues/;s/3/Wed/;s/4/Thurs/;s/5/Fri/;s/6/Sat/;')"'/' "$SCRIPT_CONF" sed -i 's~^SCHHOURS=.*$~SCHHOURS='"$3"'~' "$SCRIPT_CONF" sed -i 's~^SCHMINS=.*$~SCHMINS='"$4"'~' "$SCRIPT_CONF" Auto_Cron delete 2>/dev/null - Auto_Cron create 2>/dev/null + AutomaticMode check && Auto_Cron create 2>/dev/null ;; check) - SCHDAYS=$(grep "SCHDAYS" "$SCRIPT_CONF" | cut -f2 -d"=") - SCHHOURS=$(grep "SCHHOURS" "$SCRIPT_CONF" | cut -f2 -d"=") - SCHMINS=$(grep "SCHMINS" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$SCHDAYS|$SCHHOURS|$SCHMINS" + SCHDAYS="$(grep "^SCHDAYS=" "$SCRIPT_CONF" | cut -f2 -d'=')" + SCHHOURS="$(grep "^SCHHOURS=" "$SCRIPT_CONF" | cut -f2 -d'=')" + SCHMINS="$(grep "^SCHMINS=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${SCHDAYS:=*}|${SCHHOURS:=*}|${SCHMINS:=12,42}" ;; esac } ##----------------------------------------## -## Modified by Martinski W. [2024-Jul-15] ## +## Modified by Martinski W. [2025-Jan-04] ## ##----------------------------------------## ScriptStorageLocation() { case "$1" in usb) + printf "Please wait..." sed -i 's/^STORAGELOCATION=.*$/STORAGELOCATION=usb/' "$SCRIPT_CONF" mkdir -p "/opt/share/$SCRIPT_NAME_LOWER.d/" - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/csv" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/.databaseupgraded" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/config" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/config.bak" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/jffs/addons/$SCRIPT_NAME_LOWER.d"/lastx_*.csv "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - SCRIPT_CONF="/opt/share/$SCRIPT_NAME_LOWER.d/config" - ScriptStorageLocation load - ;; + rm -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db-shm" + rm -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db-wal" + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/csv" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/.interfaces_user.bak" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/.databaseupgraded" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/config" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/config.bak" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d"/spdstats.db* "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/jffs/addons/$SCRIPT_NAME_LOWER.d"/lastx_*.csv "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + SCRIPT_CONF="/opt/share/${SCRIPT_NAME_LOWER}.d/config" + SPEEDSTATS_DB="/opt/share/${SCRIPT_NAME_LOWER}.d/spdstats.db" + CSV_OUTPUT_DIR="/opt/share/${SCRIPT_NAME_LOWER}.d/csv" + ScriptStorageLocation load true + sleep 2 + ;; jffs) + printf "Please wait..." sed -i 's/^STORAGELOCATION=.*$/STORAGELOCATION=jffs/' "$SCRIPT_CONF" mkdir -p "/jffs/addons/$SCRIPT_NAME_LOWER.d/" - mv "/opt/share/$SCRIPT_NAME_LOWER.d/csv" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/.databaseupgraded" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/config" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/config.bak" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d/spdstats.db" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - mv "/opt/share/$SCRIPT_NAME_LOWER.d"/lastx_*.csv "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null - SCRIPT_CONF="/jffs/addons/$SCRIPT_NAME_LOWER.d/config" - ScriptStorageLocation load - ;; + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/csv" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces_user" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/.interfaces_user.bak" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/.databaseupgraded" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/config" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/config.bak" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d/spdtitletext.js" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d"/spdstats.db* "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + mv -f "/opt/share/$SCRIPT_NAME_LOWER.d"/lastx_*.csv "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null + SCRIPT_CONF="/jffs/addons/${SCRIPT_NAME_LOWER}.d/config" + SPEEDSTATS_DB="/jffs/addons/${SCRIPT_NAME_LOWER}.d/spdstats.db" + CSV_OUTPUT_DIR="/jffs/addons/${SCRIPT_NAME_LOWER}.d/csv" + ScriptStorageLocation load true + sleep 2 + ;; check) - STORAGELOCATION=$(grep "STORAGELOCATION" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$STORAGELOCATION" - ;; + STORAGELOCATION="$(grep "^STORAGELOCATION=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${STORAGELOCATION:=jffs}" + ;; load) - STORAGELOCATION=$(grep "STORAGELOCATION" "$SCRIPT_CONF" | cut -f2 -d"=") - if [ "$STORAGELOCATION" = "usb" ]; then - SCRIPT_STORAGE_DIR="/opt/share/$SCRIPT_NAME_LOWER.d" - elif [ "$STORAGELOCATION" = "jffs" ]; then - SCRIPT_STORAGE_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" + STORAGELOCATION="$(ScriptStorageLocation check)" + if [ "$STORAGELOCATION" = "usb" ] + then + SCRIPT_STORAGE_DIR="/opt/share/${SCRIPT_NAME_LOWER}.d" + elif [ "$STORAGELOCATION" = "jffs" ] + then + SCRIPT_STORAGE_DIR="/jffs/addons/${SCRIPT_NAME_LOWER}.d" fi - + chmod 777 "$SCRIPT_STORAGE_DIR" + SPEEDSTATS_DB="$SCRIPT_STORAGE_DIR/spdstats.db" + CSV_OUTPUT_DIR="$SCRIPT_STORAGE_DIR/csv" SCRIPT_INTERFACES="$SCRIPT_STORAGE_DIR/.interfaces" SCRIPT_INTERFACES_USER="$SCRIPT_STORAGE_DIR/.interfaces_user" - CSV_OUTPUT_DIR="$SCRIPT_STORAGE_DIR/csv" - ;; + if [ $# -gt 1 ] && [ "$2" = "true" ] + then _UpdateJFFS_FreeSpaceInfo_ ; fi + ;; esac } -OutputTimeMode(){ +OutputTimeMode() +{ case "$1" in unix) sed -i 's/^OUTPUTTIMEMODE=.*$/OUTPUTTIMEMODE=unix/' "$SCRIPT_CONF" @@ -1045,13 +1465,17 @@ OutputTimeMode(){ Generate_CSVs ;; check) - OUTPUTTIMEMODE=$(grep "OUTPUTTIMEMODE" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$OUTPUTTIMEMODE" + OUTPUTTIMEMODE="$(grep "^OUTPUTTIMEMODE=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${OUTPUTTIMEMODE:=unix}" ;; esac } -SpeedtestBinary(){ +##----------------------------------------## +## Modified by Martinski W. [2024-Nov-15] ## +##----------------------------------------## +SpeedtestBinary() +{ case "$1" in builtin) sed -i 's/^SPEEDTESTBINARY=.*$/SPEEDTESTBINARY=builtin/' "$SCRIPT_CONF" @@ -1060,104 +1484,144 @@ SpeedtestBinary(){ sed -i 's/^SPEEDTESTBINARY=.*$/SPEEDTESTBINARY=external/' "$SCRIPT_CONF" ;; check) - SPEEDTESTBINARY=$(grep "SPEEDTESTBINARY" "$SCRIPT_CONF" | cut -f2 -d"=") + SPEEDTESTBINARY="$(grep "^SPEEDTESTBINARY=" "$SCRIPT_CONF" | cut -f2 -d'=')" + [ -z "$SPEEDTESTBINARY" ] && SPEEDTESTBINARY="$(_GetDefaultSpeedTestBinary_)" echo "$SPEEDTESTBINARY" ;; esac } -DaysToKeep(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +DaysToKeep() +{ + local MINvalue=15 MAXvalue=365 #Days# case "$1" in update) - daystokeep=30 - exitmenu="" - ScriptHeader - while true; do - printf "\\n${BOLD}Please enter the desired number of days\\nto keep data for (30-365 days):${CLEARFORMAT} " + daysToKeep="$(DaysToKeep check)" + exitLoop=false + while true + do + ScriptHeader + printf "${BOLD}Current number of days to keep data: ${GRNct}${daysToKeep}${CLRct}\n\n" + printf "${BOLD}Please enter the maximum number of days\nto keep the data for [${MINvalue}-${MAXvalue}] (e=Exit):${CLEARFORMAT} " read -r daystokeep_choice - - if [ "$daystokeep_choice" = "e" ]; then - exitmenu="exit" + if [ -z "$daystokeep_choice" ] && \ + echo "$daysToKeep" | grep -qE "^([1-9][0-9]{1,2})$" && \ + [ "$daysToKeep" -ge "$MINvalue" ] && [ "$daysToKeep" -le "$MAXvalue" ] + then + exitLoop=true + break + elif [ "$daystokeep_choice" = "e" ] + then + exitLoop=true break - elif ! Validate_Number "$daystokeep_choice"; then - printf "\\n${ERR}Please enter a valid number (30-365)${CLEARFORMAT}\\n" - elif [ "$daystokeep_choice" -lt 30 ] || [ "$daystokeep_choice" -gt 365 ]; then - printf "\\n${ERR}Please enter a number between 30 and 365${CLEARFORMAT}\\n" + elif ! Validate_Number "$daystokeep_choice" + then + printf "\n${ERR}Please enter a valid number [${MINvalue}-${MAXvalue}].${CLEARFORMAT}\n" + PressEnter + elif [ "$daystokeep_choice" -lt "$MINvalue" ] || [ "$daystokeep_choice" -gt "$MAXvalue" ] + then + printf "\n${ERR}Please enter a number between ${MINvalue} and ${MAXvalue}.${CLEARFORMAT}\n" + PressEnter else - daystokeep="$daystokeep_choice" - printf "\\n" + daysToKeep="$daystokeep_choice" break fi done - - if [ "$exitmenu" != "exit" ]; then - sed -i 's/^DAYSTOKEEP=.*$/DAYSTOKEEP='"$daystokeep"'/' "$SCRIPT_CONF" - return 0 + + if "$exitLoop" + then + echo ; return 1 else - printf "\\n" - return 1 + DAYSTOKEEP="$daysToKeep" + sed -i 's/^DAYSTOKEEP=.*$/DAYSTOKEEP='"$DAYSTOKEEP"'/' "$SCRIPT_CONF" + echo ; return 0 fi ;; check) - DAYSTOKEEP=$(grep "DAYSTOKEEP" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$DAYSTOKEEP" + DAYSTOKEEP="$(grep "^DAYSTOKEEP=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${DAYSTOKEEP:=30}" ;; esac } -LastXResults(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +LastXResults() +{ + local MINvalue=5 MAXvalue=100 #Results# case "$1" in update) - lastxresults=10 - exitmenu="" - ScriptHeader - while true; do - printf "\\n${BOLD}Please enter the desired number of results\\nto display in the WebUI (1-100):${CLEARFORMAT} " + lastXResults="$(LastXResults check)" + exitLoop=false + while true + do + ScriptHeader + printf "${BOLD}Current number of results to display: ${GRNct}${lastXResults}${CLRct}\n\n" + printf "${BOLD}Please enter the maximum number of results\nto display in the WebUI [${MINvalue}-${MAXvalue}] (e=Exit):${CLEARFORMAT} " read -r lastx_choice - - if [ "$lastx_choice" = "e" ]; then - exitmenu="exit" + if [ -z "$lastx_choice" ] && \ + echo "$lastXResults" | grep -qE "^([1-9][0-9]{0,2})$" && \ + [ "$lastXResults" -ge "$MINvalue" ] && [ "$lastXResults" -le "$MAXvalue" ] + then + exitLoop=true + break + elif [ "$lastx_choice" = "e" ] + then + exitLoop=true break - elif ! Validate_Number "$lastx_choice"; then - printf "\\n${ERR}Please enter a valid number (1-100)${CLEARFORMAT}\\n" - elif [ "$lastx_choice" -lt 1 ] || [ "$lastx_choice" -gt 100 ]; then - printf "\\n${ERR}Please enter a number between 1 and 100${CLEARFORMAT}\\n" + elif ! Validate_Number "$lastx_choice" + then + printf "\n${ERR}Please enter a valid number [${MINvalue}-${MAXvalue}].${CLEARFORMAT}\n" + PressEnter + elif [ "$lastx_choice" -lt "$MINvalue" ] || [ "$lastx_choice" -gt "$MAXvalue" ] + then + printf "\n${ERR}Please enter a number between ${MINvalue} and ${MAXvalue}.${CLEARFORMAT}\n" + PressEnter else - lastxresults="$lastx_choice" - printf "\\n" + lastXResults="$lastx_choice" break fi done - - if [ "$exitmenu" != "exit" ]; then - sed -i 's/^LASTXRESULTS=.*$/LASTXRESULTS='"$lastxresults"'/' "$SCRIPT_CONF" - + + if "$exitLoop" + then + echo ; return 1 + else + LASTXRESULTS="$lastXResults" + sed -i 's/^LASTXRESULTS=.*$/LASTXRESULTS='"$LASTXRESULTS"'/' "$SCRIPT_CONF" + IFACELIST="" - - while IFS='' read -r line || [ -n "$line" ]; do + while IFS='' read -r line || [ -n "$line" ] + do IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" done < "$SCRIPT_INTERFACES_USER" + IFACELIST="$(echo "$IFACELIST" | cut -c2-)" - - if [ "$IFACELIST" != "" ]; then - for IFACE_NAME in $IFACELIST; do - Generate_LastXResults "$IFACE_NAME" + if [ -n "$IFACELIST" ] + then + local glxIndx=0 + for IFACE_NAME in $IFACELIST + do + glxIndx="$((glxIndx + 1))" + Generate_LastXResults "$IFACE_NAME" "$glxIndx" done fi - return 0 - else - printf "\\n" - return 1 + echo ; return 0 fi ;; check) - LASTXRESULTS=$(grep "LASTXRESULTS" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$LASTXRESULTS" + LASTXRESULTS="$(grep "^LASTXRESULTS=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${LASTXRESULTS:=10}" ;; esac } -StoreResultURL(){ +StoreResultURL() +{ case "$1" in enable) sed -i 's/^STORERESULTURL=.*$/STORERESULTURL=true/' "$SCRIPT_CONF" @@ -1166,13 +1630,14 @@ StoreResultURL(){ sed -i 's/^STORERESULTURL=.*$/STORERESULTURL=false/' "$SCRIPT_CONF" ;; check) - STORERESULTURL=$(grep "STORERESULTURL" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$STORERESULTURL" + STORERESULTURL="$(grep "^STORERESULTURL=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${STORERESULTURL:=true}" ;; esac } -ExcludeFromQoS(){ +ExcludeFromQoS() +{ case "$1" in enable) sed -i 's/^EXCLUDEFROMQOS=.*$/EXCLUDEFROMQOS=true/' "$SCRIPT_CONF" @@ -1181,13 +1646,14 @@ ExcludeFromQoS(){ sed -i 's/^EXCLUDEFROMQOS=.*$/EXCLUDEFROMQOS=false/' "$SCRIPT_CONF" ;; check) - EXCLUDEFROMQOS=$(grep "EXCLUDEFROMQOS" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$EXCLUDEFROMQOS" + EXCLUDEFROMQOS="$(grep "^EXCLUDEFROMQOS=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${EXCLUDEFROMQOS:=true}" ;; esac } -AutoBWEnable(){ +AutoBWEnable() +{ case "$1" in enable) sed -i 's/^AUTOBW_ENABLED=.*$/AUTOBW_ENABLED=true/' "$SCRIPT_CONF" @@ -1196,79 +1662,106 @@ AutoBWEnable(){ sed -i 's/^AUTOBW_ENABLED=.*$/AUTOBW_ENABLED=false/' "$SCRIPT_CONF" ;; check) - AUTOBW_ENABLED=$(grep "AUTOBW_ENABLED" "$SCRIPT_CONF" | cut -f2 -d"=") - echo "$AUTOBW_ENABLED" + AUTOBW_ENABLED="$(grep "^AUTOBW_ENABLED=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${AUTOBW_ENABLED:=false}" ;; esac } -AutoBWConf(){ +AutoBWConf() +{ case "$1" in update) sed -i 's/^AUTOBW_'"$2"'_'"$3"'=.*$/AUTOBW_'"$2"'_'"$3"'='"$4"'/' "$SCRIPT_CONF" ;; check) - grep "AUTOBW_${2}_$3" "$SCRIPT_CONF" | cut -f2 -d"=" + grep "^AUTOBW_${2}_${3}=" "$SCRIPT_CONF" | cut -f2 -d"=" ;; esac } -#$1 fieldname $2 tablename $3 frequency (hours) $4 length (days) $5 outputfile $6 outputfrequency $7 interfacename $8 sqlfile $9 timestamp -WriteSql_ToFile(){ +#---------------------------------------------------------------------------- +# $1 fieldname $2 tablename $3 frequency (hours) $4 length (days) +# $5 outputfile $6 outputfrequency $7 interfacename $8 sqlfile $9 timestamp +#---------------------------------------------------------------------------- +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## +WriteSql_ToFile() +{ timenow="$9" maxcount="$(echo "$3" "$4" | awk '{printf ((24*$2)/$1)}')" - - if ! echo "$5" | grep -q "day"; then + + if ! echo "$5" | grep -q "day" + then { - echo ".mode csv" - echo ".headers off" - echo ".output ${5}_${6}_${7}.tmp" - echo "SELECT '$1' Metric,Min(strftime('%s',datetime(strftime('%Y-%m-%d %H:00:00',datetime([Timestamp],'unixepoch'))))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$maxcount hour'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch')),strftime('%d',datetime([Timestamp],'unixepoch')),strftime('%H',datetime([Timestamp],'unixepoch')) ORDER BY [Timestamp] DESC;" + echo ".mode csv" + echo ".headers off" + echo ".output ${5}_${6}_${7}.tmp" + echo "PRAGMA temp_store=1;" + echo "SELECT '$1' Metric,Min(strftime('%s',datetime(strftime('%Y-%m-%d %H:00:00',datetime([Timestamp],'unixepoch'))))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$maxcount hour'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch')),strftime('%d',datetime([Timestamp],'unixepoch')),strftime('%H',datetime([Timestamp],'unixepoch')) ORDER BY [Timestamp] DESC;" } > "$8" else { - echo ".mode csv" - echo ".headers off" - echo ".output ${5}_${6}_${7}.tmp" - echo "SELECT '$1' Metric,Max(strftime('%s',datetime([Timestamp],'unixepoch','localtime','start of day','utc'))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] > strftime('%s',datetime($timenow,'unixepoch','localtime','start of day','utc','+1 day','-$maxcount day'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch','localtime')),strftime('%d',datetime([Timestamp],'unixepoch','localtime')) ORDER BY [Timestamp] DESC;" + echo ".mode csv" + echo ".headers off" + echo ".output ${5}_${6}_${7}.tmp" + echo "PRAGMA temp_store=1;" + echo "SELECT '$1' Metric,Max(strftime('%s',datetime([Timestamp],'unixepoch','localtime','start of day','utc'))) Time,IFNULL(printf('%f',Avg($1)),'NaN') Value FROM $2 WHERE ([Timestamp] > strftime('%s',datetime($timenow,'unixepoch','localtime','start of day','utc','+1 day','-$maxcount day'))) GROUP BY strftime('%m',datetime([Timestamp],'unixepoch','localtime')),strftime('%d',datetime([Timestamp],'unixepoch','localtime')) ORDER BY [Timestamp] DESC;" } > "$8" fi } ##----------------------------------------## -## Modified by Martinski W. [2024-Jul-15] ## +## Modified by Martinski W. [2024-Nov-15] ## ##----------------------------------------## WriteStats_ToJS() { if [ $# -lt 4 ] ; then return 1 ; fi - echo "function $3(){" > "$2" + if [ -f "$2" ] + then + sed -i -e '/^}/d;/^function/d;/^document.getElementById/d;/^databaseResetDone/d;' "$2" + awk 'NF' "$2" > "${2}.tmp" + mv -f "${2}.tmp" "$2" + fi + printf "\nfunction %s(){\n" "$3" >> "$2" html='document.getElementById("'"$4"'").innerHTML="' while IFS='' read -r line || [ -n "$line" ] - do html="${html}${line}\r\n" + do html="${html}${line}" done < "$1" html="$html"'"' if [ $# -lt 5 ] || [ -z "$5" ] - then printf "%s\r\n}\r\n" "$html" >> "$2" - else printf "%s;\r\n%s\r\n}\r\n" "$html" "$5" >> "$2" + then printf "%s\n}\n" "$html" >> "$2" + else printf "%s;\n%s\n}\n" "$html" "$5" >> "$2" fi } -GenerateServerList(){ - if [ ! -f /opt/bin/jq ]; then +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-03] ## +##----------------------------------------## +GenerateServerList() +{ + if [ ! -f /opt/bin/jq ] && [ -x /opt/bin/opkg ] + then opkg update opkg install jq fi - promptforservername="$2" - printf "Generating list of closest servers for %s...\\n\\n" "$1" + if [ $# -lt 2 ] || [ -z "$2" ] + then promptforservername="" + else promptforservername="$2" + fi + printf "Generating list of closest servers for %s...\n\n" "$1" CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" SPEEDTEST_BINARY="" - if [ "$(SpeedtestBinary check)" = "builtin" ]; then + if [ "$(SpeedtestBinary check)" = "builtin" ] + then SPEEDTEST_BINARY=/usr/sbin/ookla - elif [ "$(SpeedtestBinary check)" = "external" ]; then + elif [ "$(SpeedtestBinary check)" = "external" ] + then SPEEDTEST_BINARY="$OOKLA_DIR/speedtest" fi if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then @@ -1276,92 +1769,103 @@ GenerateServerList(){ LICENSE_STRING="" fi serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$1")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null - if [ -z "$serverlist" ]; then + if [ -z "$serverlist" ] + then Print_Output true "Error retrieving server list for for $1" "$CRIT" serverno="exit" return 1 fi servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 - until [ $COUNTER -gt "$servercount" ]; do + until [ "$COUNTER" -gt "$servercount" ] + do serverdetails="$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')|$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" - - if [ "$COUNTER" -lt 10 ]; then - printf "%s) %s\\n" "$COUNTER" "$serverdetails" - elif [ "$COUNTER" -ge 10 ]; then - printf "%s) %s\\n" "$COUNTER" "$serverdetails" - fi - COUNTER=$((COUNTER + 1)) + + printf "%2d) %s\n" "$COUNTER" "$serverdetails" + COUNTER="$((COUNTER + 1))" done - - printf "\\ne) Go back\\n" - - while true; do - printf "\\n${BOLD}Please select a server from the list above (1-%s):${CLEARFORMAT}\\n" "$servercount" - printf "\\n${BOLD}Or press c to enter a known server ID${CLEARFORMAT}\\n" - printf "${BOLD}Enter answer:${CLEARFORMAT} " + + printf "\n e) Go back\n" + + while true + do + printf "\n${BOLD}Please select a server from the list above [1-%s].${CLEARFORMAT}" "$servercount" + printf "\n${BOLD}Or press ${GRNct}c${CLRct} to enter a known server ID.${CLEARFORMAT}" + printf "\n${BOLD}Enter answer:${CLEARFORMAT} " read -r server - - if [ "$server" = "e" ]; then + + if [ "$server" = "e" ] + then serverno="exit" break - elif [ "$server" = "c" ]; then - while true; do - printf "\\n${BOLD}Please enter server ID (WARNING: this is not validated) or e to go back${CLEARFORMAT} " + elif [ "$server" = "c" ] + then + while true + do + printf "\n${BOLD}Please enter server ID (WARNING: ID is NOT validated) or ${GRNct}e${CLRct} to go back.${CLEARFORMAT} " read -r customserver - if [ "$customserver" = "e" ]; then + if [ "$customserver" = "e" ] + then break - elif ! Validate_Number "$customserver"; then - printf "\\n\\e[31mPlease enter a valid number${CLEARFORMAT}\\n" + elif ! Validate_Number "$customserver" + then + printf "\n${ERR}Please enter a valid number.${CLEARFORMAT}\n" else serverno="$customserver" - if [ "$promptforservername" != "no" ]; then - while true; do - printf "\\n${BOLD}Would you like to enter a name for this server? (default: Custom) (y/n)?${CLEARFORMAT} " + if [ "$promptforservername" != "no" ] + then + while true + do + printf "\n${BOLD}Would you like to enter a name for this server? (default: Custom) (y/n)?${CLEARFORMAT} " read -r servername_select - if [ "$servername_select" = "n" ] || [ "$servername_select" = "N" ]; then + if [ "$servername_select" = "n" ] || [ "$servername_select" = "N" ] + then servername="Custom" break - elif [ "$servername_select" = "y" ] || [ "$servername_select" = "Y" ]; then - printf "\\n${BOLD}Please enter the name for this server:${CLEARFORMAT} " + elif [ "$servername_select" = "y" ] || [ "$servername_select" = "Y" ] + then + printf "\n${BOLD}Please enter the name for this server:${CLEARFORMAT} " read -r servername - printf "\\n${BOLD}%s${CLEARFORMAT}\\n" "$servername" - printf "\\n${BOLD}Is that correct (y/n)?${CLEARFORMAT} " + printf "\n${BOLD}%s${CLEARFORMAT}\n" "$servername" + printf "\n${BOLD}Is that correct (y/n)?${CLEARFORMAT} " read -r servername_confirm - if [ "$servername_confirm" = "y" ] || [ "$servername_confirm" = "Y" ]; then + if [ "$servername_confirm" = "y" ] || \ + [ "$servername_confirm" = "Y" ] + then break else - printf "\\n\\e[31mPlease enter y or n${CLEARFORMAT}\\n" + printf "\n${ERR}Please enter y or n${CLEARFORMAT}\n" fi else - printf "\\n\\e[31mPlease enter y or n${CLEARFORMAT}\\n" + printf "\n${ERR}Please enter y or n${CLEARFORMAT}\n" fi done else servername="Custom" fi - - printf "\\n" + printf "\n" return 0 fi done - elif ! Validate_Number "$server"; then - printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$servercount" + elif ! Validate_Number "$server" + then + printf "\n${ERR}Please enter a valid number [1-%s]${CLEARFORMAT}\n" "$servercount" else if [ "$server" -lt 1 ] || [ "$server" -gt "$servercount" ]; then - printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$servercount" + printf "\n${ERR}Please enter a number between 1 and %s.${CLEARFORMAT}\n" "$servercount" else serverno="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .id')" servername="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" - printf "\\n" + printf "\n" break fi fi done } -GenerateServerList_WebUI(){ +GenerateServerList_WebUI() +{ serverlistfile="$2" rm -f "/tmp/$serverlistfile.txt" rm -f "$SCRIPT_WEB_DIR/$serverlistfile.htm" @@ -1373,31 +1877,37 @@ GenerateServerList_WebUI(){ fi CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" - if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then + if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ] + then CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" fi - + spdifacename="$1" - - if [ ! -f /opt/bin/jq ]; then + + if [ ! -f /opt/bin/jq ] && [ -x /opt/bin/opkg ] + then opkg update opkg install jq fi - - if [ "$spdifacename" = "ALL" ]; then - while IFS='' read -r line || [ -n "$line" ]; do + + if [ "$spdifacename" = "ALL" ] + then + while IFS='' read -r line || [ -n "$line" ] + do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" - - for IFACE_NAME in $IFACELIST; do + + for IFACE_NAME in $IFACELIST + do serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$IFACE_NAME")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 - until [ $COUNTER -gt "$servercount" ]; do + until [ $COUNTER -gt "$servercount" ] + do printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done @@ -1407,7 +1917,8 @@ GenerateServerList_WebUI(){ serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$spdifacename")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null servercount="$(echo "$serverlist" | jq '.servers | length')" COUNTER=1 - until [ $COUNTER -gt "$servercount" ]; do + until [ $COUNTER -gt "$servercount" ] + do printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done @@ -1417,7 +1928,11 @@ GenerateServerList_WebUI(){ ln -s "/tmp/$serverlistfile.txt" "$SCRIPT_WEB_DIR/$serverlistfile.htm" 2>/dev/null } -PreferredServer(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +PreferredServer() +{ case "$1" in update) GenerateServerList "$2" @@ -1434,18 +1949,501 @@ PreferredServer(){ sed -i 's/^USEPREFERRED_'"$2"'=.*$/USEPREFERRED_'"$2"'=false/' "$SCRIPT_CONF" ;; check) - USEPREFERRED=$(grep "USEPREFERRED_$2" "$SCRIPT_CONF" | cut -f2 -d"=") + USEPREFERRED="$(grep "^USEPREFERRED_${2}=" "$SCRIPT_CONF" | cut -f2 -d'=')" if [ "$USEPREFERRED" = "true" ]; then return 0; else return 1; fi ;; list) - PREFERREDSERVER=$(grep "PREFERREDSERVER_$2" "$SCRIPT_CONF" | cut -f2 -d"=") + PREFERREDSERVER="$(grep "^PREFERREDSERVER_${2}=" "$SCRIPT_CONF" | cut -f2 -d'=')" echo "$PREFERREDSERVER" ;; esac } -Run_Speedtest(){ - if [ ! -f /opt/bin/xargs ]; then +##-------------------------------------## +## Added by Martinski W. [2024-Nov-15] ## +##-------------------------------------## +_GetFileSize_() +{ + local sizeUnits sizeInfo fileSize + if [ $# -eq 0 ] || [ -z "$1" ] || [ ! -s "$1" ] + then echo 0; return 1 ; fi + + if [ $# -lt 2 ] || [ -z "$2" ] || \ + ! echo "$2" | grep -qE "^(B|KB|MB|GB|HR|HRx)$" + then sizeUnits="B" ; else sizeUnits="$2" ; fi + + _GetNum_() { printf "%.1f" "$(echo "$1" | awk "{print $1}")" ; } + + case "$sizeUnits" in + B|KB|MB|GB) + fileSize="$(ls -1l "$1" | awk -F ' ' '{print $3}')" + case "$sizeUnits" in + KB) fileSize="$(_GetNum_ "($fileSize / $oneKByte)")" ;; + MB) fileSize="$(_GetNum_ "($fileSize / $oneMByte)")" ;; + GB) fileSize="$(_GetNum_ "($fileSize / $oneGByte)")" ;; + esac + echo "$fileSize" + ;; + HR|HRx) + fileSize="$(ls -1lh "$1" | awk -F ' ' '{print $3}')" + sizeInfo="${fileSize}B" + if [ "$sizeUnits" = "HR" ] + then echo "$sizeInfo" ; return 0 ; fi + sizeUnits="$(echo "$sizeInfo" | tr -d '.0-9')" + case "$sizeUnits" in + MB) fileSize="$(_GetFileSize_ "$1" KB)" + sizeInfo="$sizeInfo [${fileSize}KB]" + ;; + GB) fileSize="$(_GetFileSize_ "$1" MB)" + sizeInfo="$sizeInfo [${fileSize}MB]" + ;; + esac + echo "$sizeInfo" + ;; + *) echo 0 ;; + esac + return 0 +} + +##-------------------------------------## +## Added by Martinski W. [2025-Jan-19] ## +##-------------------------------------## +_Get_JFFS_Space_() +{ + local typex total usedx freex totalx + local sizeUnits sizeType sizeInfo sizeNum + local jffsMountStr jffsUsageStr percentNum percentStr + + if [ $# -lt 1 ] || [ -z "$1" ] || \ + ! echo "$1" | grep -qE "^(ALL|USED|FREE)$" + then sizeType="ALL" ; else sizeType="$1" ; fi + + if [ $# -lt 2 ] || [ -z "$2" ] || \ + ! echo "$2" | grep -qE "^(KB|KBP|MBP|GBP|HR|HRx)$" + then sizeUnits="KB" ; else sizeUnits="$2" ; fi + + _GetNum_() { printf "%.2f" "$(echo "$1" | awk "{print $1}")" ; } + + jffsMountStr="$(mount | grep '/jffs')" + jffsUsageStr="$(df -kT /jffs | grep -E '.*[[:blank:]]+/jffs$')" + + if [ -z "$jffsMountStr" ] || [ -z "$jffsUsageStr" ] + then echo "**ERROR**: JFFS is *NOT* mounted." ; return 1 + fi + if echo "$jffsMountStr" | grep -qE "[[:blank:]]+[(]?ro[[:blank:],]" + then echo "**ERROR**: JFFS is mounted READ-ONLY." ; return 2 + fi + + typex="$(echo "$jffsUsageStr" | awk -F ' ' '{print $2}')" + total="$(echo "$jffsUsageStr" | awk -F ' ' '{print $3}')" + usedx="$(echo "$jffsUsageStr" | awk -F ' ' '{print $4}')" + freex="$(echo "$jffsUsageStr" | awk -F ' ' '{print $5}')" + totalx="$total" + if [ "$typex" = "ubifs" ] && [ "$((usedx + freex))" -ne "$total" ] + then totalx="$((usedx + freex))" ; fi + + if [ "$sizeType" = "ALL" ] ; then echo "$totalx" ; return 0 ; fi + + case "$sizeUnits" in + KB|KBP|MBP|GBP) + case "$sizeType" in + USED) sizeNum="$usedx" + percentNum="$(printf "%.1f" "$(_GetNum_ "($usedx * 100 / $totalx)")")" + percentStr="[${percentNum}%]" + ;; + FREE) sizeNum="$freex" + percentNum="$(printf "%.1f" "$(_GetNum_ "($freex * 100 / $totalx)")")" + percentStr="[${percentNum}%]" + ;; + esac + case "$sizeUnits" in + KB) sizeInfo="$sizeNum" + ;; + KBP) sizeInfo="${sizeNum}.0KB $percentStr" + ;; + MBP) sizeNum="$(_GetNum_ "($sizeNum / $oneKByte)")" + sizeInfo="${sizeNum}MB $percentStr" + ;; + GBP) sizeNum="$(_GetNum_ "($sizeNum / $oneMByte)")" + sizeInfo="${sizeNum}GB $percentStr" + ;; + esac + echo "$sizeInfo" + ;; + HR|HRx) + jffsUsageStr="$(df -hT /jffs | grep -E '.*[[:blank:]]+/jffs$')" + case "$sizeType" in + USED) usedx="$(echo "$jffsUsageStr" | awk -F ' ' '{print $4}')" + sizeInfo="${usedx}B" + ;; + FREE) freex="$(echo "$jffsUsageStr" | awk -F ' ' '{print $5}')" + sizeInfo="${freex}B" + ;; + esac + if [ "$sizeUnits" = "HR" ] + then echo "$sizeInfo" ; return 0 ; fi + sizeUnits="$(echo "$sizeInfo" | tr -d '.0-9')" + case "$sizeUnits" in + KB) sizeInfo="$(_Get_JFFS_Space_ "$sizeType" KBP)" ;; + MB) sizeInfo="$(_Get_JFFS_Space_ "$sizeType" MBP)" ;; + GB) sizeInfo="$(_Get_JFFS_Space_ "$sizeType" GBP)" ;; + esac + echo "$sizeInfo" + ;; + *) echo 0 ;; + esac + return 0 +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +##--------------------------------------------------------## +## Minimum Reserved JFFS Available Free Space is roughly +## about 20% of total space or about 9MB to 10MB. +##--------------------------------------------------------## +_JFFS_MinReservedFreeSpace_() +{ + local jffsAllxSpace jffsMinxSpace + + if ! jffsAllxSpace="$(_Get_JFFS_Space_ ALL KB)" + then echo "$jffsAllxSpace" ; return 1 ; fi + jffsAllxSpace="$(echo "$jffsAllxSpace" | awk '{printf("%s", $1 * 1024);}')" + + jffsMinxSpace="$(echo "$jffsAllxSpace" | awk '{printf("%d", $1 * 20 / 100);}')" + if [ "$(echo "$jffsMinxSpace $ni9MByte" | awk -F ' ' '{print ($1 < $2)}')" -eq 1 ] + then jffsMinxSpace="$ni9MByte" + elif [ "$(echo "$jffsMinxSpace $tenMByte" | awk -F ' ' '{print ($1 > $2)}')" -eq 1 ] + then jffsMinxSpace="$tenMByte" + fi + echo "$jffsMinxSpace" ; return 0 +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +##--------------------------------------------------------## +## Check JFFS free space *BEFORE* moving files from USB. +##--------------------------------------------------------## +_Check_JFFS_SpaceAvailable_() +{ + local requiredSpace jffsFreeSpace jffsMinxSpace + if [ $# -eq 0 ] || [ -z "$1" ] || [ ! -d "$1" ] ; then return 0 ; fi + + [ "$1" = "/jffs/addons/${SCRIPT_NAME_LOWER}.d" ] && return 0 + + if ! jffsFreeSpace="$(_Get_JFFS_Space_ FREE KB)" ; then return 1 ; fi + if ! jffsMinxSpace="$(_JFFS_MinReservedFreeSpace_)" ; then return 1 ; fi + jffsFreeSpace="$(echo "$jffsFreeSpace" | awk '{printf("%s", $1 * 1024);}')" + + requiredSpace="$(du -kc "$1" | grep -w 'total$' | awk -F ' ' '{print $1}')" + requiredSpace="$(echo "$requiredSpace" | awk '{printf("%s", $1 * 1024);}')" + requiredSpace="$(echo "$requiredSpace $jffsMinxSpace" | awk -F ' ' '{printf("%s", $1 + $2);}')" + if [ "$(echo "$requiredSpace $jffsFreeSpace" | awk -F ' ' '{print ($1 < $2)}')" -eq 1 ] + then return 0 ; fi + + ## Current JFFS Available Free Space is NOT sufficient ## + requiredSpace="$(du -hc "$1" | grep -w 'total$' | awk -F ' ' '{print $1}')" + errorMsg1="Not enough free space [$(_Get_JFFS_Space_ FREE HR)] available in JFFS." + errorMsg2="Minimum storage space required: $requiredSpace" + Print_Output true "${errorMsg1} ${errorMsg2}" "$CRIT" + return 1 +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_WriteVarDefToJSFile_() +{ + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] + then return 1; fi + + local varValue + if [ $# -eq 3 ] && [ "$3" = "true" ] + then varValue="$2" + else varValue="'${2}'" + fi + + local targetJSfile="$SCRIPT_STORAGE_DIR/spdtitletext.js" + if [ ! -s "$targetJSfile" ] + then + echo "var $1 = ${varValue};" > "$targetJSfile" + elif + ! grep -q "^var $1 =.*" "$targetJSfile" + then + sed -i "1 i var $1 = ${varValue};" "$targetJSfile" + elif + ! grep -q "^var $1 = ${varValue};" "$targetJSfile" + then + sed -i "s/^var $1 =.*/var $1 = ${varValue};/" "$targetJSfile" + fi +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_DelVarDefFromJSFile_() +{ + if [ $# -eq 0 ] || [ -z "$1" ] ; then return 1; fi + + local targetJSfile="$SCRIPT_STORAGE_DIR/spdtitletext.js" + if [ -s "$targetJSfile" ] && \ + grep -q "^var $1 =.*" "$targetJSfile" + then + sed -i "/^var $1 =.*/d" "$targetJSfile" + fi +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +JFFS_WarningLogTime() +{ + case "$1" in + update) + sed -i 's/^JFFS_MSGLOGTIME=.*$/JFFS_MSGLOGTIME='"$2"'/' "$SCRIPT_CONF" + ;; + check) + JFFS_MSGLOGTIME="$(grep "^JFFS_MSGLOGTIME=" "$SCRIPT_CONF" | cut -f2 -d'=')" + echo "${JFFS_MSGLOGTIME:=0}" + ;; + esac +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_JFFS_WarnLowFreeSpace_() +{ + if [ $# -eq 0 ] || [ -z "$1" ] ; then return 0 ; fi + local jffsWarningLogFreq jffsWarningLogTime storageLocStr + local logPriNum logTagStr logMsgStr currTimeSecs currTimeDiff + + storageLocStr="$(ScriptStorageLocation check | tr 'a-z' 'A-Z')" + if [ "$storageLocStr" = "JFFS" ] + then + if [ "$JFFS_LowFreeSpaceStatus" = "WARNING2" ] + then + logPriNum=2 + logTagStr="**ALERT**" + jffsWarningLogFreq="$_12Hours" + else + logPriNum=3 + logTagStr="**WARNING**" + jffsWarningLogFreq="$_24Hours" + fi + else + if [ "$JFFS_LowFreeSpaceStatus" = "WARNING2" ] + then + logPriNum=3 + logTagStr="**WARNING**" + jffsWarningLogFreq="$_24Hours" + else + logPriNum=4 + logTagStr="**NOTICE**" + jffsWarningLogFreq="$_36Hours" + fi + fi + jffsWarningLogTime="$(JFFS_WarningLogTime check)" + + currTimeSecs="$(date +'%s')" + currTimeDiff="$(echo "$currTimeSecs $jffsWarningLogTime" | awk -F ' ' '{printf("%s", $1 - $2);}')" + if [ "$currTimeDiff" -ge "$jffsWarningLogFreq" ] + then + JFFS_WarningLogTime update "$currTimeSecs" + logMsgStr="${logTagStr} JFFS Available Free Space ($1) is getting LOW." + logger -t "$SCRIPT_NAME" -p $logPriNum "$logMsgStr" + fi +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_UpdateJFFS_FreeSpaceInfo_() +{ + local jffsFreeSpaceHR jffsFreeSpace jffsMinxSpace + [ ! -d "$SCRIPT_STORAGE_DIR" ] && return 1 + + jffsFreeSpaceHR="$(_Get_JFFS_Space_ FREE HRx)" + _DelVarDefFromJSFile_ "jffsAvailableSpace" + _WriteVarDefToJSFile_ "jffsAvailableSpaceStr" "$jffsFreeSpaceHR" + + if ! jffsFreeSpace="$(_Get_JFFS_Space_ FREE KB)" ; then return 1 ; fi + if ! jffsMinxSpace="$(_JFFS_MinReservedFreeSpace_)" ; then return 1 ; fi + jffsFreeSpace="$(echo "$jffsFreeSpace" | awk '{printf("%s", $1 * 1024);}')" + + JFFS_LowFreeSpaceStatus="OK" + ## Warning Level 1 if JFFS Available Free Space is LESS than Minimum Reserved ## + if [ "$(echo "$jffsFreeSpace $jffsMinxSpace" | awk -F ' ' '{print ($1 < $2)}')" -eq 1 ] + then + JFFS_LowFreeSpaceStatus="WARNING1" + ## Warning Level 2 if JFFS Available Free Space is LESS than 8.0MB ## + if [ "$(echo "$jffsFreeSpace $ei8MByte" | awk -F ' ' '{print ($1 < $2)}')" -eq 1 ] + then + JFFS_LowFreeSpaceStatus="WARNING2" + fi + _JFFS_WarnLowFreeSpace_ "$jffsFreeSpaceHR" + fi + _WriteVarDefToJSFile_ "jffsAvailableSpaceLow" "$JFFS_LowFreeSpaceStatus" +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_UpdateDatabaseFileSizeInfo_() +{ + local databaseFileSize + [ ! -d "$SCRIPT_STORAGE_DIR" ] && return 1 + + _UpdateJFFS_FreeSpaceInfo_ + databaseFileSize="$(_GetFileSize_ "$SPEEDSTATS_DB" HRx)" + _WriteVarDefToJSFile_ "sqlDatabaseFileSize" "$databaseFileSize" +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_ApplyDatabaseSQLCmds_() +{ + local errorCount=0 maxErrorCount=5 callFlag + local triesCount=0 maxTriesCount=25 sqlErrorMsg + local tempLogFilePath="/tmp/spdMerlinStats_TMP_$$.LOG" + + if [ $# -gt 1 ] && [ -n "$2" ] + then callFlag="$2" + else callFlag="err" + fi + + resultStr="" + foundError=false ; foundLocked=false + rm -f "$tempLogFilePath" + + while [ "$errorCount" -lt "$maxErrorCount" ] && \ + [ "$((triesCount++))" -lt "$maxTriesCount" ] + do + if "$SQLITE3_PATH" "$SPEEDSTATS_DB" < "$1" >> "$tempLogFilePath" 2>&1 + then foundError=false ; foundLocked=false ; break + fi + sqlErrorMsg="$(cat "$tempLogFilePath")" + if echo "$sqlErrorMsg" | grep -qE "^(Parse error|Runtime error|Error:)" + then + if echo "$sqlErrorMsg" | grep -qE "^(Parse|Runtime) error .*: database is locked" + then + echo -n > "$tempLogFilePath" ##Clear for next error found## + foundLocked=true ; sleep 2 ; continue + fi + errorCount="$((errorCount + 1))" + foundError=true ; foundLocked=false + Print_Output true "SQLite3 failure[$callFlag]: $sqlErrorMsg" "$ERR" + echo -n > "$tempLogFilePath" ##Clear for next error found## + fi + [ "$triesCount" -ge "$maxTriesCount" ] && break + [ "$errorCount" -ge "$maxErrorCount" ] && break + sleep 1 + done + + rm -f "$tempLogFilePath" + if "$foundError" + then resultStr="reported error(s)." + elif "$foundLocked" + then resultStr="found database locked." + else resultStr="completed successfully." + fi + if "$foundError" || "$foundLocked" + then + Print_Output true "SQLite process ${resultStr}" "$ERR" + fi +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_Optimize_Database_() +{ + renice 15 $$ + local foundError foundLocked resultStr SQLTable sqlProcSuccess + + Print_Output true "Running database analysis and optimization..." "$PASS" + + sqlProcSuccess=true + local optIndx=0 + for dbtable in $FULL_IFACELIST + do + SQLTable="spdstats_$dbtable" + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA journal_mode=TRUNCATE;" + echo "PRAGMA analysis_limit=0;" + echo "PRAGMA cache_size=-20000;" + echo "ANALYZE $SQLTable;" + echo "VACUUM;" + } > /tmp/spdMerlin-trim.sql + optIndx="$((optIndx + 1))" + _ApplyDatabaseSQLCmds_ /tmp/spdMerlin-trim.sql "opt$optIndx" + + rm -f /tmp/spdMerlin-trim.sql + if "$foundError" || "$foundLocked" + then + sqlProcSuccess=false + Print_Output true "Database [$SQLTable] analysis and optimization ${resultStr}" "$ERR" + fi + done + + "$sqlProcSuccess" && \ + Print_Output true "Database analysis and optimization completed successfully." "$PASS" + + renice 0 $$ +} + +##-------------------------------------## +## Added by Martinski W. [2025-Feb-28] ## +##-------------------------------------## +_Trim_Database_() +{ + renice 15 $$ + TZ="$(cat /etc/TZ)" + export TZ + timeNow="$(date +'%s')" + + local foundError foundLocked resultStr SQLTable sqlProcSuccess + + Print_Output true "Trimming records from database..." "$PASS" + + sqlProcSuccess=true + local trmIndx=0 + for dbtable in $FULL_IFACELIST + do + SQLTable="spdstats_$dbtable" + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA journal_mode=TRUNCATE;" + echo "PRAGMA cache_size=-20000;" + echo "DELETE FROM [$SQLTable] WHERE [Timestamp] < strftime('%s',datetime($timeNow,'unixepoch','-$(DaysToKeep check) day'));" + } > /tmp/spdMerlin-trim.sql + trmIndx="$((trmIndx + 1))" + _ApplyDatabaseSQLCmds_ /tmp/spdMerlin-trim.sql "trm$trmIndx" + + rm -f /tmp/spdMerlin-trim.sql + if "$foundError" || "$foundLocked" + then + sqlProcSuccess=false + Print_Output true "Database [$SQLTable] record trimming ${resultStr}" "$ERR" + fi + done + + "$sqlProcSuccess" && \ + Print_Output true "Database record trimming completed successfully." "$PASS" + + renice 0 $$ +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Run_Speedtest() +{ + if [ ! -f /opt/bin/xargs ] && [ -x /opt/bin/opkg ] + then Print_Output true "Installing findutils from Entware" opkg update opkg install findutils @@ -1457,60 +2455,78 @@ Run_Speedtest(){ fi Create_Dirs Conf_Exists - if [ "$(SpeedtestBinary check)" = "builtin" ]; then + if [ "$(SpeedtestBinary check)" = "builtin" ] + then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary - elif [ "$(SpeedtestBinary check)" = "external" ]; then + elif [ "$(SpeedtestBinary check)" = "external" ] + then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi Auto_Startup create 2>/dev/null - if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi + if AutomaticMode check + then Auto_Cron create 2>/dev/null + else Auto_Cron delete 2>/dev/null + fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create ScriptStorageLocation load Create_Symlinks - + mode="$1" - specificiface="$2" + if [ $# -lt 2 ] || [ -z "$2" ] + then specificiface="" + else specificiface="$2" + fi speedtestserverno="" speedtestservername="" - + MAXwaitTestSecs=120 #2 minutes# + local spdIndx=0 + CONFIG_STRING="" LICENSE_STRING="--accept-license --accept-gdpr" PROC_NAME="speedtest" SPEEDTEST_BINARY="" - if [ "$(SpeedtestBinary check)" = "builtin" ]; then + if [ "$(SpeedtestBinary check)" = "builtin" ] + then SPEEDTEST_BINARY=/usr/sbin/ookla - elif [ "$(SpeedtestBinary check)" = "external" ]; then + elif [ "$(SpeedtestBinary check)" = "external" ] + then SPEEDTEST_BINARY="$OOKLA_DIR/speedtest" fi - if [ "$SPEEDTEST_BINARY" = /usr/sbin/ookla ]; then + if [ "$SPEEDTEST_BINARY" = "/usr/sbin/ookla" ] + then CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" PROC_NAME="ookla" fi - + echo 'var spdteststatus = "InProgress";' > /tmp/detect_spdtest.js - + tmpfile=/tmp/spd-stats.txt resultfile=/tmp/spd-result.txt rm -f "$resultfile" rm -f "$tmpfile" - + if [ -n "$(pidof "$PROC_NAME")" ]; then killall -q "$PROC_NAME" fi - - if Check_Swap ; then + + if Check_Swap + then IFACELIST="" - if [ -z "$specificiface" ]; then - while IFS='' read -r line || [ -n "$line" ]; do + if [ -z "$specificiface" ] + then + while IFS='' read -r line || [ -n "$line" ] + do if [ "$(echo "$line" | grep -c "#")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" - elif [ "$specificiface" = "All" ]; then - while IFS='' read -r line || [ -n "$line" ]; do + elif [ "$specificiface" = "All" ] + then + while IFS='' read -r line || [ -n "$line" ] + do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi @@ -1519,13 +2535,18 @@ Run_Speedtest(){ else IFACELIST="$specificiface" fi - - if [ "$IFACELIST" != "" ]; then + + if [ "$IFACELIST" != "" ] + then stoppedqos="false" - if [ "$(ExcludeFromQoS check)" = "true" ]; then - if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ]; then - for ACTION in -D -A ; do - for proto in tcp udp; do + if [ "$(ExcludeFromQoS check)" = "true" ] + then + if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ] + then + for ACTION in -D -A + do + for proto in tcp udp + do iptables "$ACTION" OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables "$ACTION" OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle "$ACTION" OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null @@ -1533,25 +2554,30 @@ Run_Speedtest(){ done stoppedqos="true" done - elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ]; then + elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ] + then /tmp/qos stop >/dev/null 2>&1 stoppedqos="true" - elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ]; then + elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ] + then /jffs/addons/cake-qos/cake-qos stop >/dev/null 2>&1 stoppedqos="true" fi fi - + applyautobw="false" - if [ "$mode" = "schedule" ] && [ "$(AutoBWEnable check)" = "true" ]; then applyautobw="true" fi - - for IFACE_NAME in $IFACELIST; do + + spdIndx=0 + for IFACE_NAME in $IFACELIST + do IFACE="$(Get_Interface_From_Name "$IFACE_NAME")" IFACE_LOWER="$(echo "$IFACE" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ]; then + if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + then Print_Output true "$IFACE not up, please check. Skipping speedtest for $IFACE_NAME" "$WARN" continue else @@ -1563,14 +2589,17 @@ Run_Speedtest(){ mode="user" fi - if [ "$mode" = "schedule" ]; then - if PreferredServer check "$IFACE_NAME"; then + if [ "$mode" = "schedule" ] + then + if PreferredServer check "$IFACE_NAME" + then speedtestserverno="$(PreferredServer list "$IFACE_NAME" | cut -f1 -d"|")" speedtestservername="$(PreferredServer list "$IFACE_NAME" | cut -f2 -d"|")" else mode="auto" fi - elif [ "$mode" = "onetime" ]; then + elif [ "$mode" = "onetime" ] + then GenerateServerList "$IFACE_NAME" no if [ "$serverno" != "exit" ]; then speedtestserverno="$serverno" @@ -1579,39 +2608,44 @@ Run_Speedtest(){ Clear_Lock return 1 fi - elif [ "$mode" = "user" ]; then + elif [ "$mode" = "user" ] + then speedtestserverno="$(PreferredServer list "$IFACE_NAME" | cut -f1 -d"|")" speedtestservername="$(PreferredServer list "$IFACE_NAME" | cut -f2 -d"|")" fi - + echo 'var spdteststatus = "InProgress_'"$IFACE_NAME"'";' > /tmp/detect_spdtest.js printf "" > "$tmpfile" - if [ "$mode" = "auto" ]; then + if [ "$mode" = "auto" ] + then Print_Output true "Starting speedtest using auto-selected server for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 - speedtestcount=0 - while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do - speedtestcount="$((speedtestcount + 1))" - sleep 1 + speedTestSecs=0 + while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedTestSecs" -lt "$MAXwaitTestSecs" ] + do + speedTestSecs="$((speedTestSecs + 1))" ; sleep 1 done - if [ "$speedtestcount" -ge 120 ]; then + if [ "$speedTestSecs" -ge "$MAXwaitTestSecs" ] + then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue fi else - if [ "$speedtestserverno" -ne 0 ]; then + if [ "$speedtestserverno" -ne 0 ] + then Print_Output true "Starting speedtest using $speedtestservername for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --server-id="$speedtestserverno" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 - speedtestcount=0 - while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do - speedtestcount="$((speedtestcount + 1))" - sleep 1 + speedTestSecs=0 + while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedTestSecs" -lt "$MAXwaitTestSecs" ] + do + speedTestSecs="$((speedTestSecs + 1))" ; sleep 1 done - if [ "$speedtestcount" -ge 120 ]; then + if [ "$speedTestSecs" -ge "$MAXwaitTestSecs" ] + then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue @@ -1620,35 +2654,37 @@ Run_Speedtest(){ Print_Output true "Starting speedtest using using auto-selected server for $IFACE_NAME interface" "$PASS" "$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$IFACE" --format="human-readable" --unit="Mbps" -p $LICENSE_STRING | tee "$tmpfile" & sleep 2 - speedtestcount=0 - while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedtestcount" -lt 120 ]; do - speedtestcount="$((speedtestcount + 1))" - sleep 1 + speedTestSecs=0 + while [ -n "$(pidof "$PROC_NAME")" ] && [ "$speedTestSecs" -lt "$MAXwaitTestSecs" ] + do + speedTestSecs="$((speedTestSecs + 1))" ; sleep 1 done - if [ "$speedtestcount" -ge 120 ]; then + if [ "$speedTestSecs" -ge "$MAXwaitTestSecs" ] + then Print_Output true "Speedtest for $IFACE_NAME hung (> 2 mins), killing process" "$CRIT" killall -q "$PROC_NAME" continue fi fi fi - - if [ ! -f "$tmpfile" ] || [ -z "$(cat "$tmpfile")" ] || [ "$(grep -c FAILED $tmpfile)" -gt 0 ]; then + + if [ ! -s "$tmpfile" ] || [ -z "$(cat "$tmpfile")" ] || [ "$(grep -c FAILED $tmpfile)" -gt 0 ]; then Print_Output true "Error running speedtest for $IFACE_NAME" "$CRIT" continue fi - + ScriptStorageLocation load - - TZ=$(cat /etc/TZ) + + TZ="$(cat /etc/TZ)" export TZ - - timenow=$(date +"%s") - timenowfriendly=$(date +"%c") - + + timenow="$(date +'%s')" + timenowfriendly="$(date +'%c')" + ## New if-then-else block added to with ookla output when buffer bloat has been added to the human readable output - BUFFBLOAT=$(grep "Idle Latency:" "$tmpfile") - if [ -n "$BUFFBLOAT" ]; then + BUFFBLOAT="$(grep "Idle Latency:" "$tmpfile")" + if [ -n "$BUFFBLOAT" ] + then # Parse human readable output when buffer bloat data is included. download="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" upload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" @@ -1658,14 +2694,13 @@ Run_Speedtest(){ resulturl="$(grep "Result URL:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}')" datadownload="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" dataupload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" - + datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" - + servername="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f1 -d'(' | cut -f2 -d':' | awk '{$1=$1;print}')" serverid="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f2 -d'(' | awk '{print $2}' | tr -d ')')" else - download="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" upload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" latency="$(grep "Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $2}')" @@ -1674,14 +2709,14 @@ Run_Speedtest(){ resulturl="$(grep "Result URL:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $3}')" datadownload="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" dataupload="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print $6}')" - + datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,length($7)-1)}')" - + servername="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f1 -d'(' | cut -f2 -d':' | awk '{$1=$1;print}')" serverid="$(grep "Server:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | cut -f2 -d'(' | awk '{print $3}' | tr -d ')')" fi - + ! Validate_Bandwidth "$download" && download=0; ! Validate_Bandwidth "$upload" && upload=0; ! Validate_Bandwidth "$latency" && latency="null"; @@ -1689,22 +2724,23 @@ Run_Speedtest(){ ! Validate_Bandwidth "$pktloss" && pktloss="null"; ! Validate_Bandwidth "$datadownload" && datadownload=0; ! Validate_Bandwidth "$dataupload" && dataupload=0; - + if [ "$datadownloadunit" = "GB" ]; then datadownload="$(echo "$datadownload" | awk '{printf ($1*1024)}')" fi - + if [ "$datauploadunit" = "GB" ]; then dataupload="$(echo "$dataupload" | awk '{printf ($1*1024)}')" fi - - if [ "$(SpeedtestBinary check)" = "builtin" ]; then + + if [ "$(SpeedtestBinary check)" = "builtin" ] + then curllatency="$latency" if [ "$curllatency" = "null" ]; then curllatency=0 fi - curlresult=$(/usr/sbin/curl -fsL --retry 3 -d "recommendedserverid=$serverid" \ + curlresult=$(curl -fsL --retry 4 --retry-delay 5 -d "recommendedserverid=$serverid" \ -d "ping=$(echo "$curllatency" | awk '{printf("%.0f\n", $1);}')" \ -d "screenresolution=" \ -d "promo=" \ @@ -1724,35 +2760,39 @@ Run_Speedtest(){ resulturl="https://www.speedtest.net/result/$(echo "$curlresult" | cut -f2 -d'&' | cut -f2 -d'=')" printf " Result URL: %s\\n" "$resulturl" | tee -a "$tmpfile" fi - - echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - + + spdIndx="$((spdIndx + 1))" + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA journal_mode=TRUNCATE;" + echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" + } > /tmp/spdTest-stats.sql + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "spd1$spdIndx" + STORERESULTURL="$(StoreResultURL check)" - - if [ "$STORERESULTURL" = "true" ]; then - echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'$resulturl',$datadownload,$dataupload,$serverid,'$servername');" > /tmp/spd-stats.sql - elif [ "$STORERESULTURL" = "false" ]; then - echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'',$datadownload,$dataupload,$serverid,'$servername');" > /tmp/spd-stats.sql + + if [ "$STORERESULTURL" = "true" ] + then + { + echo "PRAGMA temp_store=1;" + echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'$resulturl',$datadownload,$dataupload,$serverid,'$servername');" + } > /tmp/spdTest-stats.sql + elif [ "$STORERESULTURL" = "false" ] + then + { + echo "PRAGMA temp_store=1;" + echo "INSERT INTO spdstats_$IFACE_NAME ([Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[ResultURL],[DataDownload],[DataUpload],[ServerID],[ServerName]) values($timenow,$download,$upload,$latency,$jitter,$pktloss,'',$datadownload,$dataupload,$serverid,'$servername');" + } > /tmp/spdTest-stats.sql fi - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - { - echo "DELETE FROM [spdstats_$IFACE_NAME] WHERE [Timestamp] < strftime('%s',datetime($timenow,'unixepoch','-$(DaysToKeep check) day'));" - echo "PRAGMA analysis_limit=0;" - echo "PRAGMA cache_size=-20000;" - echo "ANALYZE spdstats_$IFACE_NAME;" - } > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql >/dev/null 2>&1 - rm -f /tmp/spd-stats.sql - + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "spd2$spdIndx" + spdtestresult="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};'| awk '{$1=$1};1') - $(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};'| awk '{$1=$1};1')" spdtestresult2="$(grep "Latency:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{$1=$1};1') - $(grep "Packet Loss:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{$1=$1};1')" - - printf "\\n" + + printf "\n" Print_Output true "Speedtest results - $spdtestresult" "$PASS" Print_Output true "Connection quality - $spdtestresult2" "$PASS" - + { printf "Speedtest result for %s\\n" "$IFACE_NAME" printf "\\nBandwidth - %s\\n" "$spdtestresult" @@ -1760,45 +2800,52 @@ Run_Speedtest(){ grep "Result URL" "$tmpfile" | awk '{$1=$1};1' printf "\\n\\n\\n" } >> "$resultfile" - #extStats + extStats="/jffs/addons/extstats.d/mod_spdstats.sh" if [ -f "$extStats" ]; then sh "$extStats" ext "$download" "$upload" fi fi done - - if [ "$stoppedqos" = "true" ]; then - if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ]; then - for proto in tcp udp; do + + if [ "$stoppedqos" = "true" ] + then + if [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -eq 1 ] + then + for proto in tcp udp + do iptables -D OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -D OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D OUTPUT -p "$proto" -o "$(Get_Interface_From_Name WAN)" -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null iptables -t mangle -D OUTPUT -p "$proto" -o tun1+ -j MARK --set-xmark 0x80000000/0xC0000000 2>/dev/null done - elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ]; then + elif [ "$(nvram get qos_enable)" -eq 1 ] && [ "$(nvram get qos_type)" -ne 1 ] && [ -f /tmp/qos ] + then /tmp/qos start >/dev/null 2>&1 - elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ]; then + elif [ "$(nvram get qos_enable)" -eq 0 ] && [ -f /jffs/addons/cake-qos/cake-qos ] + then /jffs/addons/cake-qos/cake-qos start >/dev/null 2>&1 fi fi - + echo 'var spdteststatus = "GenerateCSV";' > /tmp/detect_spdtest.js - Print_Output true "Retrieving data for WebUI charts" "$PASS" + Print_Output true "Retrieving data for WebUI charts..." "$PASS" Generate_CSVs + _UpdateDatabaseFileSizeInfo_ + echo "Stats last updated: $timenowfriendly" > /tmp/spdstatstitle.txt WriteStats_ToJS /tmp/spdstatstitle.txt "$SCRIPT_STORAGE_DIR/spdtitletext.js" SetSPDStatsTitle statstitle if [ "$applyautobw" = "true" ]; then Menu_AutoBW_Update fi - + rm -f "$tmpfile" rm -f /tmp/spdstatstitle.txt - + echo 'var spdteststatus = "Done";' > /tmp/detect_spdtest.js - + Clear_Lock else echo 'var spdteststatus = "Error";' > /tmp/detect_spdtest.js @@ -1815,17 +2862,21 @@ Run_Speedtest(){ fi } -Run_Speedtest_WebUI(){ +Run_Speedtest_WebUI() +{ spdteststring="$(echo "$1" | sed "s/${SCRIPT_NAME_LOWER}spdtest_//;s/%/ /g")"; spdtestmode="webui_$(echo "$spdteststring" | cut -f1 -d'_')"; spdifacename="$(echo "$spdteststring" | cut -f2 -d'_')"; - - cp -a "$SCRIPT_CONF" "$SCRIPT_CONF.bak" - - if [ "$spdtestmode" = "webui_onetime" ]; then + + cp -a "$SCRIPT_CONF" "${SCRIPT_CONF}.bak" + + if [ "$spdtestmode" = "webui_onetime" ] + then spdtestserverlist="$(echo "$spdteststring" | cut -f3 -d'_')"; - if [ "$spdifacename" = "All" ]; then - while IFS='' read -r line || [ -n "$line" ]; do + if [ "$spdifacename" = "All" ] + then + while IFS='' read -r line || [ -n "$line" ] + do if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi @@ -1833,10 +2884,11 @@ Run_Speedtest_WebUI(){ IFACELIST="$(echo "$IFACELIST" | cut -c2-)" COUNT=1 - for IFACE_NAME in $IFACELIST; do + for IFACE_NAME in $IFACELIST + do spdtestserver="$(grep -m1 "$(echo "$spdtestserverlist" | cut -f"$COUNT" -d'+')" /tmp/spdmerlin_manual_serverlist.txt)" sed -i 's/^PREFERREDSERVER_'"$IFACE_NAME"'=.*$/PREFERREDSERVER_'"$IFACE_NAME"'='"$spdtestserver"'/' "$SCRIPT_CONF" - COUNT=$((COUNT+1)) + COUNT="$((COUNT+1))" done else spdtestserver="$(grep -m1 "$spdtestserverlist" /tmp/spdmerlin_manual_serverlist.txt)" @@ -1845,15 +2897,18 @@ Run_Speedtest_WebUI(){ fi sleep 1 Run_Speedtest "$spdtestmode" "$spdifacename" - cp -a "$SCRIPT_CONF.bak" "$SCRIPT_CONF" + cp -a "${SCRIPT_CONF}.bak" "$SCRIPT_CONF" } ##----------------------------------------## -## Modified by Martinski W. [2024-Jul-15] ## +## Modified by Martinski W. [2025-Feb-28] ## ##----------------------------------------## Process_Upgrade() { - if [ ! -f "$OOKLA_DIR/speedtest" ]; then + local foundError foundLocked resultStr doUpdateDB=false + + if [ ! -f "$OOKLA_DIR/speedtest" ] + then Download_File "$SCRIPT_REPO/files/$ARCH.tar.gz" "$OOKLA_DIR/$ARCH.tar.gz" tar -xzf "$OOKLA_DIR/$ARCH.tar.gz" -C "$OOKLA_DIR" rm -f "$OOKLA_DIR/$ARCH.tar.gz" @@ -1862,158 +2917,264 @@ Process_Upgrade() rm -f "$SCRIPT_STORAGE_DIR/spdjs.js" rm -f "$SCRIPT_STORAGE_DIR/.tableupgraded"* - if [ ! -f "$SCRIPT_STORAGE_DIR/spdtitletext.js" ]; then - echo "Stats last updated: Not yet updated" > /tmp/spdstatstitle.txt - WriteStats_ToJS /tmp/spdstatstitle.txt "$SCRIPT_STORAGE_DIR/spdtitletext.js" SetSPDStatsTitle statstitle - fi if [ ! -f "$SCRIPT_DIR/README.md" ]; then Update_File README.md fi if [ ! -f "$SCRIPT_DIR/LICENSE" ]; then Update_File LICENSE fi - if [ "$(AutoBWEnable check)" = "true" ]; then - if [ "$(ExcludeFromQoS check)" = "false" ]; then + if [ "$(AutoBWEnable check)" = "true" ] + then + if [ "$(ExcludeFromQoS check)" = "false" ] + then Print_Output false "Enabling Exclude from QoS (required for AutoBW)" ExcludeFromQoS enable fi fi - - FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" - for IFACE_NAME in $FULL_IFACELIST; do - echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql + + local prcIndx=0 + for IFACE_NAME in $FULL_IFACELIST + do + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA journal_mode=TRUNCATE;" + echo "CREATE TABLE IF NOT EXISTS [spdstats_$IFACE_NAME] ([StatID] INTEGER PRIMARY KEY NOT NULL,[Timestamp] NUMERIC NOT NULL,[Download] REAL NOT NULL,[Upload] REAL NOT NULL,[Latency] REAL,[Jitter] REAL,[PktLoss] REAL,[ResultURL] TEXT,[DataDownload] REAL NOT NULL,[DataUpload] REAL NOT NULL,[ServerID] TEXT,[ServerName] TEXT);" + } > /tmp/spdstats-upgrade.sql + prcIndx="$((prcIndx + 1))" + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc01$prcIndx" + if ! "$foundError" && ! "$foundLocked" ; then doUpdateDB=true ; fi done - - if [ ! -f "$SCRIPT_STORAGE_DIR/.databaseupgraded" ]; then + + if [ ! -f "$SCRIPT_STORAGE_DIR/.databaseupgraded" ] + then renice 15 $$ Print_Output true "Upgrading database..." "$PASS" - FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" - for IFACE_NAME in $FULL_IFACELIST; do - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_download ON spdstats_${IFACE_NAME} (Timestamp,Download);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_upload ON spdstats_${IFACE_NAME} (Timestamp,Upload);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_latency ON spdstats_${IFACE_NAME} (Timestamp,Latency);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_jitter ON spdstats_${IFACE_NAME} (Timestamp,Jitter);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_pktloss ON spdstats_${IFACE_NAME} (Timestamp,PktLoss);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_resulturl ON spdstats_${IFACE_NAME} (Timestamp,ResultURL collate nocase);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataDownload);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataUpload);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerID] TEXT" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql >/dev/null 2>&1 - echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerName] TEXT" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql >/dev/null 2>&1 - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_serverid ON spdstats_${IFACE_NAME} (Timestamp,ServerID);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - echo "PRAGMA cache_size=-20000; CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_servername ON spdstats_${IFACE_NAME} (Timestamp,ServerName collate nocase);" > /tmp/spdstats-upgrade.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spdstats-upgrade.sql - Generate_LastXResults "$IFACE_NAME" + + prcIndx=0 + for IFACE_NAME in $FULL_IFACELIST + do + prcIndx="$((prcIndx + 1))" + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_download ON spdstats_${IFACE_NAME} (Timestamp,Download);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc02$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_upload ON spdstats_${IFACE_NAME} (Timestamp,Upload);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc03$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_latency ON spdstats_${IFACE_NAME} (Timestamp,Latency);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc04$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_jitter ON spdstats_${IFACE_NAME} (Timestamp,Jitter);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc05$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_pktloss ON spdstats_${IFACE_NAME} (Timestamp,PktLoss);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc06$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_resulturl ON spdstats_${IFACE_NAME} (Timestamp,ResultURL collate nocase);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc07$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataDownload);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc08$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_datadownload ON spdstats_${IFACE_NAME} (Timestamp,DataUpload);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc09$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerID] TEXT" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc10$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "ALTER TABLE spdstats_${IFACE_NAME} ADD COLUMN [ServerName] TEXT" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc11$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_serverid ON spdstats_${IFACE_NAME} (Timestamp,ServerID);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc12$prcIndx" + + { + echo "PRAGMA temp_store=1;" + echo "PRAGMA cache_size=-20000;" + echo "CREATE INDEX IF NOT EXISTS idx_${IFACE_NAME}_servername ON spdstats_${IFACE_NAME} (Timestamp,ServerName collate nocase);" + } > /tmp/spdstats-upgrade.sql + _ApplyDatabaseSQLCmds_ /tmp/spdstats-upgrade.sql "prc13$prcIndx" + + Generate_LastXResults "$IFACE_NAME" "$prcIndx" done + touch "$SCRIPT_STORAGE_DIR/.databaseupgraded" Generate_CSVs Print_Output true "Database ready, continuing..." "$PASS" renice 0 $$ + doUpdateDB=true + fi + + if [ ! -f "$SCRIPT_STORAGE_DIR/spdtitletext.js" ] + then + doUpdateDB=true + echo "Stats last updated: Not yet updated" > /tmp/spdstatstitle.txt + WriteStats_ToJS /tmp/spdstatstitle.txt "$SCRIPT_STORAGE_DIR/spdtitletext.js" SetSPDStatsTitle statstitle fi + rm -f /tmp/spdstats-upgrade.sql + "$doUpdateDB" && _UpdateDatabaseFileSizeInfo_ } +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## #$1 iface name Generate_LastXResults() { - FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" - for IFACE_NAME in $FULL_IFACELIST; do + for IFACE_NAME in $FULL_IFACELIST; + do rm -f "$SCRIPT_STORAGE_DIR/lastx_${IFACE_NAME}.htm" done + + local glxIndx=0 + if [ $# -gt 1 ] && [ -n "$2" ] ; then glxIndx="$2" ; fi + { - echo ".mode csv" - echo ".output /tmp/spd-lastx.csv" - } > /tmp/spd-lastx.sql - echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[DataDownload],[DataUpload],[ResultURL],[ServerID],[ServerName] FROM spdstats_$1 ORDER BY [Timestamp] DESC LIMIT $(LastXResults check);" >> /tmp/spd-lastx.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-lastx.sql - rm -f /tmp/spd-lastx.sql - sed -i 's/,,/,null,/g;s/"//g;' /tmp/spd-lastx.csv - mv /tmp/spd-lastx.csv "$SCRIPT_STORAGE_DIR/lastx_${1}.csv" + echo ".mode csv" + echo ".output /tmp/spdMerlin-lastx.csv" + echo "PRAGMA temp_store=1;" + echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss],[DataDownload],[DataUpload],[ResultURL],[ServerID],[ServerName] FROM spdstats_$1 ORDER BY [Timestamp] DESC LIMIT $(LastXResults check);" + } > /tmp/spdMerlin-lastx.sql + _ApplyDatabaseSQLCmds_ /tmp/spdMerlin-lastx.sql "glx$glxIndx" + + rm -f /tmp/spdMerlin-lastx.sql + sed -i 's/,,/,null,/g;s/"//g;' /tmp/spdMerlin-lastx.csv + mv -f /tmp/spdMerlin-lastx.csv "$SCRIPT_STORAGE_DIR/lastx_${1}.csv" } -Generate_CSVs(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Generate_CSVs() +{ + local gnrIndx1=0 gnrIndx2=0 + Process_Upgrade renice 15 $$ - + OUTPUTTIMEMODE="$(OutputTimeMode check)" STORERESULTURL="$(StoreResultURL check)" IFACELIST="" - + while IFS='' read -r line || [ -n "$line" ]; do IFACELIST="$IFACELIST $(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" - - if [ "$IFACELIST" != "" ]; then - for IFACE_NAME in $IFACELIST; do + + if [ "$IFACELIST" != "" ] + then + gnrIndx1=0 + for IFACE_NAME in $IFACELIST + do + gnrIndx1="$((gnrIndx1 + 1))" IFACE="$(Get_Interface_From_Name "$IFACE_NAME")" - - TZ=$(cat /etc/TZ) + + TZ="$(cat /etc/TZ)" export TZ - - timenow=$(date +"%s") - timenowfriendly=$(date +"%c") - + + timenow="$(date +'%s')" + timenowfriendly="$(date +'%c')" + metriclist="Download Upload Latency Jitter PktLoss" # DataDownload DataUpload" - - for metric in $metriclist; do + gnrIndx2=0 + + for metric in $metriclist + do + gnrIndx2="$((gnrIndx2 + 1))" { - echo ".mode csv" - echo ".headers off" - echo ".output $CSV_OUTPUT_DIR/${metric}_raw_daily_$IFACE_NAME.tmp" - echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-1 day'))) ORDER BY [Timestamp] DESC;" - } > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - + echo ".mode csv" + echo ".headers off" + echo ".output $CSV_OUTPUT_DIR/${metric}_raw_daily_$IFACE_NAME.tmp" + echo "PRAGMA temp_store=1;" + echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-1 day'))) ORDER BY [Timestamp] DESC;" + } > /tmp/spdTest-stats.sql + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr1${gnrIndx1}${gnrIndx2}" + { - echo ".mode csv" - echo ".headers off" - echo ".output $CSV_OUTPUT_DIR/${metric}_raw_weekly_$IFACE_NAME.tmp" - echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-7 day'))) ORDER BY [Timestamp] DESC;" - } > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - + echo ".mode csv" + echo ".headers off" + echo ".output $CSV_OUTPUT_DIR/${metric}_raw_weekly_$IFACE_NAME.tmp" + echo "PRAGMA temp_store=1;" + echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-7 day'))) ORDER BY [Timestamp] DESC;" + } > /tmp/spdTest-stats.sql + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr2${gnrIndx1}${gnrIndx2}" + { - echo ".mode csv" - echo ".headers off" - echo ".output $CSV_OUTPUT_DIR/${metric}_raw_monthly_$IFACE_NAME.tmp" - echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-30 day'))) ORDER BY [Timestamp] DESC;" - } > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 1 "$CSV_OUTPUT_DIR/${metric}_hour" daily "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 7 "$CSV_OUTPUT_DIR/${metric}_hour" weekly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 30 "$CSV_OUTPUT_DIR/${metric}_hour" monthly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 1 "$CSV_OUTPUT_DIR/${metric}_day" daily "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 7 "$CSV_OUTPUT_DIR/${metric}_day" weekly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - - WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 30 "$CSV_OUTPUT_DIR/${metric}_day" monthly "$IFACE_NAME" /tmp/spd-stats.sql "$timenow" - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql + echo ".mode csv" + echo ".headers off" + echo ".output $CSV_OUTPUT_DIR/${metric}_raw_monthly_$IFACE_NAME.tmp" + echo "PRAGMA temp_store=1;" + echo "SELECT '$metric' Metric,[Timestamp] Time,[$metric] Value FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-30 day'))) ORDER BY [Timestamp] DESC;" + } > /tmp/spdTest-stats.sql + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr3${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 1 "$CSV_OUTPUT_DIR/${metric}_hour" daily "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr4${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 7 "$CSV_OUTPUT_DIR/${metric}_hour" weekly "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr5${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 1 30 "$CSV_OUTPUT_DIR/${metric}_hour" monthly "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr6${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 1 "$CSV_OUTPUT_DIR/${metric}_day" daily "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr7${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 7 "$CSV_OUTPUT_DIR/${metric}_day" weekly "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr8${gnrIndx1}${gnrIndx2}" + + WriteSql_ToFile "$metric" "spdstats_$IFACE_NAME" 24 30 "$CSV_OUTPUT_DIR/${metric}_day" monthly "$IFACE_NAME" /tmp/spdTest-stats.sql "$timenow" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "gnr9${gnrIndx1}${gnrIndx2}" done - + periodfilelist="raw hour day" - - for periodfile in $periodfilelist; do + + for periodfile in $periodfilelist + do cat "$CSV_OUTPUT_DIR/Download_${periodfile}_daily_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_daily_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_daily_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Download_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_weekly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_weekly_${IFACE_NAME}.htm" 2> /dev/null cat "$CSV_OUTPUT_DIR/Download_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/Upload_${periodfile}_monthly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/Combined_${periodfile}_monthly_${IFACE_NAME}.htm" 2> /dev/null @@ -2026,33 +3187,36 @@ Generate_CSVs(){ #cat "$CSV_OUTPUT_DIR/DataDownload_${periodfile}_weekly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/DataUpload_${periodfile}_weekly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/DataUsage_${periodfile}_weekly_${IFACE_NAME}.htm" 2> /dev/null #cat "$CSV_OUTPUT_DIR/DataDownload_${periodfile}_monthly_${IFACE_NAME}.tmp" "$CSV_OUTPUT_DIR/DataUpload_${periodfile}_monthly_${IFACE_NAME}.tmp" > "$CSV_OUTPUT_DIR/DataUsage_${periodfile}_monthly_${IFACE_NAME}.htm" 2> /dev/null done - + csvlist="Combined Quality" # DataUsage" - for csvfile in $csvlist; do + for csvfile in $csvlist + do rm -f "$CSV_OUTPUT_DIR/${csvfile}daily_${IFACE_NAME}.htm" rm -f "$CSV_OUTPUT_DIR/${csvfile}weekly_${IFACE_NAME}.htm" rm -f "$CSV_OUTPUT_DIR/${csvfile}monthly_${IFACE_NAME}.htm" - for periodfile in $periodfilelist; do + for periodfile in $periodfilelist + do sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_daily_${IFACE_NAME}.htm" sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_weekly_${IFACE_NAME}.htm" sed -i '1i Metric,Time,Value' "$CSV_OUTPUT_DIR/${csvfile}_${periodfile}_monthly_${IFACE_NAME}.htm" done done - + INCLUDEURL="" if [ "$STORERESULTURL" = "true" ]; then INCLUDEURL=",[ResultURL]" fi - + { - echo ".mode csv" - echo ".headers on" - echo ".output $CSV_OUTPUT_DIR/CompleteResults_${IFACE_NAME}.htm" - echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss]$INCLUDEURL,[DataDownload],[DataUpload],[ServerID],[ServerName] FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$(DaysToKeep check) day'))) ORDER BY [Timestamp] DESC;" + echo ".mode csv" + echo ".headers on" + echo ".output $CSV_OUTPUT_DIR/CompleteResults_${IFACE_NAME}.htm" + echo "PRAGMA temp_store=1;" + echo "SELECT [Timestamp],[Download],[Upload],[Latency],[Jitter],[PktLoss]$INCLUDEURL,[DataDownload],[DataUpload],[ServerID],[ServerName] FROM spdstats_$IFACE_NAME WHERE ([Timestamp] >= strftime('%s',datetime($timenow,'unixepoch','-$(DaysToKeep check) day'))) ORDER BY [Timestamp] DESC;" } > /tmp/spd-complete.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-complete.sql + _ApplyDatabaseSQLCmds_ /tmp/spd-complete.sql "gnr10$gnrIndx1" + rm -f /tmp/spd-complete.sql - rm -f "$CSV_OUTPUT_DIR/Download"* rm -f "$CSV_OUTPUT_DIR/Upload"* rm -f "$CSV_OUTPUT_DIR/Latency"* @@ -2060,65 +3224,73 @@ Generate_CSVs(){ rm -f "$CSV_OUTPUT_DIR/PktLoss"* rm -f "$CSV_OUTPUT_DIR/DataDownload"* rm -f "$CSV_OUTPUT_DIR/DataUpload"* - - Generate_LastXResults "$IFACE_NAME" - rm -f /tmp/spd-stats.sql + + Generate_LastXResults "$IFACE_NAME" "$gnrIndx1" + rm -f /tmp/spdTest-stats.sql done - + dos2unix "$CSV_OUTPUT_DIR/"*.htm - + tmpoutputdir="/tmp/${SCRIPT_NAME_LOWER}results" mkdir -p "$tmpoutputdir" - mv "$CSV_OUTPUT_DIR/CompleteResults"*.htm "$tmpoutputdir/." - - if [ "$OUTPUTTIMEMODE" = "unix" ]; then + mv -f "$CSV_OUTPUT_DIR/CompleteResults"*.htm "$tmpoutputdir/." + + if [ "$OUTPUTTIMEMODE" = "unix" ] + then find "$tmpoutputdir/" -name '*.htm' -exec sh -c 'i="$1"; mv -- "$i" "${i%.htm}.csv"' _ {} \; - elif [ "$OUTPUTTIMEMODE" = "non-unix" ]; then + elif [ "$OUTPUTTIMEMODE" = "non-unix" ] + then for i in "$tmpoutputdir/"*".htm"; do awk -F"," 'NR==1 {OFS=","; print} NR>1 {OFS=","; $1=strftime("%Y-%m-%d %H:%M:%S", $1); print }' "$i" > "$i.out" done - + find "$tmpoutputdir/" -name '*.htm.out' -exec sh -c 'i="$1"; mv -- "$i" "${i%.htm.out}.csv"' _ {} \; rm -f "$tmpoutputdir/"*.htm fi - - if [ ! -f /opt/bin/7za ]; then + + if [ ! -f /opt/bin/7za ] && [ -x /opt/bin/opkg ] + then opkg update opkg install p7zip fi /opt/bin/7za a -y -bsp0 -bso0 -tzip "/tmp/${SCRIPT_NAME_LOWER}data.zip" "$tmpoutputdir/*" - mv "/tmp/${SCRIPT_NAME_LOWER}data.zip" "$CSV_OUTPUT_DIR" + mv -f "/tmp/${SCRIPT_NAME_LOWER}data.zip" "$CSV_OUTPUT_DIR" rm -rf "$tmpoutputdir" fi renice 0 $$ } ##----------------------------------------## -## Modified by Martinski W. [2024-Jul-15] ## +## Modified by Martinski W. [2025-Feb-28] ## ##----------------------------------------## -# [Ported code over from "connmon" script] # Reset_DB() { - SIZEAVAIL="$(df -P -k "$SCRIPT_STORAGE_DIR" | awk '{print $4}' | tail -n 1)" - SIZEDB="$(ls -l "$SCRIPT_STORAGE_DIR/spdstats.db" | awk '{print $5}')" + SIZEAVAIL="$(df -kP "$SCRIPT_STORAGE_DIR" | awk -F ' ' '{print $4}' | tail -n 1)" + SIZEDB="$(ls -l "$SPEEDSTATS_DB" | awk -F ' ' '{print $5}')" SIZEAVAIL="$(echo "$SIZEAVAIL" | awk '{printf("%s", $1 * 1024);}')" if [ "$(echo "$SIZEAVAIL $SIZEDB" | awk -F ' ' '{print ($1 < $2)}')" -eq 1 ] then - Print_Output true "Database size exceeds available space. $(ls -lh "$SCRIPT_STORAGE_DIR/spdstats.db" | awk '{print $5}')B is required to create backup." "$ERR" + Print_Output true "Database size exceeds available space. $(ls -lh "$SPEEDSTATS_DB" | awk '{print $5}')B is required to create backup." "$ERR" return 1 else Print_Output true "Sufficient free space to back up database, proceeding..." "$PASS" - if ! cp -a "$SCRIPT_STORAGE_DIR/spdstats.db" "$SCRIPT_STORAGE_DIR/spdstats.db.bak"; then + if ! cp -a "$SPEEDSTATS_DB" "${SPEEDSTATS_DB}.bak"; then Print_Output true "Database backup failed, please check storage device" "$WARN" fi Print_Output false "Please wait..." "$PASS" - tablelist="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 WGVPN4 WGVPN5" - for dbtable in $tablelist; do - echo "DELETE FROM [spdstats_$dbtable];" > /tmp/spd-stats.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-stats.sql - rm -f /tmp/spd-stats.sql + + local rstIndx=0 + for dbtable in $FULL_IFACELIST + do + { + echo "PRAGMA temp_store=1;" + echo "DELETE FROM [spdstats_$dbtable];" + } > /tmp/spdTest-reset.sql + rstIndx="$((rstIndx + 1))" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-reset.sql "rst${rstIndx}" + rm -f /tmp/spdTest-reset.sql done ## Clear/Reset all CSV files ## @@ -2134,6 +3306,7 @@ Reset_DB() Print_Output true "Database reset complete" "$WARN" { sleep 4 + _UpdateDatabaseFileSizeInfo_ timeDateNow="$(/bin/date +"%c")" extraJScode='databaseResetDone = 0;' echo "Stats were reset: $timeDateNow" > /tmp/spdstatstitle.txt @@ -2143,10 +3316,13 @@ Reset_DB() fi } -Shortcut_Script(){ +Shortcut_Script() +{ case $1 in create) - if [ -d "/opt/bin" ] && [ ! -f "/opt/bin/$SCRIPT_NAME_LOWER" ] && [ -f "/jffs/scripts/$SCRIPT_NAME_LOWER" ]; then + if [ -d /opt/bin ] && [ ! -f "/opt/bin/$SCRIPT_NAME_LOWER" ] && \ + [ -f "/jffs/scripts/$SCRIPT_NAME_LOWER" ] + then ln -s "/jffs/scripts/$SCRIPT_NAME_LOWER" /opt/bin chmod 0755 "/opt/bin/$SCRIPT_NAME_LOWER" fi @@ -2159,125 +3335,257 @@ Shortcut_Script(){ esac } -PressEnter(){ - while true; do - printf "Press enter to continue..." - read -r key +PressEnter() +{ + while true + do + printf "Press key to continue..." + read -rs key case "$key" in - *) - break - ;; + *) break ;; esac done return 0 } -ScriptHeader(){ +ScriptHeader() +{ clear - printf "\\n" - printf "${BOLD}####################################################################${CLEARFORMAT}\\n" - printf "${BOLD}## _ __ __ _ _ ##${CLEARFORMAT}\\n" - printf "${BOLD}## | || \/ | | |(_) ##${CLEARFORMAT}\\n" - printf "${BOLD}## ___ _ __ __| || \ / | ___ _ __ | | _ _ __ ##${CLEARFORMAT}\\n" - printf "${BOLD}## / __|| '_ \ / _ || |\/| | / _ \| '__|| || || '_ \ ##${CLEARFORMAT}\\n" - printf "${BOLD}## \__ \| |_) || (_| || | | || __/| | | || || | | | ##${CLEARFORMAT}\\n" - printf "${BOLD}## |___/| .__/ \__,_||_| |_| \___||_| |_||_||_| |_| ##${CLEARFORMAT}\\n" - printf "${BOLD}## | | ##${CLEARFORMAT}\\n" - printf "${BOLD}## |_| ##${CLEARFORMAT}\\n" - printf "${BOLD}## ##${CLEARFORMAT}\\n" - printf "${BOLD}## %s on %-11s ##${CLEARFORMAT}\\n" "$SCRIPT_VERSION" "$ROUTER_MODEL" - printf "${BOLD}## ##${CLEARFORMAT}\\n" - printf "${BOLD}## https://github.com/jackyaz/spdMerlin ##${CLEARFORMAT}\\n" - printf "${BOLD}## ##${CLEARFORMAT}\\n" - printf "${BOLD}####################################################################${CLEARFORMAT}\\n" + printf "${BOLD}################################################################${CLEARFORMAT}\\n" + printf "${BOLD}## _ __ __ _ _ ##${CLEARFORMAT}\\n" + printf "${BOLD}## | || \/ | | |(_) ##${CLEARFORMAT}\\n" + printf "${BOLD}## ___ _ __ __| || \ / | ___ _ __ | | _ _ __ ##${CLEARFORMAT}\\n" + printf "${BOLD}## / __|| '_ \ / _ || |\/| | / _ \| '__|| || || '_ \ ##${CLEARFORMAT}\\n" + printf "${BOLD}## \__ \| |_) || (_| || | | || __/| | | || || | | | ##${CLEARFORMAT}\\n" + printf "${BOLD}## |___/| .__/ \__,_||_| |_| \___||_| |_||_||_| |_| ##${CLEARFORMAT}\\n" + printf "${BOLD}## | | ##${CLEARFORMAT}\\n" + printf "${BOLD}## |_| ##${CLEARFORMAT}\\n" + printf "${BOLD}## ##${CLEARFORMAT}\\n" + printf "${BOLD}## %9s on %-18s ##${CLEARFORMAT}\n" "$SCRIPT_VERSION" "$ROUTER_MODEL" + printf "${BOLD}## ##${CLEARFORMAT}\\n" + printf "${BOLD}## https://github.com/jackyaz/spdMerlin ##${CLEARFORMAT}\\n" + printf "${BOLD}## ##${CLEARFORMAT}\\n" + printf "${BOLD}################################################################${CLEARFORMAT}\\n" printf "\\n" } -MainMenu(){ - AUTOMATIC_ENABLED="" - EXCLUDEFROMQOS_MENU="" - if AutomaticMode check; then AUTOMATIC_ENABLED="${PASS}Enabled"; else AUTOMATIC_ENABLED="${ERR}Disabled"; fi - TEST_SCHEDULE="$(TestSchedule check)" - if [ "$(echo "$TEST_SCHEDULE" | cut -f2 -d'|' | grep -c "/")" -gt 0 ] && [ "$(echo "$TEST_SCHEDULE" | cut -f3 -d'|')" -eq 0 ]; then - TEST_SCHEDULE_MENU="Every $(echo "$TEST_SCHEDULE" | cut -f2 -d'|' | cut -f2 -d'/') hours" - elif [ "$(echo "$TEST_SCHEDULE" | cut -f3 -d'|' | grep -c "/")" -gt 0 ] && [ "$(echo "$TEST_SCHEDULE" | cut -f2 -d'|')" = "*" ]; then - TEST_SCHEDULE_MENU="Every $(echo "$TEST_SCHEDULE" | cut -f3 -d'|' | cut -f2 -d'/') minutes" - else - TEST_SCHEDULE_MENU="Hours: $(echo "$TEST_SCHEDULE" | cut -f2 -d'|') - Minutes: $(echo "$TEST_SCHEDULE" | cut -f3 -d'|')" +##-------------------------------------## +## Added by Martinski W. [2025-Jan-19] ## +##-------------------------------------## +_CronScheduleHourMinsInfo_() +{ + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] + then echo ; return 1 ; fi + local schedHour="$1" schedMins="$2" schedInfoStr + local freqHourNum freqMinsNum hasFreqHour hasFreqMins + + _IsValidNumber_() + { + if echo "$1" | grep -qE "^[0-9]+$" + then return 0 ; else return 1 ; fi + } + + _Get12HourAmPm_() + { + if [ $# -eq 0 ] || [ -z "$1" ] + then echo ; return 1 ; fi + local theHour theMins="" ampmTag="AM" + theHour="$1" + if [ $# -eq 2 ] && [ -n "$2" ] + then theMins="$2" + fi + if [ "$theHour" -eq 0 ] + then theHour=12 + elif [ "$theHour" -eq 12 ] + then ampmTag="PM" + elif [ "$theHour" -gt 12 ] + then + ampmTag="PM" ; theHour="$((theHour - 12))" + fi + if [ -z "$theMins" ] + then printf "%d $ampmTag" "$theHour" + else printf "%d:%02d $ampmTag" "$theHour" "$theMins" + fi + } + + if echo "$schedHour" | grep -qE "^[*]/.*" + then + hasFreqHour=true + freqHourNum="$(echo "$schedHour" | cut -f2 -d'/')" + else + hasFreqHour=false ; freqHourNum="" + fi + if echo "$schedMins" | grep -qE "^[*]/.*" + then + hasFreqMins=true + freqMinsNum="$(echo "$schedMins" | cut -f2 -d'/')" + else + hasFreqMins=false ; freqMinsNum="" + fi + if [ "$schedHour" = "*" ] && [ "$schedMins" = "0" ] + then + schedInfoStr="Every hour" + elif [ "$schedHour" = "*" ] && [ "$schedMins" = "*" ] + then + schedInfoStr="Every minute" + elif [ "$schedHour" = "*" ] && _IsValidNumber_ "$schedMins" + then + schedInfoStr="Every hour at minute $schedMins" + elif "$hasFreqHour" && [ "$schedMins" = "0" ] + then + schedInfoStr="Every $freqHourNum hours" + elif "$hasFreqHour" && [ "$schedMins" = "*" ] + then + schedInfoStr="Every minute, every $freqHourNum hours" + elif "$hasFreqHour" && _IsValidNumber_ "$schedMins" + then + schedInfoStr="Every $freqHourNum hours at minute $schedMins" + elif "$hasFreqMins" && [ "$schedHour" = "*" ] + then + schedInfoStr="Every $freqMinsNum minutes" + elif "$hasFreqHour" && "$hasFreqMins" + then + schedInfoStr="Every $freqMinsNum minutes, every $freqHourNum hours" + elif "$hasFreqMins" && _IsValidNumber_ "$schedHour" + then + schedInfoStr="Hour: $(_Get12HourAmPm_ "$schedHour"), every $freqMinsNum minutes" + elif _IsValidNumber_ "$schedHour" && _IsValidNumber_ "$schedMins" + then + schedInfoStr="Hour: $(_Get12HourAmPm_ "$schedHour" "$schedMins")" + elif "$hasFreqHour" + then + schedInfoStr="Every $freqHourNum hours, Minutes: $schedMins" + elif "$hasFreqMins" + then + schedInfoStr="Hours: ${schedHour}; every $freqMinsNum minutes" + elif [ "$schedHour" = "*" ] + then + schedInfoStr="Every hour, Minutes: $schedMins" + elif [ "$schedMins" = "*" ] + then + schedInfoStr="Hours: ${schedHour}; every minute" + else + schedInfoStr="Hours: ${schedHour}; Minutes: $schedMins" + fi + echo "$schedInfoStr" +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +MainMenu() +{ + local menuOption storageLocStr automaticModeStatus + local jffsFreeSpace jffsFreeSpaceStr jffsSpaceMsgTag + + if [ "$(ExcludeFromQoS check)" = "true" ] + then EXCLUDEFROMQOS_MENU="excluded from" + else EXCLUDEFROMQOS_MENU="included in" + fi + + if AutomaticMode check + then automaticModeStatus="${PassBGRNct} ENABLED ${CLRct}" + else automaticModeStatus="${CritIREDct} DISABLED ${CLRct}" fi - - if [ "$(echo "$TEST_SCHEDULE" | cut -f1 -d'|')" = "*" ]; then - TEST_SCHEDULE_MENU2="Days of week: All" - else - TEST_SCHEDULE_MENU2="Days of week: $(echo "$TEST_SCHEDULE" | cut -f1 -d'|')" + + TEST_SCHEDULE="$(CronTestSchedule check)" + CRON_SCHED_DAYS="$(echo "$TEST_SCHEDULE" | cut -f1 -d'|')" + CRON_SCHED_HOUR="$(echo "$TEST_SCHEDULE" | cut -f2 -d'|')" + CRON_SCHED_MINS="$(echo "$TEST_SCHEDULE" | cut -f3 -d'|')" + if [ "$CRON_SCHED_DAYS" = "*" ] + then TEST_SCHEDULE_DAYS="Every day" + else TEST_SCHEDULE_DAYS="Days of Week: $CRON_SCHED_DAYS" fi - STORERESULTURL_MENU="" - if [ "$(StoreResultURL check)" = "true" ]; then - STORERESULTURL_MENU="Enabled" + TEST_SCHEDULE_MENU="$(_CronScheduleHourMinsInfo_ "$CRON_SCHED_HOUR" "$CRON_SCHED_MINS")" + + if [ "$(StoreResultURL check)" = "true" ] + then STORERESULTURL_MENU="ENABLED" + else STORERESULTURL_MENU="DISABLED" + fi + + storageLocStr="$(ScriptStorageLocation check | tr 'a-z' 'A-Z')" + + jffsFreeSpace="$(_Get_JFFS_Space_ FREE HRx | sed 's/%/%%/')" + if ! echo "$JFFS_LowFreeSpaceStatus" | grep -E "^WARNING[0-9]$" + then + jffsFreeSpaceStr="${SETTING}$jffsFreeSpace" else - STORERESULTURL_MENU="Disabled" + if [ "$storageLocStr" = "JFFS" ] + then jffsSpaceMsgTag="${CritBREDct} <<< WARNING! " + else jffsSpaceMsgTag="${WarnBMGNct} <<< NOTICE! " + fi + jffsFreeSpaceStr="${WarnBYLWct} $jffsFreeSpace ${CLRct} ${jffsSpaceMsgTag}${CLRct}" fi + + printf "WebUI for %s is available at:\n${SETTING}%s${CLEARFORMAT}\n\n" "$SCRIPT_NAME" "$(Get_WebUI_URL)" + + printf "1. Run a speedtest now\n" + printf " Database size: ${SETTING}%s${CLEARFORMAT}\n\n" "$(_GetFileSize_ "$SPEEDSTATS_DB" HRx)" + printf "2. Choose a preferred server for an interface\n\n" + printf "3. Toggle automatic speedtests\n" + printf " Currently: ${automaticModeStatus}${CLEARFORMAT}\n\n" + printf "4. Configure schedule for automatic speedtests\n" + printf " Currently: ${SETTING}%s - %s${CLEARFORMAT}\n\n" "$TEST_SCHEDULE_MENU" "$TEST_SCHEDULE_DAYS" + printf "5. Toggle time output mode\n" + printf " Currently: ${SETTING}%s${CLEARFORMAT} time values will be used for CSV exports\n\n" "$(OutputTimeMode check)" + printf "6. Toggle storage of speedtest result URLs\n" + printf " Currently: ${SETTING}%s${CLEARFORMAT}\n\n" "$STORERESULTURL_MENU" + printf "7. Set number of speedtest results to show in WebUI\n" + printf " Currently: ${SETTING}%s results will be shown${CLEARFORMAT}\n\n" "$(LastXResults check)" + printf "8. Set number of days data to keep in database\n" + printf " Currently: ${SETTING}%s days data will be kept${CLEARFORMAT}\n\n" "$(DaysToKeep check)" + printf "9. Toggle between built-in Ookla speedtest and speedtest-cli\n" + printf " Currently: ${SETTING}%s${CLEARFORMAT} binary will be used for speedtests${CLEARFORMAT}\n\n" "$(SpeedtestBinary check)" + printf "c. Customise list of interfaces for automatic speedtests\n" + printf "r. Reset list of interfaces for automatic speedtests to default\n\n" + printf "s. Toggle storage location for stats and config\n" + printf " Current location: ${SETTING}%s${CLEARFORMAT}\n" "$storageLocStr" + printf " JFFS Available: ${jffsFreeSpaceStr}${CLEARFORMAT}\n\n" + printf "q. Toggle exclusion of %s speedtests from QoS\n" "$SCRIPT_NAME" + printf " Currently: %s speedtests are ${SETTING}%s${CLEARFORMAT} QoS\n\n" "$SCRIPT_NAME" "$EXCLUDEFROMQOS_MENU" + printf "a. AutoBW\n\n" + printf "u. Check for updates\n" + printf "uf. Update %s with latest version (force update)\n\n" "$SCRIPT_NAME" + printf "rs. Reset %s database / delete all data\n\n" "$SCRIPT_NAME" + printf "e. Exit %s\n\n" "$SCRIPT_NAME" + printf "z. Uninstall %s\n" "$SCRIPT_NAME" + printf "\n" + printf "${BOLD}####################################################################${CLEARFORMAT}\n" + printf "\n" - printf "WebUI for %s is available at:\\n${SETTING}%s${CLEARFORMAT}\\n\\n" "$SCRIPT_NAME" "$(Get_WebUI_URL)" - if [ "$(ExcludeFromQoS check)" = "true" ]; then EXCLUDEFROMQOS_MENU="excluded from"; else EXCLUDEFROMQOS_MENU="included in"; fi - - printf "1. Run a speedtest now\\n\\n" - printf "2. Choose a preferred server for an interface\\n\\n" - printf "3. Toggle automatic speedtests\\n Currently: ${BOLD}${AUTOMATIC_ENABLED}%s${CLEARFORMAT}\\n\\n" - printf "4. Configure schedule for automatic speedtests\\n ${SETTING}%s\\n %s${CLEARFORMAT}\\n\\n" "$TEST_SCHEDULE_MENU" "$TEST_SCHEDULE_MENU2" - printf "5. Toggle time output mode\\n Currently ${SETTING}%s${CLEARFORMAT} time values will be used for CSV exports\\n\\n" "$(OutputTimeMode check)" - printf "6. Toggle storage of speedtest result URLs\\n Currently: ${SETTING}%s${CLEARFORMAT}\\n\\n" "$STORERESULTURL_MENU" - printf "7. Set number of speedtest results to show in WebUI\\n Currently: ${SETTING}%s results will be shown${CLEARFORMAT}\\n\\n" "$(LastXResults check)" - printf "8. Set number of days data to keep in database\\n Currently: ${SETTING}%s days data will be kept${CLEARFORMAT}\\n\\n" "$(DaysToKeep check)" - printf "9. Toggle between built-in Ookla speedtest and speedtest-cli\\n Currently: ${SETTING}%s${CLEARFORMAT} will be used for speedtests${CLEARFORMAT}\\n\\n" "$(SpeedtestBinary check)" - printf "c. Customise list of interfaces for automatic speedtests\\n" - printf "r. Reset list of interfaces for automatic speedtests to default\\n\\n" - printf "s. Toggle storage location for stats and config\\n Current location is ${SETTING}%s${CLEARFORMAT} \\n\\n" "$(ScriptStorageLocation check)" - printf "q. Toggle exclusion of %s speedtests from QoS\\n Currently %s speedtests are ${SETTING}%s${CLEARFORMAT} QoS\\n\\n" "$SCRIPT_NAME" "$SCRIPT_NAME" "$EXCLUDEFROMQOS_MENU" - printf "a. AutoBW\\n\\n" - printf "u. Check for updates\\n" - printf "uf. Update %s with latest version (force update)\\n\\n" "$SCRIPT_NAME" - printf "rs. Reset %s database / delete all data\\n\\n" "$SCRIPT_NAME" - printf "e. Exit %s\\n\\n" "$SCRIPT_NAME" - printf "z. Uninstall %s\\n" "$SCRIPT_NAME" - printf "\\n" - printf "${BOLD}####################################################################${CLEARFORMAT}\\n" - printf "\\n" - - while true; do + while true + do printf "Choose an option: " - read -r menu - case "$menu" in + read -r menuOption + case "$menuOption" in 1) - printf "\\n" - Menu_RunSpeedtest - PressEnter + printf "\n" + Menu_RunSpeedtest && PressEnter break ;; 2) - printf "\\n" - Menu_ConfigurePreferred - PressEnter + printf "\n" + Menu_ConfigurePreferred && PressEnter break ;; 3) - printf "\\n" - if AutomaticMode check; then - AutomaticMode disable - else - AutomaticMode enable + printf "\n" + if AutomaticMode check + then AutomaticMode disable + else AutomaticMode enable fi + PressEnter break ;; 4) - printf "\\n" + printf "\n" Menu_EditSchedule PressEnter break ;; 5) - printf "\\n" + printf "\n" if [ "$(OutputTimeMode check)" = "unix" ]; then OutputTimeMode non-unix elif [ "$(OutputTimeMode check)" = "non-unix" ]; then @@ -2286,7 +3594,7 @@ MainMenu(){ break ;; 6) - printf "\\n" + printf "\n" if [ "$(StoreResultURL check)" = "true" ]; then StoreResultURL disable elif [ "$(StoreResultURL check)" = "false" ]; then @@ -2295,19 +3603,17 @@ MainMenu(){ break ;; 7) - printf "\\n" - LastXResults update - PressEnter + printf "\n" + LastXResults update && PressEnter break ;; 8) - printf "\\n" - DaysToKeep update - PressEnter + printf "\n" + DaysToKeep update && PressEnter break ;; 9) - printf "\\n" + printf "\n" if [ "$(SpeedtestBinary check)" = "builtin" ]; then SpeedtestBinary external elif [ "$(SpeedtestBinary check)" = "external" ]; then @@ -2317,39 +3623,53 @@ MainMenu(){ ;; c) Generate_Interface_List - printf "\\n" + printf "\n" Create_Symlinks - printf "\\n" - PressEnter + printf "\n" break ;; r) Create_Symlinks force - printf "\\n" + printf "\n" PressEnter break ;; s) - printf "\\n" - if [ "$(ScriptStorageLocation check)" = "jffs" ]; then - ScriptStorageLocation usb - Create_Symlinks - elif [ "$(ScriptStorageLocation check)" = "usb" ]; then - ScriptStorageLocation jffs + printf "\n" + if Check_Lock menu + then + if [ "$(ScriptStorageLocation check)" = "jffs" ] + then + ScriptStorageLocation usb + elif [ "$(ScriptStorageLocation check)" = "usb" ] + then + if ! _Check_JFFS_SpaceAvailable_ "$SCRIPT_STORAGE_DIR" + then + Clear_Lock + PressEnter + break + fi + ScriptStorageLocation jffs + fi Create_Symlinks + Clear_Lock fi break ;; q) - printf "\\n" - if [ "$(ExcludeFromQoS check)" = "true" ]; then - if [ "$(AutoBWEnable check)" = "true" ]; then + printf "\n" + if [ "$(ExcludeFromQoS check)" = "true" ] + then + if [ "$(AutoBWEnable check)" = "true" ] + then Print_Output false "Cannot disable Exclude from QoS when AutoBW is enabled" "$WARN" PressEnter - elif [ "$(AutoBWEnable check)" = "false" ]; then + elif [ "$(AutoBWEnable check)" = "false" ] + then ExcludeFromQoS disable fi - elif [ "$(ExcludeFromQoS check)" = "false" ]; then + elif [ "$(ExcludeFromQoS check)" = "false" ] + then ExcludeFromQoS enable fi break @@ -2360,7 +3680,7 @@ MainMenu(){ break ;; u) - printf "\\n" + printf "\n" if Check_Lock menu; then Update_Version Clear_Lock @@ -2369,7 +3689,7 @@ MainMenu(){ break ;; uf) - printf "\\n" + printf "\n" if Check_Lock menu; then Update_Version force Clear_Lock @@ -2378,7 +3698,7 @@ MainMenu(){ break ;; rs) - printf "\\n" + printf "\n" if Check_Lock menu; then Menu_ResetDB Clear_Lock @@ -2388,12 +3708,13 @@ MainMenu(){ ;; e) ScriptHeader - printf "\\n${BOLD}Thanks for using %s!${CLEARFORMAT}\\n\\n\\n" "$SCRIPT_NAME" + printf "\n${BOLD}Thanks for using %s!${CLEARFORMAT}\n\n\n" "$SCRIPT_NAME" exit 0 ;; z) - while true; do - printf "\\n${BOLD}Are you sure you want to uninstall %s? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" + while true + do + printf "\n${BOLD}Are you sure you want to uninstall %s? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) @@ -2407,41 +3728,51 @@ MainMenu(){ done ;; *) - printf "\\nPlease choose a valid option\\n\\n" + [ -n "$menuOption" ] && \ + printf "\n${REDct}INVALID input [$menuOption]${CLEARFORMAT}" + printf "\nPlease choose a valid option.\n\n" + PressEnter + break ;; esac done - + ScriptHeader MainMenu } -Check_Requirements(){ +Check_Requirements() +{ CHECKSFAILED="false" - - if [ "$(nvram get jffs2_scripts)" -ne 1 ]; then + + if [ "$(nvram get jffs2_scripts)" -ne 1 ] + then nvram set jffs2_scripts=1 nvram commit Print_Output true "Custom JFFS Scripts enabled" "$WARN" fi - - if ! Check_Swap; then + + if ! Check_Swap + then Print_Output false "No Swap file detected!" "$ERR" CHECKSFAILED="true" fi - - if [ ! -f /opt/bin/opkg ]; then - Print_Output false "Entware not detected!" "$ERR" + + if [ ! -f /opt/bin/opkg ] + then + Print_Output false "Entware NOT detected!" "$CRIT" CHECKSFAILED="true" fi - - if ! Firmware_Version_Check; then - Print_Output false "Unsupported firmware version detected" "$ERR" + + if ! Firmware_Version_Check + then + Print_Output false "Unsupported firmware version detected" "$CRIT" Print_Output false "$SCRIPT_NAME requires Merlin 384.15/384.13_4 or Fork 43E5 (or later)" "$ERR" CHECKSFAILED="true" fi - - if [ "$CHECKSFAILED" = "false" ]; then + + if [ "$CHECKSFAILED" = "false" ] + then Print_Output false "Installing required packages from Entware" "$PASS" opkg update opkg install sqlite3-cli @@ -2454,14 +3785,18 @@ Check_Requirements(){ fi } -Menu_Install(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Menu_Install() +{ ScriptHeader - Print_Output true "Welcome to $SCRIPT_NAME $SCRIPT_VERSION, a script by JackYaz" + Print_Output true "Welcome to $SCRIPT_NAME $SCRIPT_VERSION, a script by JackYaz" "$PASS" sleep 1 - + Print_Output true "By installing $SCRIPT_NAME you are agreeing to Ookla's license: $SCRIPT_REPO/files/speedtest-cli-license" "$WARN" - - printf "\\n${BOLD}Do you wish to continue? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" + + printf "\n${BOLD}Do you wish to continue? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) @@ -2474,17 +3809,18 @@ Menu_Install(){ exit 1 ;; esac - - Print_Output true "Checking your router meets the requirements for $SCRIPT_NAME" - - if ! Check_Requirements; then + + Print_Output true "Checking if your router meets the requirements for $SCRIPT_NAME" "$PASS" + + if ! Check_Requirements + then Print_Output true "Requirements for $SCRIPT_NAME not met, please see above for the reason(s)" "$CRIT" PressEnter Clear_Lock rm -f "/jffs/scripts/$SCRIPT_NAME_LOWER" 2>/dev/null exit 1 fi - + Create_Dirs Conf_Exists if [ "$(SpeedtestBinary check)" = "builtin" ]; then @@ -2496,48 +3832,55 @@ Menu_Install(){ Set_Version_Custom_Settings server "$SCRIPT_VERSION" ScriptStorageLocation load Create_Symlinks - + Download_File "$SCRIPT_REPO/files/$ARCH.tar.gz" "$OOKLA_DIR/$ARCH.tar.gz" tar -xzf "$OOKLA_DIR/$ARCH.tar.gz" -C "$OOKLA_DIR" rm -f "$OOKLA_DIR/$ARCH.tar.gz" chmod 0755 "$OOKLA_DIR/speedtest" - + Update_File README.md Update_File spdstats_www.asp Update_File shared-jy.tar.gz - + Auto_Startup create 2>/dev/null - Auto_Cron create 2>/dev/null + Auto_Cron delete 2>/dev/null + AutomaticMode check && Auto_Cron create 2>/dev/null Auto_ServiceEvent create 2>/dev/null Shortcut_Script create - + Process_Upgrade - + Run_Speedtest auto WAN - + Clear_Lock - + Download_File "$SCRIPT_REPO/install-success/LICENSE" "$SCRIPT_DIR/LICENSE" - + ScriptHeader MainMenu } -Menu_Startup(){ - if [ -z "$1" ]; then - Print_Output true "Missing argument for startup, not starting $SCRIPT_NAME" "$WARN" +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-04] ## +##----------------------------------------## +Menu_Startup() +{ + if [ $# -eq 0 ] || [ -z "$1" ] + then + Print_Output true "Missing argument for startup, not starting $SCRIPT_NAME" "$ERR" exit 1 - elif [ "$1" != "force" ]; then - if [ ! -f "$1/entware/bin/opkg" ]; then - Print_Output true "$1 does not contain Entware, not starting $SCRIPT_NAME" "$WARN" + elif [ "$1" != "force" ] + then + if [ ! -x "${1}/entware/bin/opkg" ] + then + Print_Output true "$1 does NOT contain Entware, not starting $SCRIPT_NAME" "$CRIT" exit 1 else - Print_Output true "$1 contains Entware, starting $SCRIPT_NAME" "$WARN" + Print_Output true "$1 contains Entware, starting $SCRIPT_NAME" "$PASS" fi fi - + NTP_Ready - Check_Lock if [ "$1" != "force" ]; then @@ -2554,45 +3897,58 @@ Menu_Startup(){ ScriptStorageLocation load Create_Symlinks Auto_Startup create 2>/dev/null - if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi + if AutomaticMode check + then Auto_Cron create 2>/dev/null + else Auto_Cron delete 2>/dev/null + fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create Mount_WebUI - Clear_Lock } -Menu_RunSpeedtest(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-03] ## +##----------------------------------------## +Menu_RunSpeedtest() +{ exitmenu="" validselection="" useiface="" usepreferred="" ScriptHeader - while true; do - printf "Choose an interface to speedtest:\\n\\n" - printf "1. All\\n" + while true + do + printf "Choose an interface to speedtest:\n\n" + printf "1. All\n" COUNTER="2" - while IFS='' read -r line || [ -n "$line" ]; do - if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then - printf "%s. %s\\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" - COUNTER=$((COUNTER+1)) + while IFS='' read -r line || [ -n "$line" ] + do + if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ] + then + printf "%s. %s\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" + COUNTER="$((COUNTER+1))" fi done < "$SCRIPT_INTERFACES_USER" - printf "\\nChoose an option: " + printf "\nChoose an option (e=Exit): " read -r iface_choice - - if [ "$iface_choice" = "e" ]; then + + if [ "$iface_choice" = "e" ] + then exitmenu="exit" break - elif ! Validate_Number "$iface_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$((COUNTER-1))" + elif ! Validate_Number "$iface_choice" + then + printf "\n${ERR}Please enter a valid number [1-%s].${CLEARFORMAT}\n" "$((COUNTER-1))" validselection="false" else - if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ]; then - printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$((COUNTER-1))" + if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ] + then + printf "\n${ERR}Please enter a number between 1 and %s.${CLEARFORMAT}\n" "$((COUNTER-1))" validselection="false" else - if [ "$iface_choice" -gt "1" ]; then + if [ "$iface_choice" -gt "1" ] + then useiface="$(grep -v "interface not up" "$SCRIPT_INTERFACES_USER" | sed -n $((iface_choice-1))p | cut -f1 -d"#" | sed 's/ *$//')" else useiface="All" @@ -2600,48 +3956,46 @@ Menu_RunSpeedtest(){ validselection="true" fi fi - - printf "\\n" - - if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ]; then - while true; do - printf "What mode would you like to use?\\n\\n" - printf "1. Auto-select\\n" - printf "2. Preferred server\\n" - printf "3. Choose a server\\n" - printf "\\nChoose an option: " + printf "\n" + + if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ] + then + while true + do + printf "What mode would you like to use?\n\n" + printf "1. Auto-select\n" + printf "2. Preferred server\n" + printf "3. Choose a server\n" + printf "\nChoose an option (e=Exit): " read -r usepref_choice - - if [ "$usepref_choice" = "e" ]; then + + if [ "$usepref_choice" = "e" ] + then exitmenu="exit" break - elif ! Validate_Number "$usepref_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$COUNTER" - validselection="false" + elif ! Validate_Number "$usepref_choice" + then + printf "\n${ERR}Please enter a valid number [1-3].${CLEARFORMAT}\n" + validselection="false" ; echo else - if [ "$usepref_choice" -lt 0 ] || [ "$usepref_choice" -gt "3" ]; then - printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$COUNTER" - validselection="false" + if [ "$usepref_choice" -lt 1 ] || [ "$usepref_choice" -gt 3 ] + then + printf "\n${ERR}Please enter a number between 1 and 3.${CLEARFORMAT}\n" + validselection="false" ; echo else case "$usepref_choice" in - 1) - usepreferred="auto" - ;; - 2) - usepreferred="user" - ;; - 3) - usepreferred="onetime" - ;; + 1) usepreferred="auto" ;; + 2) usepreferred="user" ;; + 3) usepreferred="onetime" ;; esac - validselection="true" - printf "\\n" + validselection="true" ; echo break fi fi done fi - if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ]; then + if [ "$exitmenu" != "exit" ] && [ "$validselection" != "false" ] + then if Check_Lock menu; then Run_Speedtest "$usepreferred" "$useiface" Clear_Lock @@ -2649,50 +4003,62 @@ Menu_RunSpeedtest(){ elif [ "$exitmenu" = "exit" ]; then break fi - printf "\\n" + printf "\n" PressEnter ScriptHeader done - - if [ "$exitmenu" != "exit" ]; then - return 0 - else - printf "\\n" - return 1 + + if [ "$exitmenu" != "exit" ] + then return 0 + else echo ; return 1 fi } -Menu_ConfigurePreferred(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-29] ## +##----------------------------------------## +Menu_ConfigurePreferred() +{ exitmenu="" prefiface="" ScriptHeader - while true; do - printf "Choose an interface to configure server preference for:\\n\\n" - printf "1. All (on/off only)\\n\\n" + while true + do + printf "Choose an interface to configure server preference for:\n\n" + printf "1. All (on/off only)\n\n" COUNTER="2" - while IFS='' read -r line || [ -n "$line" ]; do - if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then + while IFS='' read -r line || [ -n "$line" ] + do + if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ] + then pref_enabled="" - if PreferredServer check "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')"; then pref_enabled="On"; else pref_enabled="Off"; fi - printf "%s. %s\\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" - printf " Preferred: %s - Server: %s\\n\\n" "$pref_enabled" "$(PreferredServer list "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" | cut -f2 -d"|")" + if PreferredServer check "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" + then pref_enabled="On"; else pref_enabled="Off"; fi + printf "%s. %s\n" "$COUNTER" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" + printf " Preferred: %s - Server: %s\n\n" "$pref_enabled" "$(PreferredServer list "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" | cut -f2 -d'|')" COUNTER=$((COUNTER+1)) fi done < "$SCRIPT_INTERFACES_USER" - while true; do - printf "\\nChoose an option: " + + while true + do + printf "\nChoose an option (e=Exit): " read -r iface_choice - - if [ "$iface_choice" = "e" ]; then + + if [ "$iface_choice" = "e" ] + then exitmenu="exit" break - elif ! Validate_Number "$iface_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-%s)${CLEARFORMAT}\\n" "$((COUNTER-1))" + elif ! Validate_Number "$iface_choice" + then + printf "\n${ERR}Please enter a valid number [1-%s].${CLEARFORMAT}\n" "$((COUNTER-1))" else - if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ]; then - printf "\\n\\e[31mPlease enter a number between 1 and %s${CLEARFORMAT}\\n" "$((COUNTER-1))" + if [ "$iface_choice" -lt 1 ] || [ "$iface_choice" -gt "$((COUNTER-1))" ] + then + printf "\n${ERR}Please enter a number between 1 and %s.${CLEARFORMAT}\n" "$((COUNTER-1))" else - if [ "$iface_choice" -gt "1" ]; then + if [ "$iface_choice" -gt "1" ] + then prefiface="$(grep -v "interface not up" "$SCRIPT_INTERFACES_USER" | sed -n $((iface_choice-1))p | cut -f1 -d"#" | sed 's/ *$//')" break else @@ -2702,70 +4068,88 @@ Menu_ConfigurePreferred(){ fi fi done - - printf "\\n" - - if [ "$exitmenu" != "exit" ]; then - if [ "$prefiface" = "All" ]; then - while true; do - printf "What would you like to do?\\n\\n" - printf "1. Turn on preferred servers\\n" - printf "2. Turn off preferred servers\\n" - printf "\\nChoose an option: " + + printf "\n" + + if [ "$exitmenu" != "exit" ] + then + if [ "$prefiface" = "All" ] + then + while true + do + printf "What would you like to do?\n\n" + printf "1. Turn on preferred servers\n" + printf "2. Turn off preferred servers\n" + printf "\nChoose an option (e=Exit): " read -r usepref_choice - - if [ "$usepref_choice" = "e" ]; then + + if [ "$usepref_choice" = "e" ] + then break - elif ! Validate_Number "$usepref_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n" + elif ! Validate_Number "$usepref_choice" + then + printf "\n${ERR}Please enter a valid number [1-2].${CLEARFORMAT}\n" else - if [ "$usepref_choice" -lt 1 ] || [ "$usepref_choice" -gt 2 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" + if [ "$usepref_choice" -lt 1 ] || [ "$usepref_choice" -gt 2 ] + then + printf "\n${ERR}Please enter a number between 1 and 2.${CLEARFORMAT}\n\n" else prefenabledisable="" - if [ "$usepref_choice" -eq 1 ]; then + if [ "$usepref_choice" -eq 1 ] + then prefenabledisable="enable" else prefenabledisable="disable" fi - while IFS='' read -r line || [ -n "$line" ]; do - if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ]; then + while IFS='' read -r line || [ -n "$line" ] + do + if [ "$(echo "$line" | grep -c "interface not up")" -eq 0 ] + then PreferredServer "$prefenabledisable" "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" fi done < "$SCRIPT_INTERFACES_USER" - printf "\\n" + printf "\n" break fi fi done else - while true; do + while true + do pref_enabled="" - if PreferredServer check "$prefiface"; then pref_enabled="On"; else pref_enabled="Off"; fi - printf "What would you like to do?\\n\\n" - printf "1. Toggle preferred server on/off - currently: %s\\n" "$pref_enabled" - printf "2. Set preferred server - currently: %s\\n" "$(PreferredServer list "$prefiface" | cut -f2 -d"|")" - printf "\\nChoose an option: " + if PreferredServer check "$prefiface" + then pref_enabled="On"; else pref_enabled="Off" + fi + printf "What would you like to do?\n\n" + printf "1. Toggle preferred server on/off - currently: %s\n" "$pref_enabled" + printf "2. Set preferred server - currently: %s\n" "$(PreferredServer list "$prefiface" | cut -f2 -d"|")" + printf "\nChoose an option (e=Exit): " read -r ifpref_choice - - if [ "$ifpref_choice" = "e" ]; then + + if [ "$ifpref_choice" = "e" ] + then break - elif ! Validate_Number "$ifpref_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n" + elif ! Validate_Number "$ifpref_choice" + then + printf "\n${ERR}Please enter a valid number [1-2].${CLEARFORMAT}\n" else - if [ "$ifpref_choice" -lt 1 ] || [ "$ifpref_choice" -gt 2 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n" + if [ "$ifpref_choice" -lt 1 ] || [ "$ifpref_choice" -gt 2 ] + then + printf "\n${ERR}Please enter a number between 1 and 2.${CLEARFORMAT}\n" else - if [ "$ifpref_choice" -eq 1 ]; then - printf "\\n" - if PreferredServer check "$prefiface"; then + if [ "$ifpref_choice" -eq 1 ] + then + printf "\n" + if PreferredServer check "$prefiface" + then PreferredServer disable "$prefiface" else PreferredServer enable "$prefiface" fi break - elif [ "$ifpref_choice" -eq 2 ]; then - printf "\\n" + elif [ "$ifpref_choice" -eq 2 ] + then + printf "\n" PreferredServer "update" "$prefiface" break fi @@ -2777,20 +4161,21 @@ Menu_ConfigurePreferred(){ if [ "$exitmenu" = "exit" ]; then break fi - printf "\\n" + printf "\n" PressEnter ScriptHeader done - - if [ "$exitmenu" != "exit" ]; then + + if [ "$exitmenu" != "exit" ] + then return 0 else - printf "\\n" - return 1 + echo ; return 1 fi } -Menu_EditSchedule(){ +Menu_EditSchedule() +{ exitmenu="" formattype="" crudays="" @@ -2798,78 +4183,93 @@ Menu_EditSchedule(){ cruhours="" crumins="" - while true; do - printf "\\n${BOLD}Please choose which day(s) to run speedtest (0-6 - 0 = Sunday, * for every day, or comma separated days):${CLEARFORMAT} " + while true + do + printf "\n${BOLD}Please choose which day(s) to run speedtest (0-6 - 0 = Sunday, * for every day, or comma separated days):${CLEARFORMAT} " read -r day_choice - if [ "$day_choice" = "e" ]; then + if [ "$day_choice" = "e" ] + then exitmenu="exit" break - elif [ "$day_choice" = "*" ]; then + elif [ "$day_choice" = "*" ] + then crudays="$day_choice" - printf "\\n" + printf "\n" break - elif [ -z "$day_choice" ]; then - printf "\\n\\e[31mPlease enter a valid number (0-6) or comma separated values${CLEARFORMAT}\\n" + elif [ -z "$day_choice" ] + then + printf "\n${ERR}Please enter a valid number (0-6) or comma separated values${CLEARFORMAT}\n" else crudaystmp="$(echo "$day_choice" | sed "s/,/ /g")" crudaysvalidated="true" - for i in $crudaystmp; do - if echo "$i" | grep -q "-"; then - if [ "$i" = "-" ]; then - printf "\\n\\e[31mPlease enter a valid number (0-6)${CLEARFORMAT}\\n" + for i in $crudaystmp + do + if echo "$i" | grep -q "-" + then + if [ "$i" = "-" ] + then + printf "\n${ERR}Please enter a valid number (0-6)${CLEARFORMAT}\n" crudaysvalidated="false" break fi crudaystmp2="$(echo "$i" | sed "s/-/ /")" - for i2 in $crudaystmp2; do - if ! Validate_Number "$i2"; then - printf "\\n\\e[31mPlease enter a valid number (0-6)${CLEARFORMAT}\\n" + for i2 in $crudaystmp2 + do + if ! Validate_Number "$i2" + then + printf "\n${ERR}Please enter a valid number (0-6)${CLEARFORMAT}\n" crudaysvalidated="false" break - elif [ "$i2" -lt 0 ] || [ "$i2" -gt 6 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 6${CLEARFORMAT}\\n" + elif [ "$i2" -lt 0 ] || [ "$i2" -gt 6 ] + then + printf "\n${ERR}Please enter a number between 0 and 6${CLEARFORMAT}\n" crudaysvalidated="false" break fi done - elif ! Validate_Number "$i"; then - printf "\\n\\e[31mPlease enter a valid number (0-6) or comma separated values${CLEARFORMAT}\\n" + elif ! Validate_Number "$i" + then + printf "\n${ERR}Please enter a valid number (0-6) or comma separated values${CLEARFORMAT}\n" crudaysvalidated="false" break else - if [ "$i" -lt 0 ] || [ "$i" -gt 6 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 6 or comma separated values${CLEARFORMAT}\\n" + if [ "$i" -lt 0 ] || [ "$i" -gt 6 ] + then + printf "\n${ERR}Please enter a number between 0 and 6 or comma separated values${CLEARFORMAT}\n" crudaysvalidated="false" break fi fi done - if [ "$crudaysvalidated" = "true" ]; then + if [ "$crudaysvalidated" = "true" ] + then crudays="$day_choice" - printf "\\n" + printf "\n" break fi fi done - if [ "$exitmenu" != "exit" ]; then - while true; do - printf "\\n${BOLD}Please choose the format to specify the hour/minute(s) to run speedtest:${CLEARFORMAT}\\n" - printf " 1. Every X hours/minutes\\n" - printf " 2. Custom\\n\\n" + if [ "$exitmenu" != "exit" ] + then + while true + do + printf "\n${BOLD}Please choose the format to specify the hour/minute(s) to run speedtest:${CLEARFORMAT}\n" + printf " 1. Every X hours/minutes\n" + printf " 2. Custom\n\n" printf "Choose an option: " read -r formatmenu case "$formatmenu" in 1) formattype="everyx" - printf "\\n" + printf "\n" break ;; 2) formattype="custom" - printf "\\n" + printf "\n" break ;; e) @@ -2877,18 +4277,21 @@ Menu_EditSchedule(){ break ;; *) - printf "\\n\\e[31mPlease enter a valid choice (1-2)${CLEARFORMAT}\\n" + printf "\n${ERR}Please enter a valid choice (1-2)${CLEARFORMAT}\n" ;; esac done fi - if [ "$exitmenu" != "exit" ]; then - if [ "$formattype" = "everyx" ]; then - while true; do - printf "\\n${BOLD}Please choose whether to specify every X hours or every X minutes to run speedtest:${CLEARFORMAT}\\n" - printf " 1. Hours\\n" - printf " 2. Minutes\\n\\n" + if [ "$exitmenu" != "exit" ] + then + if [ "$formattype" = "everyx" ] + then + while true + do + printf "\n${BOLD}Please choose whether to specify every X hours or every X minutes to run speedtest:${CLEARFORMAT}\n" + printf " 1. Hours\n" + printf " 2. Minutes\n\n" printf "Choose an option: " read -r formatmenu @@ -2908,27 +4311,34 @@ Menu_EditSchedule(){ break ;; *) - printf "\\n\\e[31mPlease enter a valid choice (1-2)${CLEARFORMAT}\\n" + printf "\n${ERR}Please enter a valid choice (1-2)${CLEARFORMAT}\n" ;; esac done fi fi - if [ "$exitmenu" != "exit" ]; then - if [ "$formattype" = "hours" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + if [ "$formattype" = "hours" ] + then + while true + do printf "\\n${BOLD}Please choose how often to run speedtest (every X hours, where X is 1-24):${CLEARFORMAT} " read -r hour_choice - if [ "$hour_choice" = "e" ]; then + if [ "$hour_choice" = "e" ] + then exitmenu="exit" break - elif ! Validate_Number "$hour_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-24)${CLEARFORMAT}\\n" - elif [ "$hour_choice" -lt 1 ] || [ "$hour_choice" -gt 24 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 24${CLEARFORMAT}\\n" - elif [ "$hour_choice" -eq 24 ]; then + elif ! Validate_Number "$hour_choice" + then + printf "\\n${ERR}Please enter a valid number (1-24)${CLEARFORMAT}\\n" + elif [ "$hour_choice" -lt 1 ] || [ "$hour_choice" -gt 24 ] + then + printf "\\n${ERR}Please enter a number between 1 and 24${CLEARFORMAT}\\n" + elif [ "$hour_choice" -eq 24 ] + then cruhours=0 crumins=0 printf "\\n" @@ -2940,18 +4350,23 @@ Menu_EditSchedule(){ break fi done - elif [ "$formattype" = "mins" ]; then - while true; do + elif [ "$formattype" = "mins" ] + then + while true + do printf "\\n${BOLD}Please choose how often to run speedtest (every X minutes, where X is 1-30):${CLEARFORMAT} " read -r min_choice - if [ "$min_choice" = "e" ]; then + if [ "$min_choice" = "e" ] + then exitmenu="exit" break - elif ! Validate_Number "$min_choice"; then - printf "\\n\\e[31mPlease enter a valid number (1-30)${CLEARFORMAT}\\n" - elif [ "$min_choice" -lt 1 ] || [ "$min_choice" -gt 30 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 30${CLEARFORMAT}\\n" + elif ! Validate_Number "$min_choice" + then + printf "\\n${ERR}Please enter a valid number (1-30)${CLEARFORMAT}\\n" + elif [ "$min_choice" -lt 1 ] || [ "$min_choice" -gt 30 ] + then + printf "\\n${ERR}Please enter a number between 1 and 30${CLEARFORMAT}\\n" else crumins="*/$min_choice" cruhours="*" @@ -2962,69 +4377,89 @@ Menu_EditSchedule(){ fi fi - if [ "$exitmenu" != "exit" ]; then - if [ "$formattype" = "custom" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + if [ "$formattype" = "custom" ] + then + while true + do printf "\\n${BOLD}Please choose which hour(s) to run speedtest (0-23, * for every hour, or comma separated hours):${CLEARFORMAT} " read -r hour_choice - if [ "$hour_choice" = "e" ]; then + if [ "$hour_choice" = "e" ] + then exitmenu="exit" break - elif [ "$hour_choice" = "*" ]; then + elif [ "$hour_choice" = "*" ] + then cruhours="$hour_choice" printf "\\n" break else cruhourstmp="$(echo "$hour_choice" | sed "s/,/ /g")" cruhoursvalidated="true" - for i in $cruhourstmp; do - if echo "$i" | grep -q "-"; then - if [ "$i" = "-" ]; then - printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" + for i in $cruhourstmp + do + if echo "$i" | grep -q "-" + then + if [ "$i" = "-" ] + then + printf "\\n${ERR}Please enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi cruhourstmp2="$(echo "$i" | sed "s/-/ /")" - for i2 in $cruhourstmp2; do - if ! Validate_Number "$i2"; then - printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" + for i2 in $cruhourstmp2 + do + if ! Validate_Number "$i2" + then + printf "\\n${ERR}Please enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break - elif [ "$i2" -lt 0 ] || [ "$i2" -gt 23 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 23${CLEARFORMAT}\\n" + elif [ "$i2" -lt 0 ] || [ "$i2" -gt 23 ] + then + printf "\\n${ERR}Please enter a number between 0 and 23${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi done - elif echo "$i" | grep -q "/"; then + elif echo "$i" | grep -q "/" + then cruhourstmp3="$(echo "$i" | sed "s/\*\///")" - if ! Validate_Number "$cruhourstmp3"; then - printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" + if ! Validate_Number "$cruhourstmp3" + then + printf "\\n${ERR}Please enter a valid number (0-23)${CLEARFORMAT}\\n" cruhoursvalidated="false" break - elif [ "$cruhourstmp3" -lt 0 ] || [ "$cruhourstmp3" -gt 23 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 23${CLEARFORMAT}\\n" + elif [ "$cruhourstmp3" -lt 0 ] || [ "$cruhourstmp3" -gt 23 ] + then + printf "\\n${ERR}Please enter a number between 0 and 23${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi - elif ! Validate_Number "$i"; then - printf "\\n\\e[31mPlease enter a valid number (0-23) or comma separated values${CLEARFORMAT}\\n" + elif ! Validate_Number "$i" + then + printf "\\n${ERR}Please enter a valid number (0-23) or comma separated values${CLEARFORMAT}\\n" cruhoursvalidated="false" break - elif [ "$i" -lt 0 ] || [ "$i" -gt 23 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 23 or comma separated values${CLEARFORMAT}\\n" + elif [ "$i" -lt 0 ] || [ "$i" -gt 23 ] + then + printf "\\n${ERR}Please enter a number between 0 and 23 or comma separated values${CLEARFORMAT}\\n" cruhoursvalidated="false" break fi done - if [ "$cruhoursvalidated" = "true" ]; then - if echo "$hour_choice" | grep -q "-"; then + if [ "$cruhoursvalidated" = "true" ] + then + if echo "$hour_choice" | grep -q "-" + then cruhours1="$(echo "$hour_choice" | cut -f1 -d'-')" cruhours2="$(echo "$hour_choice" | cut -f2 -d'-')" - if [ "$cruhours1" -lt "$cruhours2" ]; then + if [ "$cruhours1" -lt "$cruhours2" ] + then cruhours="$hour_choice" - elif [ "$cruhours2" -lt "$cruhours1" ]; then + elif [ "$cruhours2" -lt "$cruhours1" ] + then cruhours="$cruhours1-23,0-$cruhours2" fi else @@ -3038,76 +4473,96 @@ Menu_EditSchedule(){ fi fi - if [ "$exitmenu" != "exit" ]; then - if [ "$formattype" = "custom" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + if [ "$formattype" = "custom" ] + then + while true + do printf "\\n${BOLD}Please choose which minutes(s) to run speedtest (0-59, * for every minute, or comma separated minutes):${CLEARFORMAT} " read -r min_choice - if [ "$min_choice" = "e" ]; then + if [ "$min_choice" = "e" ] + then exitmenu="exit" break - elif [ "$min_choice" = "*" ]; then + elif [ "$min_choice" = "*" ] + then crumins="$min_choice" printf "\\n" break else cruminstmp="$(echo "$min_choice" | sed "s/,/ /g")" cruminsvalidated="true" - for i in $cruminstmp; do - if echo "$i" | grep -q "-"; then - if [ "$i" = "-" ]; then - printf "\\n\\e[31mPlease enter a valid number (0-23)${CLEARFORMAT}\\n" + for i in $cruminstmp + do + if echo "$i" | grep -q "-" + then + if [ "$i" = "-" ] + then + printf "\\n${ERR}Please enter a valid number (0-23)${CLEARFORMAT}\\n" cruminsvalidated="false" break fi cruminstmp2="$(echo "$i" | sed "s/-/ /")" - for i2 in $cruminstmp2; do - if ! Validate_Number "$i2"; then - printf "\\n\\e[31mPlease enter a valid number (0-59)${CLEARFORMAT}\\n" + for i2 in $cruminstmp2 + do + if ! Validate_Number "$i2" + then + printf "\\n${ERR}Please enter a valid number (0-59)${CLEARFORMAT}\\n" cruminsvalidated="false" break - elif [ "$i2" -lt 0 ] || [ "$i2" -gt 59 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 59${CLEARFORMAT}\\n" + elif [ "$i2" -lt 0 ] || [ "$i2" -gt 59 ] + then + printf "\\n${ERR}Please enter a number between 0 and 59${CLEARFORMAT}\\n" cruminsvalidated="false" break fi done - elif echo "$i" | grep -q "/"; then + elif echo "$i" | grep -q "/" + then cruminstmp3="$(echo "$i" | sed "s/\*\///")" - if ! Validate_Number "$cruminstmp3"; then - printf "\\n\\e[31mPlease enter a valid number (0-30)${CLEARFORMAT}\\n" + if ! Validate_Number "$cruminstmp3" + then + printf "\\n${ERR}Please enter a valid number (0-30)${CLEARFORMAT}\\n" cruminsvalidated="false" break - elif [ "$cruminstmp3" -lt 0 ] || [ "$cruminstmp3" -gt 30 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 30${CLEARFORMAT}\\n" + elif [ "$cruminstmp3" -lt 0 ] || [ "$cruminstmp3" -gt 30 ] + then + printf "\\n${ERR}Please enter a number between 0 and 30${CLEARFORMAT}\\n" cruminsvalidated="false" break fi - elif ! Validate_Number "$i"; then - printf "\\n\\e[31mPlease enter a valid number (0-59) or comma separated values${CLEARFORMAT}\\n" + elif ! Validate_Number "$i" + then + printf "\\n${ERR}Please enter a valid number (0-59) or comma separated values${CLEARFORMAT}\\n" cruminsvalidated="false" break - elif [ "$i" -lt 0 ] || [ "$i" -gt 59 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 59 or comma separated values${CLEARFORMAT}\\n" + elif [ "$i" -lt 0 ] || [ "$i" -gt 59 ] + then + printf "\\n${ERR}Please enter a number between 0 and 59 or comma separated values${CLEARFORMAT}\\n" cruminsvalidated="false" break fi done - if [ "$cruminsvalidated" = "true" ]; then - if echo "$min_choice" | grep -q "-"; then + if [ "$cruminsvalidated" = "true" ] + then + if echo "$min_choice" | grep -q "-" + then crumins1="$(echo "$min_choice" | cut -f1 -d'-')" crumins2="$(echo "$min_choice" | cut -f2 -d'-')" - if [ "$crumins1" -lt "$crumins2" ]; then + if [ "$crumins1" -lt "$crumins2" ] + then crumins="$min_choice" - elif [ "$crumins2" -lt "$crumins1" ]; then + elif [ "$crumins2" -lt "$crumins1" ] + then crumins="$crumins1-59,0-$crumins2" fi else crumins="$min_choice" fi - printf "\\n" + printf "\n" break fi fi @@ -3115,32 +4570,36 @@ Menu_EditSchedule(){ fi fi - if [ "$exitmenu" != "exit" ]; then - TestSchedule update "$crudays" "$cruhours" "$crumins" + if [ "$exitmenu" != "exit" ] + then + CronTestSchedule update "$crudays" "$cruhours" "$crumins" return 0 else return 1 fi } -Menu_ResetDB(){ - printf "${BOLD}\\e[33mWARNING: This will reset the %s database by deleting all database records.\\n" "$SCRIPT_NAME" - printf "A backup of the database will be created if you change your mind.${CLEARFORMAT}\\n" - printf "\\n${BOLD}Do you want to continue? (y/n)${CLEARFORMAT} " +Menu_ResetDB() +{ + printf "${BOLD}${WARN}WARNING: This will reset the %s database by deleting all database records.\n" "$SCRIPT_NAME" + printf "A backup of the database will be created if you change your mind.${CLEARFORMAT}\n" + printf "\n${BOLD}Do you want to continue? (y/n)${CLEARFORMAT} " read -r confirm case "$confirm" in y|Y) - printf "\\n" + printf "\n" Reset_DB ;; *) - printf "\\n${BOLD}\\e[33mDatabase reset cancelled${CLEARFORMAT}\\n\\n" + printf "\n${BOLD}${WARN}Database reset cancelled${CLEARFORMAT}\n\n" ;; esac } -Menu_AutoBW(){ - while true; do +Menu_AutoBW() +{ + while true + do ScriptHeader AUTOBW_MENU="" @@ -3170,11 +4629,13 @@ Menu_AutoBW(){ PressEnter ;; 2) - while true; do + while true + do ScriptHeader exitmenu="" avgnum="" - while true; do + while true + do printf "\\n" printf "Enter number of speedtests to use to calculate avg bandwidth (1-30): " read -r avgnumvalue @@ -3182,10 +4643,10 @@ Menu_AutoBW(){ exitmenu="exit" break elif ! Validate_Number "$avgnumvalue"; then - printf "\\n\\e[31mPlease enter a valid number (1-30)${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a valid number (1-30)${CLEARFORMAT}\\n" else if [ "$avgnumvalue" -lt 1 ] || [ "$avgnumvalue" -gt 30 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 30${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a number between 1 and 30${CLEARFORMAT}\\n" else avgnum="$avgnumvalue" break @@ -3204,7 +4665,8 @@ Menu_AutoBW(){ PressEnter ;; 3) - while true; do + while true + do ScriptHeader exitmenu="" updown="" @@ -3213,17 +4675,18 @@ Menu_AutoBW(){ printf "Select a scale factor to set\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" - while true; do + while true + do printf "Choose an option: " read -r autobwsfchoice if [ "$autobwsfchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwsfchoice"; then - printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwsfchoice" -lt 1 ] || [ "$autobwsfchoice" -gt 2 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwsfchoice" -eq 1 ]; then updown="DOWN" @@ -3235,8 +4698,10 @@ Menu_AutoBW(){ fi fi done - if [ "$exitmenu" != "exit" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + while true + do printf "\\n" printf "Enter percentage to scale bandwidth by (1-100): " read -r autobwsfvalue @@ -3244,10 +4709,10 @@ Menu_AutoBW(){ exitmenu="exit" break elif ! Validate_Number "$autobwsfvalue"; then - printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a valid number (1-100)${CLEARFORMAT}\\n" else if [ "$autobwsfvalue" -lt 1 ] || [ "$autobwsfvalue" -gt 100 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 100${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a number between 1 and 100${CLEARFORMAT}\\n" else sfvalue="$autobwsfvalue" break @@ -3269,7 +4734,8 @@ Menu_AutoBW(){ PressEnter ;; 4) - while true; do + while true + do ScriptHeader exitmenu="" updown="" @@ -3279,17 +4745,18 @@ Menu_AutoBW(){ printf "Select a bandwidth to set limit for\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" - while true; do + while true + do printf "Choose an option: " read -r autobwchoice if [ "$autobwchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwchoice"; then - printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwchoice" -lt 1 ] || [ "$autobwchoice" -gt 2 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwchoice" -eq 1 ]; then updown="DOWN" @@ -3301,8 +4768,10 @@ Menu_AutoBW(){ fi fi done - if [ "$exitmenu" != "exit" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + while true + do printf "\\n" printf "Select a limit to set\\n" printf "1. Upper\\n" @@ -3313,10 +4782,10 @@ Menu_AutoBW(){ exitmenu="exit" break elif ! Validate_Number "$autobwlimit"; then - printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a valid number (1-100)${CLEARFORMAT}\\n" else if [ "$autobwlimit" -lt 1 ] || [ "$autobwlimit" -gt 100 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 100${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a number between 1 and 100${CLEARFORMAT}\\n" else if [ "$autobwlimit" -eq 1 ]; then limithighlow="ULIMIT" @@ -3326,8 +4795,10 @@ Menu_AutoBW(){ fi fi - if [ "$exitmenu" != "exit" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + while true + do printf "\\n" printf "Enter value to set limit to (0 = unlimited for upper): " read -r autobwlimvalue @@ -3335,7 +4806,7 @@ Menu_AutoBW(){ exitmenu="exit" break elif ! Validate_Number "$autobwlimvalue"; then - printf "\\n\\e[31mPlease enter a valid number (1-100)${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a valid number (1-100)${CLEARFORMAT}\\n" else limitvalue="$autobwlimvalue" break @@ -3358,7 +4829,8 @@ Menu_AutoBW(){ PressEnter ;; 5) - while true; do + while true + do ScriptHeader exitmenu="" updown="" @@ -3367,17 +4839,18 @@ Menu_AutoBW(){ printf "Select a threshold to set\\n" printf "1. Download\\n" printf "2. Upload\\n\\n" - while true; do + while true + do printf "Choose an option: " read -r autobwthchoice if [ "$autobwthchoice" = "e" ]; then exitmenu="exit" break elif ! Validate_Number "$autobwthchoice"; then - printf "\\n\\e[31mPlease enter a valid number (1-2)${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a valid number (1-2)${CLEARFORMAT}\\n\\n" else if [ "$autobwthchoice" -lt 1 ] || [ "$autobwthchoice" -gt 2 ]; then - printf "\\n\\e[31mPlease enter a number between 1 and 2${CLEARFORMAT}\\n\\n" + printf "\\n${ERR}Please enter a number between 1 and 2${CLEARFORMAT}\\n\\n" else if [ "$autobwthchoice" -eq 1 ]; then updown="DOWN" @@ -3390,8 +4863,10 @@ Menu_AutoBW(){ fi done - if [ "$exitmenu" != "exit" ]; then - while true; do + if [ "$exitmenu" != "exit" ] + then + while true + do printf "\\n" printf "Enter percentage to use for result threshold: " read -r autobwthvalue @@ -3399,10 +4874,10 @@ Menu_AutoBW(){ exitmenu="exit" break elif ! Validate_Number "$autobwthvalue"; then - printf "\\n\\e[31mPlease enter a valid number (0-100)${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a valid number (0-100)${CLEARFORMAT}\\n" else if [ "$autobwthvalue" -lt 0 ] || [ "$autobwthvalue" -gt 100 ]; then - printf "\\n\\e[31mPlease enter a number between 0 and 100${CLEARFORMAT}\\n" + printf "\\n${ERR}Please enter a number between 0 and 100${CLEARFORMAT}\\n" else thvalue="$autobwthvalue" break @@ -3444,103 +4919,175 @@ Menu_AutoBW(){ done } -Menu_AutoBW_Update(){ +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Menu_AutoBW_Update() +{ if [ "$(nvram get qos_enable)" -eq 0 ]; then Print_Output true "QoS is not enabled, please enable this in the Asus WebUI." "$ERR" return 1 fi - + dsf="$(AutoBWConf check SF DOWN | awk '{printf ($1/100)}')" usf="$(AutoBWConf check SF UP | awk '{printf ($1/100)}')" - + dlimitlow="$(($(AutoBWConf check LLIMIT DOWN)*1024))" dlimithigh="$(($(AutoBWConf check ULIMIT DOWN)*1024))" ulimitlow="$(($(AutoBWConf check LLIMIT UP)*1024))" ulimithigh="$(($(AutoBWConf check ULIMIT UP)*1024))" avgcalc="$(AutoBWConf check AVERAGE CALC)" - + metriclist="Download Upload" - - for metric in $metriclist; do + local abwIndx=0 + for metric in $metriclist + do { { - echo ".mode list" - echo ".headers off" - echo ".output /tmp/spdbw$metric" - echo "SELECT avg($metric) FROM (SELECT $metric FROM spdstats_WAN ORDER BY [Timestamp] DESC LIMIT $avgcalc);" - } > /tmp/spd-autobw.sql - "$SQLITE3_PATH" "$SCRIPT_STORAGE_DIR/spdstats.db" < /tmp/spd-autobw.sql - rm -f /tmp/spd-autobw.sql + echo ".mode list" + echo ".headers off" + echo ".output /tmp/spdbw$metric" + echo "PRAGMA temp_store=1;" + echo "SELECT avg($metric) FROM (SELECT $metric FROM spdstats_WAN ORDER BY [Timestamp] DESC LIMIT $avgcalc);" + } > /tmp/spdTest-autobw.sql + abwIndx="$((abwIndx + 1))" + _ApplyDatabaseSQLCmds_ /tmp/spdTest-autobw.sql "abw${abwIndx}" + rm -f /tmp/spdTest-autobw.sql } done - + autobwoutfile="$SCRIPT_STORAGE_DIR/.autobwoutfile" - TZ=$(cat /etc/TZ) + TZ="$(cat /etc/TZ)" export TZ - - printf "AutoBW report - %s\\n\\n" "$(date +"%c")" > "$autobwoutfile" - + + printf "AutoBW report - %s\\n\\n" "$(date +'%c')" > "$autobwoutfile" + dspdkbps="$(echo "$(awk '{printf (1024*$1)}' /tmp/spdbwDownload)" "$dsf" | awk '{printf int($1*$2)}')" uspdkbps="$(echo "$(awk '{printf (1024*$1)}' /tmp/spdbwUpload)" "$usf" | awk '{printf int($1*$2)}')" - + rm -f /tmp/spdbwDownload rm -f /tmp/spdbwUpload - - if [ "$dspdkbps" -lt "$dlimitlow" ]; then + + if [ "$dspdkbps" -lt "$dlimitlow" ] + then Print_Output true "Download speed ($dspdkbps Kbps) < lower limit ($dlimitlow Kbps)" "$WARN" | tee -a "$autobwoutfile" dspdkbps="$dlimitlow" - elif [ "$dspdkbps" -gt "$dlimithigh" ] && [ "$dlimithigh" -gt 0 ]; then + elif [ "$dspdkbps" -gt "$dlimithigh" ] && [ "$dlimithigh" -gt 0 ] + then Print_Output true "Download speed ($dspdkbps Kbps) > upper limit ($dlimithigh Kbps)" "$WARN" | tee -a "$autobwoutfile" dspdkbps="$dlimithigh" fi - - if [ "$uspdkbps" -lt "$ulimitlow" ]; then + + if [ "$uspdkbps" -lt "$ulimitlow" ] + then Print_Output true "Upload speed ($uspdkbps Kbps) < lower limit ($ulimitlow Kbps)" "$WARN" | tee -a "$autobwoutfile" uspdkbps="$ulimitlow" - elif [ "$uspdkbps" -gt "$ulimithigh" ] && [ "$ulimithigh" -gt 0 ]; then + elif [ "$uspdkbps" -gt "$ulimithigh" ] && [ "$ulimithigh" -gt 0 ] + then Print_Output true "Upload speed ($uspdkbps Kbps) > upper limit ($ulimithigh Kbps)" "$WARN" | tee -a "$autobwoutfile" uspdkbps="$ulimithigh" fi - + old_uspdkbps="$(nvram get qos_obw)" old_dspdkbps="$(nvram get qos_ibw)" - + bw_changed="false" - + dbw_threshold="$(AutoBWConf check THRESHOLD DOWN | awk '{printf ($1/100)}')" - - if [ "$dspdkbps" -gt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1+$1*$2)}')" ] || [ "$dspdkbps" -lt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1-$1*$2)}')" ]; then + + if [ "$dspdkbps" -gt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1+$1*$2)}')" ] || \ + [ "$dspdkbps" -lt "$(echo "$old_dspdkbps" "$dbw_threshold" | awk '{printf int($1-$1*$2)}')" ] + then bw_changed="true" nvram set qos_ibw="$(echo $dspdkbps | cut -d'.' -f1)" Print_Output true "Setting QoS Download Speed to $dspdkbps Kbps (was $old_dspdkbps Kbps)" "$PASS" | tee -a "$autobwoutfile" else Print_Output true "Calculated Download speed ($dspdkbps Kbps) does not exceed $(AutoBWConf check THRESHOLD DOWN)% threshold of existing value ($old_dspdkbps Kbps)" "$WARN" | tee -a "$autobwoutfile" fi - + ubw_threshold="$(AutoBWConf check THRESHOLD UP | awk '{printf ($1/100)}')" - - if [ "$uspdkbps" -gt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1+$1*$2)}')" ] || [ "$uspdkbps" -lt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1-$1*$2)}')" ]; then + + if [ "$uspdkbps" -gt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1+$1*$2)}')" ] || \ + [ "$uspdkbps" -lt "$(echo "$old_uspdkbps" "$ubw_threshold" | awk '{printf int($1-$1*$2)}')" ] + then bw_changed="true" nvram set qos_obw="$(echo $uspdkbps | cut -d'.' -f1)" Print_Output true "Setting QoS Upload Speed to $uspdkbps Kbps (was $old_uspdkbps Kbps)" "$PASS" | tee -a "$autobwoutfile" else Print_Output true "Calculated Upload speed ($uspdkbps Kbps) does not exceed $(AutoBWConf check THRESHOLD UP)% threshold of existing value ($old_uspdkbps Kbps)" "$WARN" | tee -a "$autobwoutfile" fi - - if [ "$bw_changed" = "true" ]; then + + if [ "$bw_changed" = "true" ] + then nvram commit service "restart_qos;restart_firewall" >/dev/null 2>&1 printf "AutoBW made changes to QoS bandwidth, QoS will be restarted" >> "$autobwoutfile" else printf "No changes made to QoS by AutoBW" >> "$autobwoutfile" fi - + sed -i 's/[^a-zA-Z0-9():%<>-]/ /g;s/ 1m//g;s/ 33m//g;s/ 0m//g' "$autobwoutfile" - + Clear_Lock } -Menu_Uninstall(){ +##-------------------------------------## +## Added by Martinski W. [2025-Mar-01] ## +##-------------------------------------## +_RemoveMenuAddOnsSection_() +{ + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] || \ + ! echo "$1" | grep -qE "^[1-9][0-9]*$" || \ + ! echo "$2" | grep -qE "^[1-9][0-9]*$" || \ + [ "$1" -ge "$2" ] + then return 1 ; fi + local BEGINnum="$1" ENDINnum="$2" + + if [ -n "$(sed -E "${BEGINnum},${ENDINnum}!d;/${webPageLineTabExp}/!d" "$TEMP_MENU_TREE")" ] + then return 1 + fi + sed -i "${BEGINnum},${ENDINnum}d" "$TEMP_MENU_TREE" + return 0 +} + +##-------------------------------------## +## Added by Martinski W. [2025-Mar-01] ## +##-------------------------------------## +_FindandRemoveMenuAddOnsSection_() +{ + local BEGINnum ENDINnum retCode=1 + + if grep -qE "^${BEGIN_MenuAddOnsTag}$" "$TEMP_MENU_TREE" && \ + grep -qE "^${ENDIN_MenuAddOnsTag}$" "$TEMP_MENU_TREE" + then + BEGINnum="$(grep -nE "^${BEGIN_MenuAddOnsTag}$" "$TEMP_MENU_TREE" | awk -F ':' '{print $1}')" + ENDINnum="$(grep -nE "^${ENDIN_MenuAddOnsTag}$" "$TEMP_MENU_TREE" | awk -F ':' '{print $1}')" + _RemoveMenuAddOnsSection_ "$BEGINnum" "$ENDINnum" && retCode=0 + fi + + if grep -qE "^${webPageMenuAddons}$" "$TEMP_MENU_TREE" && \ + grep -qE "${webPageHelpSupprt}$" "$TEMP_MENU_TREE" + then + BEGINnum="$(grep -nE "^${webPageMenuAddons}$" "$TEMP_MENU_TREE" | awk -F ':' '{print $1}')" + ENDINnum="$(grep -nE "${webPageHelpSupprt}$" "$TEMP_MENU_TREE" | awk -F ':' '{print $1}')" + if [ -n "$BEGINnum" ] && [ -n "$ENDINnum" ] && [ "$BEGINnum" -lt "$ENDINnum" ] + then + BEGINnum="$((BEGINnum - 2))" ; ENDINnum="$((ENDINnum + 3))" + if [ "$(sed -n "${BEGINnum}p" "$TEMP_MENU_TREE")" = "," ] && \ + [ "$(sed -n "${ENDINnum}p" "$TEMP_MENU_TREE")" = "}" ] + then + _RemoveMenuAddOnsSection_ "$BEGINnum" "$ENDINnum" && retCode=0 + fi + fi + fi + return "$retCode" +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-01] ## +##----------------------------------------## +Menu_Uninstall() +{ if [ -n "$PPID" ]; then ps | grep -v grep | grep -v $$ | grep -v "$PPID" | grep -i "$SCRIPT_NAME_LOWER" | grep generate | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 else @@ -3557,19 +5104,30 @@ Menu_Uninstall(){ Auto_Startup delete 2>/dev/null Auto_Cron delete 2>/dev/null Auto_ServiceEvent delete 2>/dev/null - + Shortcut_Script delete + + LOCKFILE=/tmp/addonwebui.lock + FD=386 + eval exec "$FD>$LOCKFILE" + flock -x "$FD" + Get_WebUI_Page "$SCRIPT_DIR/spdstats_www.asp" - if [ -n "$MyPage" ] && [ "$MyPage" != "none" ] && [ -f "/tmp/menuTree.js" ]; then - sed -i "\\~$MyPage~d" /tmp/menuTree.js + if [ -n "$MyWebPage" ] && \ + [ "$MyWebPage" != "NONE" ] && \ + [ -f "$TEMP_MENU_TREE" ] + then + sed -i "\\~$MyWebPage~d" "$TEMP_MENU_TREE" + rm -f "$SCRIPT_WEBPAGE_DIR/$MyWebPage" + rm -f "$SCRIPT_WEBPAGE_DIR/$(echo "$MyWebPage" | cut -f1 -d'.').title" + _FindandRemoveMenuAddOnsSection_ umount /www/require/modules/menuTree.js - mount -o bind /tmp/menuTree.js /www/require/modules/menuTree.js - rm -f "$SCRIPT_WEBPAGE_DIR/$MyPage" - rm -f "$SCRIPT_WEBPAGE_DIR/$(echo $MyPage | cut -f1 -d'.').title" + mount -o bind "$TEMP_MENU_TREE" /www/require/modules/menuTree.js fi - + + flock -u "$FD" rm -f "$SCRIPT_DIR/spdstats_www.asp" 2>/dev/null - - printf "\\n${BOLD}Do you want to delete %s stats and config? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" + + printf "\n${BOLD}Do you want to delete %s stats and config? (y/n)${CLEARFORMAT} " "$SCRIPT_NAME" read -r confirm case "$confirm" in y|Y) @@ -3580,8 +5138,11 @@ Menu_Uninstall(){ : ;; esac - Shortcut_Script delete - + + SETTINGSFILE="/jffs/addons/custom_settings.txt" + sed -i '/spdmerlin_version_local/d' "$SETTINGSFILE" + sed -i '/spdmerlin_version_server/d' "$SETTINGSFILE" + rm -rf "$SCRIPT_WEB_DIR" 2>/dev/null rm -rf "$OOKLA_DIR" 2>/dev/null rm -rf "$OOKLA_LICENSE_DIR" 2>/dev/null @@ -3590,16 +5151,30 @@ Menu_Uninstall(){ Print_Output true "Uninstall completed" "$PASS" } -NTP_Ready(){ - if [ "$(nvram get ntp_ready)" -eq 0 ]; then - ntpwaitcount=0 +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +NTP_Ready() +{ + local theSleepDelay=15 ntpMaxWaitSecs=600 ntpWaitSecs + + if [ "$(nvram get ntp_ready)" -eq 0 ] + then Check_Lock - while [ "$(nvram get ntp_ready)" -eq 0 ] && [ "$ntpwaitcount" -lt 600 ]; do - ntpwaitcount="$((ntpwaitcount + 30))" - Print_Output true "Waiting for NTP to sync..." "$WARN" - sleep 30 + ntpWaitSecs=0 + Print_Output true "Waiting for NTP to sync..." "$WARN" + + while [ "$(nvram get ntp_ready)" -eq 0 ] && [ "$ntpWaitSecs" -lt "$ntpMaxWaitSecs" ] + do + if [ "$ntpWaitSecs" -gt 0 ] && [ "$((ntpWaitSecs % 30))" -eq 0 ] + then + Print_Output true "Waiting for NTP to sync [$ntpWaitSecs secs]..." "$WARN" + fi + sleep "$theSleepDelay" + ntpWaitSecs="$((ntpWaitSecs + theSleepDelay))" done - if [ "$ntpwaitcount" -ge 600 ]; then + if [ "$ntpWaitSecs" -ge "$ntpMaxWaitSecs" ] + then Print_Output true "NTP failed to sync after 10 minutes. Please resolve!" "$CRIT" Clear_Lock exit 1 @@ -3611,84 +5186,111 @@ NTP_Ready(){ } ### function based on @Adamm00's Skynet USB wait function ### -Entware_Ready(){ - if [ ! -f "/opt/bin/opkg" ]; then +##----------------------------------------## +## Modified by Martinski W. [2025-Feb-28] ## +##----------------------------------------## +Entware_Ready() +{ + local theSleepDelay=5 maxSleepTimer=120 sleepTimerSecs + + if [ ! -f /opt/bin/opkg ] + then Check_Lock - sleepcount=1 - while [ ! -f /opt/bin/opkg ] && [ "$sleepcount" -le 10 ]; do - Print_Output true "Entware not found, sleeping for 10s (attempt $sleepcount of 10)" "$ERR" - sleepcount="$((sleepcount + 1))" - sleep 10 + sleepTimerSecs=0 + + while [ ! -f /opt/bin/opkg ] && [ "$sleepTimerSecs" -lt "$maxSleepTimer" ] + do + if [ "$((sleepTimerSecs % 10))" -eq 0 ] + then + Print_Output true "Entware NOT found. Wait for Entware to be ready [$sleepTimerSecs secs]..." "$WARN" + fi + sleep "$theSleepDelay" + sleepTimerSecs="$((sleepTimerSecs + theSleepDelay))" done - if [ ! -f /opt/bin/opkg ]; then - Print_Output true "Entware not found and is required for $SCRIPT_NAME to run, please resolve" "$CRIT" + if [ ! -f /opt/bin/opkg ] + then + Print_Output true "Entware NOT found and is required for $SCRIPT_NAME to run, please resolve!" "$CRIT" Clear_Lock exit 1 else - Print_Output true "Entware found, $SCRIPT_NAME will now continue" "$PASS" + Print_Output true "Entware found. $SCRIPT_NAME will now continue" "$PASS" Clear_Lock fi fi } -### ### ### function based on @dave14305's FlexQoS about function ### -Show_About(){ +Show_About() +{ cat </dev/null - if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi + if AutomaticMode check + then Auto_Cron create 2>/dev/null + else Auto_Cron delete 2>/dev/null + fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create + _CheckFor_WebGUI_Page_ ScriptHeader MainMenu exit 0 fi +##----------------------------------------## +## Modified by Martinski W. [2025-Jan-19] ## +##----------------------------------------## case "$1" in install) Check_Lock @@ -3733,8 +5342,19 @@ case "$1" in Clear_Lock exit 0 ;; + trimdb) + NTP_Ready + Entware_Ready + Check_Lock + _Trim_Database_ + _Optimize_Database_ + _UpdateDatabaseFileSizeInfo_ + Clear_Lock + exit 0 + ;; service_event) - if [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}spdtest"; then + if [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}spdtest" + then rm -f /tmp/detect_spdtest.js rm -f /tmp/spd-result.txt rm -f /tmp/spd-stats.txt @@ -3742,23 +5362,24 @@ case "$1" in sleep 3 Run_Speedtest_WebUI "$3" Clear_Lock - exit 0 - elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlistmanual"; then + elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlistmanual" + then spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlistmanual_//" | cut -f1 -d'_' | tr "a-z" "A-Z")"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_manual_serverlist" - elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlist"; then + elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlist" + then spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlist_//" | cut -f1 -d'_' | tr "a-z" "A-Z")"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_serverlist_$spdifacename" - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}config" ]; then + elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}config" ] + then Interfaces_FromSettings Conf_FromSettings - exit 0 - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}checkupdate" ]; then + elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}checkupdate" ] + then Update_Check - exit 0 - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}doupdate" ]; then + elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}doupdate" ] + then Update_Version force unattended - exit 0 fi exit 0 ;; @@ -3800,11 +5421,15 @@ case "$1" in Create_Symlinks Process_Upgrade Auto_Startup create 2>/dev/null - if AutomaticMode check; then Auto_Cron create 2>/dev/null; else Auto_Cron delete 2>/dev/null; fi + if AutomaticMode check + then Auto_Cron create 2>/dev/null + else Auto_Cron delete 2>/dev/null + fi Auto_ServiceEvent create 2>/dev/null Shortcut_Script create Set_Version_Custom_Settings local "$SCRIPT_VERSION" Set_Version_Custom_Settings server "$SCRIPT_VERSION" + exit 0 ;; checkupdate) Update_Check @@ -3838,8 +5463,8 @@ case "$1" in ;; *) ScriptHeader - Print_Output false "Command not recognised." "$ERR" - Print_Output false "For a list of available commands run: $SCRIPT_NAME_LOWER help" + Print_Output false "Parameter [$*] is NOT recognised." "$ERR" + Print_Output false "For a list of available commands run: $SCRIPT_NAME_LOWER help" "$SETTING" exit 1 ;; esac diff --git a/spdstats_www.asp b/spdstats_www.asp index 0ef44c1..180609b 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -33,22 +33,24 @@ p{font-weight:bolder}thead.collapsible-jquery{color:#fff;padding:0;width:100%;bo @@ -134,6 +136,12 @@ var daysofweek=["Mon","Tues","Wed","Thurs","Fri","Sat","Sun"],maxNoCharts=0,curr     + + @@ -154,9 +162,9 @@ var daysofweek=["Mon","Tues","Wed","Thurs","Fri","Sat","Sun"],maxNoCharts=0,curr Enable automatic speedtests - + - + @@ -189,14 +197,14 @@ var daysofweek=["Mon","Tues","Wed","Thurs","Fri","Sat","Sun"],maxNoCharts=0,curr
- + Minutes @@ -278,24 +286,47 @@ var daysofweek=["Mon","Tues","Wed","Thurs","Fri","Sat","Sun"],maxNoCharts=0,curr Last X results to display - - results (between 1 and 100,default: 10) + + results Number of days of data to keep - - days (between 30 and 365,default: 30) + + days Data Storage Location - + - + + + + + + diff --git a/spdstats_www.js b/spdstats_www.js index e4dea41..e28abb6 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -10,6 +10,7 @@ var interfacesdisabled = []; var arraysortlistlinesWAN = []; var sortnameWAN = 'Time'; var sortdirWAN = 'desc'; +/** OpenVPN **/ var arraysortlistlinesVPNC1 = []; var sortnameVPNC1 = 'Time'; var sortdirVPNC1 = 'desc'; @@ -25,6 +26,7 @@ var sortdirVPNC4 = 'desc'; var arraysortlistlinesVPNC5 = []; var sortnameVPNC5 = 'Time'; var sortdirVPNC5 = 'desc'; +/** WireGuard **/ var arraysortlistlinesWGVPN1 = []; var sortnameWGVPN1 = 'Time'; var sortdirWGVPN1 = 'desc'; @@ -43,11 +45,17 @@ var sortdirWGVPN5 = 'desc'; var speedtestbinary = "builtin"; +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +let databaseResetDone = 0; +var sqlDatabaseFileSize = '0 Bytes'; +var jffsAvailableSpaceStr = '0 Bytes'; +var jffsAvailableSpaceLow = 'OK'; + var ShowLines = GetCookie('ShowLines','string'); var ShowFill = GetCookie('ShowFill','string'); -if(ShowFill == ''){ - ShowFill = 'origin'; -} +if (ShowFill == '') { ShowFill = 'origin'; } var DragZoom = true; var ChartPan = false; @@ -68,7 +76,8 @@ var backgroundcolourlist_Quality = ['rgba(83,4,122,0.5)','rgba(7,242,66,0.5)','r var typelist = ['Combined','Quality']; -function keyHandler(e){ +function keyHandler(e) +{ if(e.keyCode == 82){ $(document).off('keydown'); ResetZoom(); @@ -869,27 +878,39 @@ $.fn.serializeObject = function(){ return o; }; -function SetCurrentPage(){ +function SetCurrentPage() +{ document.form.next_page.value = window.location.pathname.substring(1); document.form.current_page.value = window.location.pathname.substring(1); } -function initial(){ +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +function initial() +{ SetCurrentPage(); LoadCustomSettings(); show_menu(); $('#Time_Format').val(GetCookie('Time_Format', 'number')); - ScriptUpdateLayout(); - get_statstitle_file(); - get_interfaces_file(); + scriptUpdateLayout(); + getStatsTitleFile(); + getInterfacesFile(); + showhide('databaseSize_text',true); + showhide('jffsFreeSpace_text',true); + showhide('jffsFreeSpace_LOW',false); + showhide('jffsFreeSpace_WARN',false); + showhide('jffsFreeSpace_NOTE',false); } -function ScriptUpdateLayout(){ +function scriptUpdateLayout() +{ var localver = GetVersionNumber('local'); var serverver = GetVersionNumber('server'); $('#spdmerlin_version_local').text(localver); - if(localver != serverver && serverver != 'N/A'){ + if (localver != serverver && serverver != 'N/A') + { $('#spdmerlin_version_server').text('Updated version available: '+serverver); showhide('btnChkUpdate',false); showhide('spdmerlin_version_server',true); @@ -1191,16 +1212,20 @@ function get_spdtest_file(){ }); } -function update_spdtest(){ +function update_spdtest() +{ $.ajax({ url: '/ext/spdmerlin/detect_spdtest.js', dataType: 'script', error: function(xhr){ //do nothing }, - success: function(){ - if(spdteststatus.indexOf('InProgress') != -1){ - if(spdteststatus.indexOf('_') != -1){ + success: function() + { + if (spdteststatus.indexOf('InProgress') != -1) + { + if (spdteststatus.indexOf('_') != -1) + { showhide('imgSpdTest',true); showhide('spdtest_text',true); document.getElementById('spdtest_text').innerHTML = 'Speedtest in progress for '+spdteststatus.substring(spdteststatus.indexOf('_')+1); @@ -1208,18 +1233,22 @@ function update_spdtest(){ get_spdtest_file(); } } - else if(spdteststatus == 'GenerateCSV'){ + else if (spdteststatus == 'GenerateCSV') + { document.getElementById('spdtest_text').innerHTML = 'Retrieving data for charts...'; } - else if(spdteststatus == 'Done'){ + else if (spdteststatus == 'Done') + { clearInterval(myinterval); - if(intervalclear == false){ + if (intervalclear == false) + { intervalclear = true; document.getElementById('spdtest_text').innerHTML = 'Refreshing tables and charts...'; get_spdtestresult_file(); } } - else if(spdteststatus == 'LOCKED'){ + else if (spdteststatus == 'LOCKED') + { clearInterval(myinterval); showhide('imgSpdTest',false); document.getElementById('spdtest_text').innerHTML = 'Scheduled speedtest already running!'; @@ -1227,7 +1256,8 @@ function update_spdtest(){ document.getElementById('spdtest_output').parentElement.parentElement.style.display = 'none'; showhide('btnRunSpeedtest',true); } - else if(spdteststatus == 'NoLicense'){ + else if (spdteststatus == 'NoLicense') + { clearInterval(myinterval); showhide('imgSpdTest',false); document.getElementById('spdtest_text').innerHTML = 'Please accept Ookla license at command line via spdmerlin'; @@ -1235,7 +1265,8 @@ function update_spdtest(){ document.getElementById('spdtest_output').parentElement.parentElement.style.display = 'none'; showhide('btnRunSpeedtest',true); } - else if(spdteststatus == 'Error'){ + else if (spdteststatus == 'Error') + { clearInterval(myinterval); showhide('imgSpdTest',false); document.getElementById('spdtest_text').innerHTML = 'Error running speedtest'; @@ -1243,7 +1274,8 @@ function update_spdtest(){ document.getElementById('spdtest_output').parentElement.parentElement.style.display = 'none'; showhide('btnRunSpeedtest',true); } - else if(spdteststatus == 'NoSwap'){ + else if (spdteststatus == 'NoSwap') + { clearInterval(myinterval); showhide('imgSpdTest',false); document.getElementById('spdtest_text').innerHTML = 'No Swap file configured/detected'; @@ -1280,23 +1312,31 @@ function PostSpeedTest() ClearSpdTableRows(); currentNoCharts = 0; $('#Time_Format').val(GetCookie('Time_Format', 'number')); - get_statstitle_file(); - setTimeout(get_interfaces_file,3000); + getStatsTitleFile(); + setTimeout(getInterfacesFile,3000); } -function RunSpeedtest(){ +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +function RunSpeedtest() +{ showhide('btnRunSpeedtest',false); + showhide('databaseSize_text',false); $('#spdtest_output').html(''); var spdtestservers = ''; - if(document.form.spdtest_serverpref.value == 'onetime'){ - if(document.form.spdtest_enabled.value == 'All'){ + if (document.form.spdtest_serverpref.value == 'onetime') + { + if (document.form.spdtest_enabled.value == 'All') + { $.each($('select[name^=spdtest_serverprefselect]'),function(){ spdtestservers += this.value.substring(0,this.value.indexOf('|'))+'+'; }); spdtestservers = spdtestservers.slice(0,-1); } - else{ + else + { spdtestservers = document.form.spdtest_serverprefselect.value.substring(0,document.form.spdtest_serverprefselect.value.indexOf('|')); } } @@ -1309,38 +1349,54 @@ function RunSpeedtest(){ var myinterval; var intervalclear = false; -function StartSpeedTestInterval(){ +function StartSpeedTestInterval() +{ intervalclear = false; myinterval = setInterval(update_spdtest,1000); } -function SaveConfig(){ - if(Validate_All()){ +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +function SaveConfig() +{ + if (validateAll()) + { $('[name*=spdmerlin_]').prop('disabled',false); - for(var i = 0; i < interfacescomplete.length; i++){ - $('#spdmerlin_iface_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#spdmerlin_iface_enabled_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); - $('#spdmerlin_usepreferred_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#spdmerlin_usepreferred_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); - $('#changepref_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#changepref_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); - } - if(document.form.schedulemode.value == 'EveryX'){ - if(document.form.everyxselect.value == 'hours'){ - var everyxvalue = document.form.everyxvalue.value*1; - document.form.spdmerlin_schmins.value = 0; - if(everyxvalue == 24){ - document.form.spdmerlin_schhours.value = 0; + for (var indx = 0; indx < interfacescomplete.length; indx++) + { + $('#spdmerlin_iface_enabled_'+interfacescomplete[indx].toLowerCase()).prop('disabled',false); + $('#spdmerlin_iface_enabled_'+interfacescomplete[indx].toLowerCase()).removeClass('disabled'); + $('#spdmerlin_usepreferred_'+interfacescomplete[indx].toLowerCase()).prop('disabled',false); + $('#spdmerlin_usepreferred_'+interfacescomplete[indx].toLowerCase()).removeClass('disabled'); + $('#changepref_'+interfacescomplete[indx].toLowerCase()).prop('disabled',false); + $('#changepref_'+interfacescomplete[indx].toLowerCase()).removeClass('disabled'); + } + + if (document.form.spdmerlin_automaticmode.value === 'true') + { + if (document.form.schedulemode.value === 'EveryX') + { + if (document.form.everyxselect.value === 'hours') + { + var everyxvalue = document.form.everyxvalue.value*1; + document.form.spdmerlin_schmins.value = 0; + if (everyxvalue == 24) + { document.form.spdmerlin_schhours.value = 0; } + else + { document.form.spdmerlin_schhours.value = '*/'+everyxvalue; } } - else{ - document.form.spdmerlin_schhours.value = '*/'+everyxvalue; + else if (document.form.everyxselect.value === 'minutes') + { + document.form.spdmerlin_schhours.value = '*'; + var everyxvalue = document.form.everyxvalue.value*1; + document.form.spdmerlin_schmins.value = '*/'+everyxvalue; } } - else if(document.form.everyxselect.value == 'minutes'){ - document.form.spdmerlin_schhours.value = '*'; - var everyxvalue = document.form.everyxvalue.value*1; - document.form.spdmerlin_schmins.value = '*/'+everyxvalue; - } + } + else + { + AutomaticInterfaceEnableDisable($('#spdmerlin_auto_'+document.form.spdmerlin_automaticmode.value)[0]); } document.getElementById('amng_custom').value = JSON.stringify($('form').serializeObject()); document.form.action_script.value = 'start_spdmerlinconfig'; @@ -1348,12 +1404,12 @@ function SaveConfig(){ showLoading(); document.form.submit(); } - else{ - return false; - } + else + { return false; } } -function GetVersionNumber(versiontype){ +function GetVersionNumber(versiontype) +{ var versionprop; if(versiontype == 'local'){ versionprop = custom_settings.spdmerlin_version_local; @@ -1370,7 +1426,8 @@ function GetVersionNumber(versiontype){ } } -function get_autobw_file(){ +function get_autobw_file() +{ $.ajax({ url: '/ext/spdmerlin/autobwoutfile.htm', dataType: 'text', @@ -1382,72 +1439,95 @@ function get_autobw_file(){ }); } -function get_conf_file(){ +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +function getConfigFile() +{ $.ajax({ url: '/ext/spdmerlin/config.htm', dataType: 'text', error: function(xhr){ - setTimeout(get_conf_file,1000); + setTimeout(getConfigFile, 1000); }, - success: function(data){ - var configdata=data.split('\n'); + success: function(data) + { + let settingname, settingvalue; + var configdata = data.split('\n'); configdata = configdata.filter(Boolean); - - for(var i = 0; i < configdata.length; i++){ - let settingname = configdata[i].split('=')[0].toLowerCase(); - let settingvalue = configdata[i].split('=')[1].replace(/(\r\n|\n|\r)/gm,''); - - if(configdata[i].indexOf('SCHDAYS') != -1){ - if(settingvalue == '*'){ - for(var i2 = 0; i2 < daysofweek.length; i2++){ + + for (var indx = 0; indx < configdata.length; indx++) + { + if (configdata[indx].length === 0 || configdata[indx].match('^[ ]*#') !== null) + { continue; } //Skip comments & empty lines// + + settingname = configdata[indx].split('=')[0]; + settingvalue = configdata[indx].split('=')[1].replace(/(\r\n|\n|\r)/gm,''); + + if (settingname.match(/^JFFS_MSGLOGTIME/) != null) + { continue; } //Skip this config setting// + + settingname = settingname.toLowerCase(); + + if (configdata[indx].indexOf('SCHDAYS') != -1) + { + if (settingvalue == '*') + { + for (var i2 = 0; i2 < daysofweek.length; i2++){ $('#spdmerlin_'+daysofweek[i2].toLowerCase()).prop('checked',true); } } - else{ + else + { var schdayarray = settingvalue.split(','); - for(var i2 = 0; i2 < schdayarray.length; i2++){ + for (var i2 = 0; i2 < schdayarray.length; i2++){ $('#spdmerlin_'+schdayarray[i2].toLowerCase()).prop('checked',true); } } } - else if(configdata[i].indexOf('USEPREFERRED') != -1){ - if(settingvalue == 'true'){ - eval('document.form.spdmerlin_'+settingname).checked = true; - } - else if(settingvalue == 'false'){ - eval('document.form.spdmerlin_'+settingname).checked = false; - } + else if (configdata[indx].indexOf('USEPREFERRED') != -1) + { + if (settingvalue === 'true') + { eval('document.form.spdmerlin_'+settingname).checked = true; } + else if(settingvalue === 'false') + { eval('document.form.spdmerlin_'+settingname).checked = false; } } - else if(configdata[i].indexOf('PREFERREDSERVER') != -1){ - $('#span_spdmerlin_'+settingname).html(configdata[i].split('=')[0].split('_')[1]+' - '+settingvalue); + else if (configdata[indx].indexOf('PREFERREDSERVER') != -1){ + $('#span_spdmerlin_'+settingname).html(configdata[indx].split('=')[0].split('_')[1]+' - '+settingvalue); } - else if(configdata[i].indexOf('PREFERRED') == -1){ + else if (configdata[indx].indexOf('PREFERRED') == -1){ eval('document.form.spdmerlin_'+settingname).value = settingvalue; } - - if(configdata[i].indexOf('AUTOMATED') != -1){ - AutomaticInterfaceEnableDisable($('#spdmerlin_auto_'+document.form.spdmerlin_automated.value)[0]); + + if (configdata[indx].indexOf('AUTOMATICMODE') != -1){ + AutomaticInterfaceEnableDisable($('#spdmerlin_auto_'+document.form.spdmerlin_automaticmode.value)[0]); } - - if(configdata[i].indexOf('AUTOBW') != -1){ + + if (configdata[indx].indexOf('AUTOBW') != -1){ AutoBWEnableDisable($('#spdmerlin_autobw_'+document.form.spdmerlin_autobw_enabled.value)[0]); } - - if(configdata[i].indexOf('SPEEDTESTBINARY') != -1){ + + if (configdata[indx].indexOf('SPEEDTESTBINARY') != -1){ speedtestbinary = settingvalue; } } - if($('[name=spdmerlin_schhours]').val().indexOf('/') != -1 && $('[name=spdmerlin_schmins]').val() == 0){ + document.getElementById('theDaysToKeepText').textContent = theDaysToKeepTXT; + document.getElementById('theLastXResultsText').textContent = theLastXResultsTXT; + + if ($('[name=spdmerlin_schhours]').val().indexOf('/') != -1 && $('[name=spdmerlin_schmins]').val() == 0) + { document.form.schedulemode.value = 'EveryX'; document.form.everyxselect.value = 'hours'; document.form.everyxvalue.value = $('[name=spdmerlin_schhours]').val().split('/')[1]; } - else if($('[name=spdmerlin_schmins]').val().indexOf('/') != -1 && $('[name=spdmerlin_schhours]').val() == '*'){ + else if($('[name=spdmerlin_schmins]').val().indexOf('/') != -1 && $('[name=spdmerlin_schhours]').val() == '*') + { document.form.schedulemode.value = 'EveryX'; document.form.everyxselect.value = 'minutes'; document.form.everyxvalue.value = $('[name=spdmerlin_schmins]').val().split('/')[1]; } - else{ + else + { document.form.schedulemode.value = 'Custom'; } ScheduleModeToggle($('#schmode_'+$('[name=schedulemode]:checked').val().toLowerCase())[0]); @@ -1456,163 +1536,217 @@ function get_conf_file(){ get_autobw_file(); } -function get_interfaces_file(){ +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Mar-02] **/ +/**----------------------------------------**/ +function getInterfacesFile() +{ $.ajax({ url: '/ext/spdmerlin/interfaces_user.htm', dataType: 'text', error: function(xhr){ - setTimeout(get_interfaces_file,1000); + setTimeout(getInterfacesFile,1000); }, - success: function(data){ + success: function(data) + { showhide('spdtest_text',false); showhide('imgSpdTest',false); showhide('btnRunSpeedtest',true); + showhide('databaseSize_text',true); + var interfaces = data.split('\n'); + let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel; interfaces = interfaces.filter(Boolean); interfacelist = ''; interfacescomplete = []; interfacesdisabled = []; - + var interfacecharttablehtml='
 
'; interfacecharttablehtml += ''; interfacecharttablehtml += ''; interfacecharttablehtml += ''; interfacecharttablehtml += ''; interfacecharttablehtml += '
Interfaces (click to expand/collapse)
'; - - var interfaceconfigtablehtml = '
Interfaces to use for automatic speedtests'; - + + var interfaceconfigtablehtml = '
Interfaces to use for automatic speedtests'; var prefserverconfigtablehtml = '
Interfaces that use a preferred server'; - var prefserverselecttablehtml = '
Preferred servers for interfaces'; - + var speedtestifaceconfigtablehtml = '
Interfaces to use for manual speedtest'; speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; - - var interfacecount=interfaces.length; - for(var i = 0; i < interfacecount; i++){ - var interfacename = ''; - if(interfaces[i].indexOf('#') != -1){ - interfacename = interfaces[i].substring(0,interfaces[i].indexOf('#')).trim(); + + const ifaceMaxCount = interfaces.length; + const breakStartIndex = 1, breakStopIndex = 5; + + for (var ifaceIndx = 0; ifaceIndx < ifaceMaxCount; ifaceIndx++) + { + interfacename = ''; + if (interfaces[ifaceIndx].indexOf('#') !== -1) + { + interfacename = interfaces[ifaceIndx].substring(0,interfaces[ifaceIndx].indexOf('#')).trim(); interfacescomplete.push(interfacename); - var interfacedisabled = ''; - var ifacelabel = interfacename.toUpperCase(); + ifaceNameUpper = interfacename.toUpperCase(); + ifaceNameLower = interfacename.toLowerCase(); + ifaceLabel = ifaceNameUpper; var changelabel = 'Change?'; - if(interfaces[i].indexOf('interface not up') != -1){ + var interfacedisabled = ''; + + if (interfaces[ifaceIndx].indexOf('interface not up') !== -1) + { interfacesdisabled.push(interfacename); interfacedisabled = 'disabled'; - ifacelabel = ''+interfacename.toUpperCase()+''; - changelabel = 'Change?'; + ifaceLabel = ''+ifaceNameUpper+''; + changelabel = 'Change?'; } - interfaceconfigtablehtml += ''; - interfaceconfigtablehtml += ''; - - prefserverconfigtablehtml += ''; - prefserverconfigtablehtml += ''; - - prefserverselecttablehtml += ''+interfacename.toUpperCase()+':
'; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += '
'; - - speedtestifaceconfigtablehtml += ''; - speedtestifaceconfigtablehtml += ''; + + // For AUTOMATIC Speedtests // + if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + interfaceconfigtablehtml += ''; + interfaceconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + + // For PREFERRED Servers // + if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + prefserverconfigtablehtml += ''; + prefserverconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + + // Select a Preferred Server // + prefserverselecttablehtml += ''+ifaceNameUpper+':
'; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += '
'; + + // For MANUAL Speedtests // + if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } + speedtestifaceconfigtablehtml += ''; + speedtestifaceconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } } - else{ - interfacename = interfaces[i].trim(); + else + { + interfacename = interfaces[ifaceIndx].trim(); interfacescomplete.push(interfacename); - - interfaceconfigtablehtml += ''; - interfaceconfigtablehtml += ''; - - prefserverconfigtablehtml += ''; - prefserverconfigtablehtml += ''; - - prefserverselecttablehtml += ''+interfacename.toUpperCase()+':
'; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; - prefserverselecttablehtml += '
'; - - speedtestifaceconfigtablehtml += ''; - speedtestifaceconfigtablehtml += ''; + ifaceNameUpper = interfacename.toUpperCase(); + ifaceNameLower = interfacename.toLowerCase(); + ifaceLabel = ''+ifaceNameUpper+''; + + // For AUTOMATIC Speedtests // + if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + interfaceconfigtablehtml += ''; + interfaceconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + + // For PREFERRED Servers // + if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + prefserverconfigtablehtml += ''; + prefserverconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + + // Select a Preferred Server // + prefserverselecttablehtml += ''+ifaceNameUpper+':
'; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; + prefserverselecttablehtml += '
'; + + // For MANUAL Speedtests // + if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } + speedtestifaceconfigtablehtml += ''; + speedtestifaceconfigtablehtml += ''; + if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } } - + interfacecharttablehtml += BuildInterfaceTable(interfacename); - interfacelist += interfacename+','; } - + interfacecharttablehtml += '
'; - interfaceconfigtablehtml += ''; - prefserverconfigtablehtml += ''; - prefserverselecttablehtml += ''; - speedtestifaceconfigtablehtml += ''; - + $('#rowautomatedtests').after(prefserverselecttablehtml); $('#rowautomatedtests').after(prefserverconfigtablehtml); $('#rowautomatedtests').after(interfaceconfigtablehtml); $('#thead_manualspeedtests').after(speedtestifaceconfigtablehtml); - + GenerateManualSpdTestServerPrefSelect(); document.form.spdtest_serverpref.value = 'auto'; - - if(interfacelist.charAt(interfacelist.length-1) == ','){ - interfacelist = interfacelist.slice(0,-1); - } - + + if (interfacelist.charAt(interfacelist.length-1) == ',') + { interfacelist = interfacelist.slice(0,-1); } + $('#table_buttons2').after(interfacecharttablehtml); maxNoCharts = interfacelist.split(',').length*3*3*2; RedrawAllCharts(); - + AddEventHandlers(); - + var interfacetextarray = interfacelist.split(','); - for(var i = 0; i < interfacetextarray.length; i++){ - $('#sortTable'+interfacetextarray[i]).empty(); - $('#sortTable'+interfacetextarray[i]).append(BuildLastXTableNoData(interfacetextarray[i])); - get_lastx_file(interfacetextarray[i]); + for (var indx = 0; indx < interfacetextarray.length; indx++) + { + $('#sortTable'+interfacetextarray[indx]).empty(); + $('#sortTable'+interfacetextarray[indx]).append(BuildLastXTableNoData(interfacetextarray[indx])); + get_lastx_file(interfacetextarray[indx]); } - get_conf_file(); + getConfigFile(); } }); } /**----------------------------------------**/ -/** Modified by Martinski W. [2024-Jul-15] **/ +/** Modified by Martinski W. [2025-Mar-02] **/ /**----------------------------------------**/ -let databaseResetDone = 0; -function get_statstitle_file() +function getStatsTitleFile() { $.ajax({ url: '/ext/spdmerlin/spdtitletext.js', dataType: 'script', error: function(xhr){ - setTimeout(get_statstitle_file, 2000); + setTimeout(getStatsTitleFile, 2000); }, success: function() { SetSPDStatsTitle(); + document.getElementById('databaseSize_text').textContent = 'Database Size: '+sqlDatabaseFileSize; + + if (jffsAvailableSpaceLow.match(/^WARNING[0-9]/) === null) + { + showhide('jffsFreeSpace_LOW',false); + showhide('jffsFreeSpace_NOTE',false); + showhide('jffsFreeSpace_WARN',false); + document.getElementById('jffsFreeSpace_text').textContent = 'JFFS Available: ' + jffsAvailableSpaceStr; + } + else + { + document.getElementById('jffsFreeSpace_text').textContent = 'JFFS Available: '; + document.getElementById('jffsFreeSpace_LOW').textContent = jffsAvailableSpaceStr; + showhide('jffsFreeSpace_LOW',true); + if (document.form.connmon_storagelocation.value === 'jffs') + { showhide('jffsFreeSpace_NOTE',false); showhide('jffsFreeSpace_WARN',true); } + else + { showhide('jffsFreeSpace_WARN',false); showhide('jffsFreeSpace_NOTE',true); } + } + if (databaseResetDone === 1) { ClearSpdTableRows(); currentNoCharts = 0; $('#Time_Format').val(GetCookie('Time_Format', 'number')); - get_interfaces_file(); + getInterfacesFile(); databaseResetDone += 1; } - setTimeout(get_statstitle_file, 4000); + setTimeout(getStatsTitleFile, 4000); } }); } -function get_lastx_file(name){ +function get_lastx_file(name) +{ $.ajax({ url: '/ext/spdmerlin/lastx_'+name+'.htm', dataType: 'text', @@ -1625,11 +1759,13 @@ function get_lastx_file(name){ }); } -function ParseLastXData(name,data){ +function ParseLastXData(name,data) +{ var arraysortlines = data.split('\n'); arraysortlines = arraysortlines.filter(Boolean); window['arraysortlistlines'+name] = []; - for(var i = 0; i < arraysortlines.length; i++){ + for(var i = 0; i < arraysortlines.length; i++) + { try{ var resultfields = arraysortlines[i].split(','); var parsedsortline = new Object(); @@ -1653,7 +1789,8 @@ function ParseLastXData(name,data){ SortTable('sortTable'+name,'arraysortlistlines'+name,eval('sortname'+name)+' '+eval('sortdir'+name).replace('desc','↑').replace('asc','↓').trim(),'sortname'+name,'sortdir'+name); } -function SortTable(tableid,arrayid,sorttext,sortname,sortdir){ +function SortTable(tableid,arrayid,sorttext,sortname,sortdir) +{ window[sortname] = sorttext.replace('↑','').replace('↓','').trim(); var sorttype = 'number'; var sortfield = window[sortname]; @@ -1804,7 +1941,8 @@ function changeAllCharts(e){ } } -function changeChart(e){ +function changeChart(e) +{ value = e.value * 1; name = e.id.substring(0,e.id.indexOf('_')); SetCookie(e.id,value); @@ -1816,20 +1954,45 @@ function changeChart(e){ } } -function SettingHint(hintid){ - var tag_name = document.getElementsByTagName('a'); - for(var i=0;i'; charthtml += ''; @@ -1949,7 +2112,8 @@ function BuildInterfaceTable(name){ return charthtml; } -function AutomaticInterfaceEnableDisable(forminput){ +function AutomaticInterfaceEnableDisable(forminput) +{ var inputname = forminput.name; var inputvalue = forminput.value; var prefix = inputname.substring(0,inputname.lastIndexOf('_')); @@ -1957,8 +2121,10 @@ function AutomaticInterfaceEnableDisable(forminput){ var fieldnames = ['schhours','schmins']; var fieldnames2 = ['schedulemode','everyxselect','everyxvalue']; - if(inputvalue == 'false'){ - for(var i = 0; i < interfacescomplete.length; i++){ + if (inputvalue == 'false') + { + for (var i = 0; i < interfacescomplete.length; i++) + { $('#'+prefix+'_iface_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',true); $('#'+prefix+'_iface_enabled_'+interfacescomplete[i].toLowerCase()).addClass('disabled'); $('#'+prefix+'_usepreferred_'+interfacescomplete[i].toLowerCase()).prop('disabled',true); @@ -1966,21 +2132,27 @@ function AutomaticInterfaceEnableDisable(forminput){ $('#changepref_'+interfacescomplete[i].toLowerCase()).prop('disabled',true); $('#changepref_'+interfacescomplete[i].toLowerCase()).addClass('disabled'); } - for (var i = 0; i < fieldnames.length; i++){ + for (var i = 0; i < fieldnames.length; i++) + { $('input[name='+prefix+'_'+fieldnames[i]+']').addClass('disabled'); $('input[name='+prefix+'_'+fieldnames[i]+']').prop('disabled',true); } - for (var i = 0; i < daysofweek.length; i++){ + for (var i = 0; i < daysofweek.length; i++) + { $('#'+prefix+'_'+daysofweek[i].toLowerCase()).prop('disabled',true); } - for (var i = 0; i < fieldnames2.length; i++){ + for (var i = 0; i < fieldnames2.length; i++) + { $('[name='+fieldnames2[i]+']').addClass('disabled'); $('[name='+fieldnames2[i]+']').prop('disabled',true); } } - else if(inputvalue == 'true'){ - for(var i = 0; i < interfacescomplete.length; i++){ - if(interfacesdisabled.includes(interfacescomplete[i]) == false){ + else if (inputvalue == 'true') + { + for (var i = 0; i < interfacescomplete.length; i++) + { + if (interfacesdisabled.includes(interfacescomplete[i]) == false) + { $('#'+prefix+'_iface_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); $('#'+prefix+'_iface_enabled_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); $('#'+prefix+'_usepreferred_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); @@ -1989,21 +2161,25 @@ function AutomaticInterfaceEnableDisable(forminput){ $('#changepref_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); } } - for (var i = 0; i < fieldnames.length; i++){ + for (var i = 0; i < fieldnames.length; i++) + { $('input[name='+prefix+'_'+fieldnames[i]+']').removeClass('disabled'); $('input[name='+prefix+'_'+fieldnames[i]+']').prop('disabled',false); } - for (var i = 0; i < daysofweek.length; i++){ + for (var i = 0; i < daysofweek.length; i++) + { $('#'+prefix+'_'+daysofweek[i].toLowerCase()).prop('disabled',false); } - for (var i = 0; i < fieldnames2.length; i++){ + for (var i = 0; i < fieldnames2.length; i++) + { $('[name='+fieldnames2[i]+']').removeClass('disabled'); $('[name='+fieldnames2[i]+']').prop('disabled',false); } } } -function ScheduleModeToggle(forminput){ +function ScheduleModeToggle(forminput) +{ var inputname = forminput.name; var inputvalue = forminput.value; @@ -2162,188 +2338,234 @@ function GenerateManualSpdTestServerPrefSelect(){ $('#rowmanualserverpref').after(serverprefhtml); } -function Validate_All(){ +/**-------------------------------------**/ +/** Added by Martinski W. [2025-Mar-02] **/ +/**-------------------------------------**/ +//Between 15 and 365 days, Default: 30// +const theDaysToKeepMIN = 15; +const theDaysToKeepDEF = 30; +const theDaysToKeepMAX = 365; +const theDaysToKeepTXT = `(between ${theDaysToKeepMIN} and ${theDaysToKeepMAX}, default: ${theDaysToKeepDEF})`; + +//Between 5 and 100 results, Default: 10// +const theLastXResultsMIN = 5; +const theLastXResultsDEF = 10; +const theLastXResultsMAX = 100; +const theLastXResultsTXT = `(between ${theLastXResultsMIN} and ${theLastXResultsMAX}, default: ${theLastXResultsDEF})`; + +function validateAll() +{ var validationfailed = false; - - if(! Validate_Number_Setting(document.form.spdmerlin_autobw_sf_down,100,0)) validationfailed=true; - if(! Validate_Number_Setting(document.form.spdmerlin_autobw_sf_up,100,0)) validationfailed=true; - if(! Validate_Number_Setting(document.form.spdmerlin_autobw_threshold_down,100,0)) validationfailed=true; - if(! Validate_Number_Setting(document.form.spdmerlin_autobw_threshold_up,100,0)) validationfailed=true; - if(! Validate_Number_Setting(document.form.spdmerlin_autobw_average_calc,30,1)) validationfailed=true; - - if(document.form.schedulemode.value == 'EveryX'){ - if(! Validate_ScheduleValue(document.form.everyxvalue)) validationfailed=true; - } - else if(document.form.schedulemode.value == 'Custom'){ - if(! Validate_Schedule(document.form.spdmerlin_schhours,'hours')) validationfailed=true; - if(! Validate_Schedule(document.form.spdmerlin_schmins,'mins')) validationfailed=true; - } - - if(validationfailed){ - alert('Validation for some fields failed. Please correct invalid values and try again.'); + + if (!Validate_Number_Setting(document.form.spdmerlin_autobw_sf_down,100,0)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_autobw_sf_up,100,0)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_autobw_threshold_down,100,0)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_autobw_threshold_up,100,0)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_autobw_average_calc,30,1)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_lastxresults, theLastXResultsMAX, theLastXResultsMIN)) + { validationfailed = true; } + if (!Validate_Number_Setting(document.form.spdmerlin_daystokeep, theDaysToKeepMAX, theDaysToKeepMIN)) + { validationfailed = true; } + + if (document.form.spdmerlin_automaticmode.value === 'true') + { + if (document.form.schedulemode.value === 'EveryX') + { + if (!Validate_ScheduleValue(document.form.everyxvalue)) validationfailed=true; + } + else if (document.form.schedulemode.value === 'Custom') + { + if (!Validate_Schedule(document.form.spdmerlin_schhours,'hours')) + { validationfailed = true; } + if (!Validate_Schedule(document.form.spdmerlin_schmins,'mins')) + { validationfailed = true; } + } + } + if (validationfailed) + { + alert('**ERROR**\nValidation for some fields failed.\nPlease correct invalid values and try again.'); return false; } - else{ - return true; - } + else + { return true; } } -function Validate_Schedule(forminput,hoursmins){ +function Validate_Schedule (forminput,hoursmins) +{ var inputname = forminput.name; var inputvalues = forminput.value.split(','); var upperlimit = 0; - - if(hoursmins == 'hours'){ - upperlimit = 23; - } - else if (hoursmins == 'mins'){ - upperlimit = 59; - } - + + if (hoursmins == 'hours') + { upperlimit = 23; } + else if (hoursmins == 'mins') + { upperlimit = 59; } + showhide('btnfixhours',false); showhide('btnfixmins',false); - + var validationfailed = 'false'; - for(var i=0; i < inputvalues.length; i++){ - if(inputvalues[i] == '*' && i == 0){ + for (var i = 0; i < inputvalues.length; i++) + { + if (inputvalues[i] == '*' && i == 0) + { validationfailed = 'false'; } - else if(inputvalues[i] == '*' && i != 0){ + else if (inputvalues[i] == '*' && i != 0) + { validationfailed = 'true'; } - else if(inputvalues[0] == '*' && i > 0){ + else if (inputvalues[0] == '*' && i > 0) + { validationfailed = 'true'; } - else if(inputvalues[i] == ''){ + else if (inputvalues[i] == '') + { validationfailed = 'true'; } - else if(inputvalues[i].startsWith('*/')){ - if(! isNaN(inputvalues[i].replace('*/','')*1)){ - if((inputvalues[i].replace('*/','')*1) > upperlimit || (inputvalues[i].replace('*/','')*1) < 0){ - validationfailed = 'true'; - } - } - else{ - validationfailed = 'true'; - } - } - else if(inputvalues[i].indexOf('-') != -1){ - if(inputvalues[i].startsWith('-')){ - validationfailed = 'true'; + else if (inputvalues[i].startsWith('*/')) + { + if (!isNaN(inputvalues[i].replace('*/','')*1)) + { + if ((inputvalues[i].replace('*/','')*1) > upperlimit || (inputvalues[i].replace('*/','')*1) < 0) + { validationfailed = 'true'; } } - else{ + else + { validationfailed = 'true'; } + } + else if (inputvalues[i].indexOf('-') != -1) + { + if (inputvalues[i].startsWith('-')) + { validationfailed = 'true'; } + else + { var inputvalues2 = inputvalues[i].split('-'); - for(var i2=0; i2 < inputvalues2.length; i2++){ - if(inputvalues2[i2] == ''){ + for (var i2=0; i2 < inputvalues2.length; i2++) + { + if (inputvalues2[i2] == ''){ validationfailed = 'true'; } - else if(! isNaN(inputvalues2[i2]*1)){ - if((inputvalues2[i2]*1) > upperlimit || (inputvalues2[i2]*1) < 0){ + else if (! isNaN(inputvalues2[i2]*1)) + { + if ((inputvalues2[i2]*1) > upperlimit || (inputvalues2[i2]*1) < 0) + { validationfailed = 'true'; } - else if((inputvalues2[i2+1]*1) < (inputvalues2[i2]*1)){ + else if ((inputvalues2[i2+1]*1) < (inputvalues2[i2]*1)) + { validationfailed = 'true'; - if(hoursmins == 'hours'){ - showhide('btnfixhours',true) - } - else if (hoursmins == 'mins'){ - showhide('btnfixmins',true) - } + if (hoursmins == 'hours') + { showhide('btnfixhours',true); } + else if (hoursmins == 'mins') + { showhide('btnfixmins',true); } } } - else{ - validationfailed = 'true'; - } + else + { validationfailed = 'true'; } } } } - else if(! isNaN(inputvalues[i]*1)){ - if((inputvalues[i]*1) > upperlimit || (inputvalues[i]*1) < 0){ - validationfailed = 'true'; - } - } - else{ - validationfailed = 'true'; + else if (! isNaN(inputvalues[i]*1)) + { + if ((inputvalues[i]*1) > upperlimit || (inputvalues[i]*1) < 0) + { validationfailed = 'true'; } } + else + { validationfailed = 'true'; } } - - if(validationfailed == 'true'){ + + if (validationfailed == 'true') + { $(forminput).addClass('invalid'); return false; } - else{ + else + { $(forminput).removeClass('invalid'); return true; } } -function Validate_ScheduleValue(forminput){ +function Validate_ScheduleValue(forminput) +{ var inputname = forminput.name; var inputvalue = forminput.value*1; - + var upperlimit = 0; var lowerlimit = 1; - var unittype = $('#everyxselect').val(); - - if(unittype == 'hours'){ - upperlimit = 24; - } - else if(unittype == 'minutes'){ - upperlimit = 30; - } - - if(inputvalue > upperlimit || inputvalue < lowerlimit || forminput.value.length < 1){ + + if (unittype == 'hours') + { upperlimit = 24; } + else if (unittype == 'minutes') + { upperlimit = 30; } + + if (inputvalue > upperlimit || inputvalue < lowerlimit || forminput.value.length < 1) + { $(forminput).addClass('invalid'); return false; } - else{ + else + { $(forminput).removeClass('invalid'); return true; } } -function Validate_Number_Setting(forminput,upperlimit,lowerlimit){ +function Validate_Number_Setting(forminput,upperlimit,lowerlimit) +{ var inputname = forminput.name; var inputvalue = forminput.value*1; - if(inputvalue > upperlimit || inputvalue < lowerlimit){ + if (inputvalue > upperlimit || inputvalue < lowerlimit) + { $(forminput).addClass('invalid'); return false; } - else{ + else + { $(forminput).removeClass('invalid'); return true; } } -function Format_Number_Setting(forminput){ +function Format_Number_Setting(forminput) +{ var inputname = forminput.name; var inputvalue = forminput.value*1; - if(forminput.value.length == 0 || inputvalue == NaN){ - return false; - } - else{ + if (forminput.value.length == 0 || inputvalue == NaN) + { return false; } + else + { forminput.value = parseInt(forminput.value); return true; } } -function FixCron(hoursmins){ - if(hoursmins == 'hours'){ +function FixCron(hoursmins) +{ + if (hoursmins == 'hours') + { var origvalue = document.form.spdmerlin_schhours.value; document.form.spdmerlin_schhours.value = origvalue.split('-')[0]+'-23,0-'+origvalue.split('-')[1]; Validate_Schedule(document.form.spdmerlin_schhours,'hours'); } - else if(hoursmins == 'mins'){ + else if (hoursmins == 'mins') + { var origvalue = document.form.spdmerlin_schmins.value; document.form.spdmerlin_schmins.value = origvalue.split('-')[0]+'-59,0-'+origvalue.split('-')[1]; Validate_Schedule(document.form.spdmerlin_schmins,'mins'); } } -function AddEventHandlers(){ +function AddEventHandlers() +{ $('.collapsible-jquery').off('click').on('click',function(){ $(this).siblings().toggle('fast',function(){ if($(this).css('display') == 'none'){ From 287b84e00774df1799e6da8ba991c22366dcb63c Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Tue, 4 Mar 2025 05:48:35 -0800 Subject: [PATCH 2/6] Fine-Tuning Alignment n WebGUI Fine-tuned the margins on the WebGUI page to make interfaces checkboxes and radio button align vertically as needed. --- README.md | 2 +- spdstats_www.asp | 4 ++-- spdstats_www.js | 25 +++++++++++++++++-------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7835723..afbfefa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Shellcheck](https://github.com/jackyaz/spdMerlin/actions/workflows/shellcheck.yml/badge.svg) ## v4.4.6 -### Updated on 2025-Mar-03 +### Updated on 2025-Mar-04 ## About spdMerlin is an internet speedtest and monitoring tool for AsusWRT Merlin with charts for daily, weekly and monthly summaries. It tracks download/upload bandwidth as well as latency, jitter and packet loss. diff --git a/spdstats_www.asp b/spdstats_www.asp index 180609b..a45fca6 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -33,7 +33,7 @@ p{font-weight:bolder}thead.collapsible-jquery{color:#fff;padding:0;width:100%;bo diff --git a/spdstats_www.js b/spdstats_www.js index e28abb6..c75db7b 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1537,7 +1537,7 @@ function getConfigFile() } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Mar-02] **/ +/** Modified by Martinski W. [2025-Mar-04] **/ /**----------------------------------------**/ function getInterfacesFile() { @@ -1555,7 +1555,8 @@ function getInterfacesFile() showhide('databaseSize_text',true); var interfaces = data.split('\n'); - let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel; + const styleLeftMarginIndx = [2, 3, 4, 5]; + let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle; interfaces = interfaces.filter(Boolean); interfacelist = ''; interfacescomplete = []; @@ -1588,6 +1589,7 @@ function getInterfacesFile() interfacescomplete.push(interfacename); ifaceNameUpper = interfacename.toUpperCase(); ifaceNameLower = interfacename.toLowerCase(); + ifaceStyle = 'style="margin-left:0px;"'; ifaceLabel = ifaceNameUpper; var changelabel = 'Change?'; var interfacedisabled = ''; @@ -1602,13 +1604,15 @@ function getInterfacesFile() // For AUTOMATIC Speedtests // if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - interfaceconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + interfaceconfigtablehtml += ''; interfaceconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - prefserverconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + prefserverconfigtablehtml += ''; prefserverconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } @@ -1621,7 +1625,8 @@ function getInterfacesFile() // For MANUAL Speedtests // if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - speedtestifaceconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } } @@ -1631,17 +1636,20 @@ function getInterfacesFile() interfacescomplete.push(interfacename); ifaceNameUpper = interfacename.toUpperCase(); ifaceNameLower = interfacename.toLowerCase(); + ifaceStyle = 'style="margin-left:0px;"'; ifaceLabel = ''+ifaceNameUpper+''; // For AUTOMATIC Speedtests // if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - interfaceconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + interfaceconfigtablehtml += ''; interfaceconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - prefserverconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + prefserverconfigtablehtml += ''; prefserverconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } @@ -1654,7 +1662,8 @@ function getInterfacesFile() // For MANUAL Speedtests // if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - speedtestifaceconfigtablehtml += ''; + if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } } From 8d202224408d41944f7755eb150f58dc5e07d686 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 8 Mar 2025 03:33:00 -0800 Subject: [PATCH 3/6] Fixes to WireGuard Connection Status Modified code to correctly detect when a WireGuard interface is up (connected) or down (disconnected) in addition to being not enabled. Since a WireGuard connection is essentially stateless, the new code improves on the detection method to provide the correct status so the user can select the desired interface to run a speed test. NOTE: This fix was provided by @ExtremeFiretop. --- README.md | 2 +- spdmerlin.sh | 94 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index afbfefa..8b8d9e6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Shellcheck](https://github.com/jackyaz/spdMerlin/actions/workflows/shellcheck.yml/badge.svg) ## v4.4.6 -### Updated on 2025-Mar-04 +### Updated on 2025-Mar-08 ## About spdMerlin is an internet speedtest and monitoring tool for AsusWRT Merlin with charts for daily, weekly and monthly summaries. It tracks download/upload bandwidth as well as latency, jitter and packet loss. diff --git a/spdmerlin.sh b/spdmerlin.sh index c2df6e7..aa7d71a 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -13,7 +13,7 @@ ## https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Mar-03 +# Last Modified: 2025-Mar-08 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -499,6 +499,56 @@ Create_Dirs() fi } +############################################################## +# Function: Check_WG_Interface +# Checks if wgcX is enabled in nvram AND +# verifies that it had a handshake within THRESHOLD seconds. +# Returns 0 if "connected" (up), 1 otherwise (down). +# Added by ExtremeFiretop [2025-Mar-07] +############################################################## +Check_WG_Interface() +{ + index="$1" # e.g. 1 => wgc1 # + threshold=180 # 180-second cutoff for "connected" # + iface="wgc${index}" + + # First confirm NVRAM says it is enabled # + # wgc1_enable = 1 => on, 0 => off # + if [ "$(nvram get wgc${index}_enable)" != "1" ] + then # NOT enabled # + return 1 + fi + + # Attempt to read the handshake time from 'wg' # + # If no handshake line, consider it "down" # + handshake_line="$(wg show "$iface" latest-handshakes 2>/dev/null | head -n1)" + [ -z "$handshake_line" ] && return 1 + + # Extract numeric epoch from the second column # + timestamp="$(echo "$handshake_line" | awk '{print $2}')" + # If no timestamp or if it’s explicitly zero, treat as "disconnected" # + if [ -z "$timestamp" ] || [ "$timestamp" = "0" ] + then + return 1 + fi + + # Compare to current time # + nowtime="$(date +%s)" + elapsed="$((nowtime - timestamp))" + + # If handshake was within threshold, call it "up" # + if [ "$elapsed" -ge 0 ] && [ "$elapsed" -le "$threshold" ] + then + return 0 + fi + + # Otherwise, treat as "down" # + return 1 +} + +##------------------------------------------## +## Modified by ExtremeFiretop [2025-Mar-07] ## +##------------------------------------------## Create_Symlinks() { printf "WAN\n" > "$SCRIPT_INTERFACES" @@ -520,11 +570,10 @@ Create_Symlinks() for index in 1 2 3 4 5 do - comment="" - if [ ! -f "/sys/class/net/wgc$index/operstate" ] || \ - [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ] + comment=" #excluded - interface not up#" + if Check_WG_Interface "$index" then - comment=" #excluded - interface not up#" + comment="" # empty comment => interface is included # fi if [ "$index" -lt 5 ]; then printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" @@ -650,9 +699,9 @@ Conf_FromSettings() fi } -##----------------------------------------## -## Modified by Martinski W. [2025-Mar-02] ## -##----------------------------------------## +##------------------------------------------## +## Modified by ExtremeFiretop [2025-Mar-07] ## +##------------------------------------------## Interfaces_FromSettings() { SETTINGSFILE="/jffs/addons/custom_settings.txt" @@ -680,13 +729,12 @@ Interfaces_FromSettings() for index in 1 2 3 4 5 do - comment=" #excluded#" - if [ ! -f "/sys/class/net/wgc$index/operstate" ] || \ - [ "$(cat "/sys/class/net/wgc$index/operstate")" = "down" ] + comment=" #excluded - interface not up#" + if Check_WG_Interface "$index" then - comment=" #excluded - interface not up#" + comment=" #excluded#" fi - printf "WGVPN%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" done echo "" > "$SCRIPT_INTERFACES_USER" @@ -2470,7 +2518,7 @@ Run_Speedtest() Auto_ServiceEvent create 2>/dev/null Shortcut_Script create ScriptStorageLocation load - Create_Symlinks + Create_Symlinks force mode="$1" if [ $# -lt 2 ] || [ -z "$2" ] @@ -3624,7 +3672,7 @@ MainMenu() c) Generate_Interface_List printf "\n" - Create_Symlinks + Create_Symlinks force printf "\n" break ;; @@ -3895,7 +3943,7 @@ Menu_Startup() printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load - Create_Symlinks + Create_Symlinks force Auto_Startup create 2>/dev/null if AutomaticMode check then Auto_Cron create 2>/dev/null @@ -5295,19 +5343,21 @@ then opkg update opkg install sqlite3-cli fi - + Create_Dirs Conf_Exists - if [ "$(SpeedtestBinary check)" = "builtin" ]; then + if [ "$(SpeedtestBinary check)" = "builtin" ] + then printf "/usr/sbin/ookla" > /tmp/spdmerlin-binary - elif [ "$(SpeedtestBinary check)" = "external" ]; then + elif [ "$(SpeedtestBinary check)" = "external" ] + then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load - Create_Symlinks - + Create_Symlinks force + Process_Upgrade - + Auto_Startup create 2>/dev/null if AutomaticMode check then Auto_Cron create 2>/dev/null From b792919ecefe3e55d59542463cf1f8c4ae0deb82 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 8 Mar 2025 04:11:30 -0800 Subject: [PATCH 4/6] Fixed WireGuard name. Fixed the correct capital letters for WireGuard. --- spdstats_www.asp | 4 ++-- spdstats_www.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spdstats_www.asp b/spdstats_www.asp index a45fca6..ecb8cb9 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -33,7 +33,7 @@ p{font-weight:bolder}thead.collapsible-jquery{color:#fff;padding:0;width:100%;bo diff --git a/spdstats_www.js b/spdstats_www.js index c75db7b..d7475b9 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1977,7 +1977,7 @@ function SettingHint (hintID, formField) if (hintID === 1) { if (formField.name.match(/^WGVPN[1-9]/) !== null) - { hintMsg = 'Wireguard interface is not enabled.'; } + { hintMsg = 'WireGuard interface is not enabled.'; } else if (formField.name.match(/^VPNC[1-9]/) !== null) { hintMsg = 'OpenVPN client interface is not enabled.'; } else @@ -1986,7 +1986,7 @@ function SettingHint (hintID, formField) else if (hintID === 2) { if (formField.name.match(/^WGVPN[1-9]/) !== null) - { hintMsg = 'Wireguard interface is enabled.'; } + { hintMsg = 'WireGuard interface is enabled.'; } else if (formField.name.match(/^VPNC[1-9]/) !== null) { hintMsg = 'OpenVPN client interface is enabled.'; } else From a9b6c5f0e1580c78b0b9c6d5c38743199f712433 Mon Sep 17 00:00:00 2001 From: Joel Samson Date: Sat, 8 Mar 2025 12:02:38 -0500 Subject: [PATCH 5/6] Update spdmerlin.sh --- spdmerlin.sh | 78 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/spdmerlin.sh b/spdmerlin.sh index aa7d71a..74d3f8c 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -1102,26 +1102,56 @@ Get_Interface_From_Name() echo "$IFACE" } -##----------------------------------------## -## Modified by Martinski W. [2025-Mar-02] ## -##----------------------------------------## -Set_Interface_State() -{ - interfaceline="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - if echo "$interfaceline" | grep -qE "^(VPN|WGVPN)" - then - if echo "$interfaceline" | grep -q "#excluded" - then - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] - then - sed -i "$1"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" - else - sed -i "$1"'s/ #excluded - interface not up#/ #excluded#/' "$SCRIPT_INTERFACES_USER" - fi - fi - fi +##-------------------------------------==---## +## Modified by ExtremeFiretop [2025-Mar-08] ## +##------------------------------------------## +Set_Interface_State(){ + interfaceline="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + + # Only process lines that begin with VPNC or WGVPN + if echo "$interfaceline" | grep -qE "^(VPNC|WGVPN)"; then + IFACE_NAME="$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" + IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" + + # Check if the interface is up vs down + # For WGVPN => use Check_WG_Interface(index) + # For VPNC => check /sys/class/net//operstate + interface_is_up=false + if echo "$IFACE_NAME" | grep -q "WGVPN"; then + # Extract the numeric suffix (e.g. WGVPN3 => index=3) + index="$(echo "$IFACE_NAME" | sed 's/[^0-9]//g')" + if Check_WG_Interface "$index"; then + interface_is_up=true + fi + else + # This is an OpenVPN client + if [ -f "/sys/class/net/$IFACE_LOWER/operstate" ] && + [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "up" ]; then + interface_is_up=true + fi + fi + + # Decide how to update the #excluded marker based on up/down + if echo "$interfaceline" | grep -q "#excluded"; then + # The user has explicitly excluded this interface at some point + if [ "$interface_is_up" = true ]; then + # If it was #excluded - interface not up#, strip off the suffix + sed -i "$1 s/#excluded - interface not up#/#excluded#/" "$SCRIPT_INTERFACES_USER" + else + # The interface is down: ensure we have "#excluded - interface not up#" + if echo "$interfaceline" | grep -q "#excluded#$"; then + sed -i "$1 s/#excluded$/#excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" + fi + fi + + else + # No “#excluded” marker => user wanted it included + if [ "$interface_is_up" = false ]; then + # If it’s down, automatically exclude it with “- interface not up#” + sed -i "$1 s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" + fi + fi + fi } ##----------------------------------------## @@ -2518,7 +2548,7 @@ Run_Speedtest() Auto_ServiceEvent create 2>/dev/null Shortcut_Script create ScriptStorageLocation load - Create_Symlinks force + Create_Symlinks mode="$1" if [ $# -lt 2 ] || [ -z "$2" ] @@ -3672,7 +3702,7 @@ MainMenu() c) Generate_Interface_List printf "\n" - Create_Symlinks force + Create_Symlinks printf "\n" break ;; @@ -3943,7 +3973,7 @@ Menu_Startup() printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load - Create_Symlinks force + Create_Symlinks Auto_Startup create 2>/dev/null if AutomaticMode check then Auto_Cron create 2>/dev/null @@ -5354,7 +5384,7 @@ then printf "%s" "$OOKLA_DIR/speedtest" > /tmp/spdmerlin-binary fi ScriptStorageLocation load - Create_Symlinks force + Create_Symlinks Process_Upgrade From 028c4b8b32a235e8fbad42f1405c88f28ad78c22 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sun, 9 Mar 2025 04:06:58 -0700 Subject: [PATCH 6/6] More Fixes to WireGuard Connection Status Fixed 5 more places where checking for interface connection status to include WireGuard interfaces. --- README.md | 2 +- spdmerlin.sh | 356 +++++++++++++++++++++++++++++---------------------- 2 files changed, 205 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 8b8d9e6..4986f1c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Shellcheck](https://github.com/jackyaz/spdMerlin/actions/workflows/shellcheck.yml/badge.svg) ## v4.4.6 -### Updated on 2025-Mar-08 +### Updated on 2025-Mar-09 ## About spdMerlin is an internet speedtest and monitoring tool for AsusWRT Merlin with charts for daily, weekly and monthly summaries. It tracks download/upload bandwidth as well as latency, jitter and packet loss. diff --git a/spdmerlin.sh b/spdmerlin.sh index 74d3f8c..ec947b5 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -13,7 +13,7 @@ ## https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Mar-08 +# Last Modified: 2025-Mar-09 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -499,33 +499,60 @@ Create_Dirs() fi } +##-------------------------------------## +## Added by Martinski W. [2025-Mar-08] ## +##-------------------------------------## +## For both OpenVPN and WAN interfaces ## +##-------------------------------------## +_CheckNetClientInterfaceUP_() +{ + if [ $# -eq 0 ] || [ -z "$1" ] ; then return 1 ; fi + + local IFACE_NAME="$1" + + if echo "$1" | grep -qE "^[1-5]$" + then IFACE_NAME="tun1$1" ; fi + + if [ ! -f "/sys/class/net/${IFACE_NAME}/operstate" ] || \ + [ "$(cat "/sys/class/net/${IFACE_NAME}/operstate")" = "down" ] + then return 1 + else return 0 + fi +} + ############################################################## -# Function: Check_WG_Interface -# Checks if wgcX is enabled in nvram AND -# verifies that it had a handshake within THRESHOLD seconds. -# Returns 0 if "connected" (up), 1 otherwise (down). -# Added by ExtremeFiretop [2025-Mar-07] +# Checks if 'wgcX' interface is enabled in NVRAM *AND* +# verifies that it had a handshake within THRESHOLD seconds. +# Returns 0 if "connected" (up), 1 otherwise (down). +#------------------------------------------------------------- +# Modified by Martinski W. [2025-Mar-08] ############################################################## -Check_WG_Interface() +_Check_WG_ClientInterfaceUP_() { - index="$1" # e.g. 1 => wgc1 # - threshold=180 # 180-second cutoff for "connected" # - iface="wgc${index}" + if [ $# -eq 0 ] || [ -z "$1" ] ; then return 1 ; fi + local IFACE_NAME threshold handshakeLine + + if echo "$1" | grep -qE "^wgc[1-5]$" + then IFACE_NAME="$1" + else IFACE_NAME="wgc$1" + fi + + threshold=180 # 180-second cutoff for "connected" # # First confirm NVRAM says it is enabled # # wgc1_enable = 1 => on, 0 => off # - if [ "$(nvram get wgc${index}_enable)" != "1" ] + if [ "$(nvram get ${IFACE_NAME}_enable)" != "1" ] then # NOT enabled # return 1 fi # Attempt to read the handshake time from 'wg' # # If no handshake line, consider it "down" # - handshake_line="$(wg show "$iface" latest-handshakes 2>/dev/null | head -n1)" - [ -z "$handshake_line" ] && return 1 + handshakeLine="$(wg show "$IFACE_NAME" latest-handshakes 2>/dev/null | head -n1)" + [ -z "$handshakeLine" ] && return 1 # Extract numeric epoch from the second column # - timestamp="$(echo "$handshake_line" | awk '{print $2}')" + timestamp="$(echo "$handshakeLine" | awk '{print $2}')" # If no timestamp or if it’s explicitly zero, treat as "disconnected" # if [ -z "$timestamp" ] || [ "$timestamp" = "0" ] then @@ -546,40 +573,34 @@ Check_WG_Interface() return 1 } -##------------------------------------------## -## Modified by ExtremeFiretop [2025-Mar-07] ## -##------------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-08] ## +##----------------------------------------## Create_Symlinks() { printf "WAN\n" > "$SCRIPT_INTERFACES" + local ifaceTagStr + local excludedNotUPstr=" #excluded - interface not up#" + for index in 1 2 3 4 5 do - comment="" - if [ ! -f "/sys/class/net/tun1$index/operstate" ] || \ - [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ] + ifaceTagStr="$excludedNotUPstr" + if _CheckNetClientInterfaceUP_ "$index" then - comment=" #excluded - interface not up#" - fi - if [ "$index" -lt 5 ]; then - printf "VPNC%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" - else - printf "VPNC%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + ifaceTagStr="" #Interface is included# fi + printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done for index in 1 2 3 4 5 do - comment=" #excluded - interface not up#" - if Check_WG_Interface "$index" + ifaceTagStr="$excludedNotUPstr" + if _Check_WG_ClientInterfaceUP_ "$index" then - comment="" # empty comment => interface is included # - fi - if [ "$index" -lt 5 ]; then - printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" - else - printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + ifaceTagStr="" #Interface is included# fi + printf "WGVPN%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done if [ $# -gt 0 ] && [ "$1" = "force" ]; then @@ -598,9 +619,9 @@ Create_Symlinks() fi done < "$SCRIPT_INTERFACES" - interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" + interfaceCount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 - until [ "$COUNTER" -gt "$interfacecount" ] + until [ "$COUNTER" -gt "$interfaceCount" ] do Set_Interface_State "$COUNTER" COUNTER="$((COUNTER + 1))" @@ -699,12 +720,16 @@ Conf_FromSettings() fi } -##------------------------------------------## -## Modified by ExtremeFiretop [2025-Mar-07] ## -##------------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2025-Mar-08] ## +##----------------------------------------## Interfaces_FromSettings() { SETTINGSFILE="/jffs/addons/custom_settings.txt" + + local ifaceTagStr interface_UP + local excludedNotUPstr=" #excluded - interface not up#" + if [ -f "$SETTINGSFILE" ] then if grep -q "spdmerlin_ifaces_enabled" "$SETTINGSFILE" @@ -714,27 +739,26 @@ Interfaces_FromSettings() SETTINGVALUE="$(grep "spdmerlin_ifaces_enabled" "$SETTINGSFILE" | cut -f2 -d' ')" sed -i "\\~spdmerlin_ifaces_enabled~d" "$SETTINGSFILE" - printf "WAN #excluded#\\n" > "$SCRIPT_INTERFACES" + printf "WAN #excluded#\n" > "$SCRIPT_INTERFACES" for index in 1 2 3 4 5 do - comment=" #excluded#" - if [ ! -f "/sys/class/net/tun1$index/operstate" ] || \ - [ "$(cat "/sys/class/net/tun1$index/operstate")" = "down" ] + ifaceTagStr="$excludedNotUPstr" + if _CheckNetClientInterfaceUP_ "$index" then - comment=" #excluded - interface not up#" + ifaceTagStr=" #excluded#" fi - printf "VPNC%s%s\\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done for index in 1 2 3 4 5 do - comment=" #excluded - interface not up#" - if Check_WG_Interface "$index" + ifaceTagStr="$excludedNotUPstr" + if _Check_WG_ClientInterfaceUP_ "$index" then - comment=" #excluded#" + ifaceTagStr=" #excluded#" fi - printf "WGVPN%s%s\n" "$index" "$comment" >> "$SCRIPT_INTERFACES" + printf "WGVPN%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done echo "" > "$SCRIPT_INTERFACES_USER" @@ -747,34 +771,42 @@ Interfaces_FromSettings() fi done < "$SCRIPT_INTERFACES" - interfacecount="$(wc -l < "$SCRIPT_INTERFACES_USER")" + interfaceCount="$(wc -l < "$SCRIPT_INTERFACES_USER")" COUNTER=1 - until [ "$COUNTER" -gt "$interfacecount" ] + until [ "$COUNTER" -gt "$interfaceCount" ] do Set_Interface_State "$COUNTER" COUNTER="$((COUNTER + 1))" done - for iface in $(echo "$SETTINGVALUE" | sed "s/,/ /g") + for IFACEname in $(echo "$SETTINGVALUE" | sed "s/,/ /g") do - ifacelinenumber="$(grep -n "$iface" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" - interfaceline="$(sed "$ifacelinenumber!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + ifacelinenumber="$(grep -n "$IFACEname" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" + interfaceLine="$(sed "$ifacelinenumber!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + IFACE_NAME="$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" + IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" - if echo "$interfaceline" | grep -q "#excluded" + interface_UP=false + if echo "$IFACE_NAME" | grep -q "^WGVPN" then - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + if _Check_WG_ClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + else + if _CheckNetClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + fi + + if echo "$interfaceLine" | grep -q "#excluded" + then + if "$interface_UP" then - sed -i "${ifacelinenumber}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" - else sed -i "${ifacelinenumber}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" sed -i "${ifacelinenumber}s/ #excluded#//" "$SCRIPT_INTERFACES_USER" + else + sed -i "${ifacelinenumber}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi else - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + if ! "$interface_UP" then sed -i "$ifacelinenumber"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" fi @@ -1072,82 +1104,83 @@ Auto_Cron() } ##----------------------------------------## -## Modified by Martinski W. [2025-Mar-02] ## +## Modified by Martinski W. [2025-Mar-08] ## ##----------------------------------------## Get_Interface_From_Name() { - IFACE="" + local IFACEname="" case "$1" in WAN) - if [ "$(nvram get sw_mode)" -ne 1 ]; then - IFACE="br0" - elif [ "$(nvram get wan0_proto)" = "pppoe" ] || [ "$(nvram get wan0_proto)" = "pptp" ] || [ "$(nvram get wan0_proto)" = "l2tp" ]; then - IFACE="ppp0" + if [ "$(nvram get sw_mode)" -ne 1 ] + then + IFACEname="br0" + elif [ "$(nvram get wan0_proto)" = "pppoe" ] || \ + [ "$(nvram get wan0_proto)" = "pptp" ] || \ + [ "$(nvram get wan0_proto)" = "l2tp" ] + then + IFACEname="ppp0" else - IFACE="$(nvram get wan0_ifname)" + IFACEname="$(nvram get wan0_ifname)" fi ;; - VPNC1) IFACE="tun11" ;; - VPNC2) IFACE="tun12" ;; - VPNC3) IFACE="tun13" ;; - VPNC4) IFACE="tun14" ;; - VPNC5) IFACE="tun15" ;; - WGVPN1) IFACE="wgc1" ;; - WGVPN2) IFACE="wgc2" ;; - WGVPN3) IFACE="wgc3" ;; - WGVPN4) IFACE="wgc4" ;; - WGVPN5) IFACE="wgc5" ;; + VPNC1) IFACEname="tun11" ;; + VPNC2) IFACEname="tun12" ;; + VPNC3) IFACEname="tun13" ;; + VPNC4) IFACEname="tun14" ;; + VPNC5) IFACEname="tun15" ;; + WGVPN1) IFACEname="wgc1" ;; + WGVPN2) IFACEname="wgc2" ;; + WGVPN3) IFACEname="wgc3" ;; + WGVPN4) IFACEname="wgc4" ;; + WGVPN5) IFACEname="wgc5" ;; esac - - echo "$IFACE" + echo "$IFACEname" } -##-------------------------------------==---## -## Modified by ExtremeFiretop [2025-Mar-08] ## -##------------------------------------------## -Set_Interface_State(){ - interfaceline="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - - # Only process lines that begin with VPNC or WGVPN - if echo "$interfaceline" | grep -qE "^(VPNC|WGVPN)"; then - IFACE_NAME="$(echo "$interfaceline" | cut -f1 -d"#" | sed 's/ *$//')" +##------------------------------------=---## +## Modified by Martinski W. [2025-Mar-08] ## +##----------------------------------------## +Set_Interface_State() +{ + local interfaceLine interface_UP index + interfaceLine="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + + if echo "$interfaceLine" | grep -qE "^(VPNC|WGVPN)" + then + IFACE_NAME="$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" - # Check if the interface is up vs down - # For WGVPN => use Check_WG_Interface(index) - # For VPNC => check /sys/class/net//operstate - interface_is_up=false - if echo "$IFACE_NAME" | grep -q "WGVPN"; then - # Extract the numeric suffix (e.g. WGVPN3 => index=3) - index="$(echo "$IFACE_NAME" | sed 's/[^0-9]//g')" - if Check_WG_Interface "$index"; then - interface_is_up=true - fi + # Check if interface is 'up' vs 'down' # + interface_UP=false + if echo "$IFACE_NAME" | grep -q "^WGVPN" + then + if _Check_WG_ClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi else - # This is an OpenVPN client - if [ -f "/sys/class/net/$IFACE_LOWER/operstate" ] && - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "up" ]; then - interface_is_up=true - fi + if _CheckNetClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi fi - # Decide how to update the #excluded marker based on up/down - if echo "$interfaceline" | grep -q "#excluded"; then - # The user has explicitly excluded this interface at some point - if [ "$interface_is_up" = true ]; then - # If it was #excluded - interface not up#, strip off the suffix + # Decide how to update the '#excluded' marker based on up/down # + if echo "$interfaceLine" | grep -q "#excluded" + then + # The user has explicitly excluded this interface at some point # + if "$interface_UP" + then + # If it was '#excluded - interface not up#' strip off the suffix # sed -i "$1 s/#excluded - interface not up#/#excluded#/" "$SCRIPT_INTERFACES_USER" else - # The interface is down: ensure we have "#excluded - interface not up#" - if echo "$interfaceline" | grep -q "#excluded#$"; then + # The interface is 'down' so ensure we have "#excluded - interface not up#" # + if echo "$interfaceLine" | grep -q "#excluded#$" + then sed -i "$1 s/#excluded$/#excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi - else - # No “#excluded” marker => user wanted it included - if [ "$interface_is_up" = false ]; then - # If it’s down, automatically exclude it with “- interface not up#” + # No '#excluded' marker => user wanted it included # + if ! "$interface_UP" + then + # If it’s 'down' automatically exclude it with '- interface not up#' # sed -i "$1 s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi @@ -1155,11 +1188,11 @@ Set_Interface_State(){ } ##----------------------------------------## -## Modified by Martinski W. [2025-Mar-03] ## +## Modified by Martinski W. [2025-Mar-08] ## ##----------------------------------------## Generate_Interface_List() { - local ifaceCount ifaceEntryNum interfaceLine + local ifaceCount ifaceEntryNum interfaceLine interface_UP printf "\nRetrieving list of interfaces...\n\n" _GenerateIFaceList_() @@ -1196,25 +1229,34 @@ Generate_Interface_List() PressEnter else interfaceLine="$(sed "$ifaceEntryNum!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - IFACE_LOWER="$(Get_Interface_From_Name "$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" | tr "A-Z" "a-z")" + IFACE_NAME="$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" + IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" + + interface_UP=false + if echo "$IFACE_NAME" | grep -q "^WGVPN" + then + if _Check_WG_ClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + else + if _CheckNetClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + fi if echo "$interfaceLine" | grep -q "#excluded" then - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + if "$interface_UP" then - sed -i "$ifaceEntryNum"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" - else sed -i "$ifaceEntryNum"'s/ #excluded - interface not up#//' "$SCRIPT_INTERFACES_USER" sed -i "$ifaceEntryNum"'s/ #excluded#//' "$SCRIPT_INTERFACES_USER" + else + sed -i "$ifaceEntryNum"'s/ #excluded#/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" fi else - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + if "$interface_UP" then - sed -i "$ifaceEntryNum"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" - else sed -i "$ifaceEntryNum"'s/$/ #excluded#/' "$SCRIPT_INTERFACES_USER" + else + sed -i "$ifaceEntryNum"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" fi fi sed -i 's/ *$//' "$SCRIPT_INTERFACES_USER" @@ -1846,18 +1888,18 @@ GenerateServerList() CONFIG_STRING="-c http://www.speedtest.net/api/embed/vz0azjarf5enop8a/config" LICENSE_STRING="" fi - serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$1")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null - if [ -z "$serverlist" ] + serverList="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$1")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null + if [ -z "$serverList" ] then Print_Output true "Error retrieving server list for for $1" "$CRIT" serverno="exit" return 1 fi - servercount="$(echo "$serverlist" | jq '.servers | length')" + serverCount="$(echo "$serverList" | jq '.servers | length')" COUNTER=1 - until [ "$COUNTER" -gt "$servercount" ] + until [ "$COUNTER" -gt "$serverCount" ] do - serverdetails="$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')|$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" + serverdetails="$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')|$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" printf "%2d) %s\n" "$COUNTER" "$serverdetails" COUNTER="$((COUNTER + 1))" @@ -1867,7 +1909,7 @@ GenerateServerList() while true do - printf "\n${BOLD}Please select a server from the list above [1-%s].${CLEARFORMAT}" "$servercount" + printf "\n${BOLD}Please select a server from the list above [1-%s].${CLEARFORMAT}" "$serverCount" printf "\n${BOLD}Or press ${GRNct}c${CLRct} to enter a known server ID.${CLEARFORMAT}" printf "\n${BOLD}Enter answer:${CLEARFORMAT} " read -r server @@ -1928,13 +1970,13 @@ GenerateServerList() done elif ! Validate_Number "$server" then - printf "\n${ERR}Please enter a valid number [1-%s]${CLEARFORMAT}\n" "$servercount" + printf "\n${ERR}Please enter a valid number [1-%s]${CLEARFORMAT}\n" "$serverCount" else - if [ "$server" -lt 1 ] || [ "$server" -gt "$servercount" ]; then - printf "\n${ERR}Please enter a number between 1 and %s.${CLEARFORMAT}\n" "$servercount" + if [ "$server" -lt 1 ] || [ "$server" -gt "$serverCount" ]; then + printf "\n${ERR}Please enter a number between 1 and %s.${CLEARFORMAT}\n" "$serverCount" else - serverno="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .id')" - servername="$(echo "$serverlist" | jq -r --argjson index "$((server-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" + serverno="$(echo "$serverList" | jq -r --argjson index "$((server-1))" '.servers[$index] | .id')" + servername="$(echo "$serverList" | jq -r --argjson index "$((server-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" printf "\n" break fi @@ -1981,23 +2023,23 @@ GenerateServerList_WebUI() for IFACE_NAME in $IFACELIST do - serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$IFACE_NAME")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null - servercount="$(echo "$serverlist" | jq '.servers | length')" + serverList="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$IFACE_NAME")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null + serverCount="$(echo "$serverList" | jq '.servers | length')" COUNTER=1 - until [ $COUNTER -gt "$servercount" ] + until [ $COUNTER -gt "$serverCount" ] do - printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" + printf "%s|%s\\n" "$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done printf "-----\\n" >> "/tmp/$serverlistfile.tmp" done else - serverlist="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$spdifacename")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null - servercount="$(echo "$serverlist" | jq '.servers | length')" + serverList="$("$SPEEDTEST_BINARY" $CONFIG_STRING --interface="$(Get_Interface_From_Name "$spdifacename")" --servers --format="json" $LICENSE_STRING)" 2>/dev/null + serverCount="$(echo "$serverList" | jq '.servers | length')" COUNTER=1 - until [ $COUNTER -gt "$servercount" ] + until [ $COUNTER -gt "$serverCount" ] do - printf "%s|%s\\n" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverlist" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" + printf "%s|%s\\n" "$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .id')" "$(echo "$serverList" | jq -r --argjson index "$((COUNTER-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" >> "/tmp/$serverlistfile.tmp" COUNTER=$((COUNTER + 1)) done fi @@ -2516,7 +2558,7 @@ _Trim_Database_() } ##----------------------------------------## -## Modified by Martinski W. [2025-Feb-28] ## +## Modified by Martinski W. [2025-Mar-08] ## ##----------------------------------------## Run_Speedtest() { @@ -2653,8 +2695,18 @@ Run_Speedtest() do IFACE="$(Get_Interface_From_Name "$IFACE_NAME")" IFACE_LOWER="$(echo "$IFACE" | tr "A-Z" "a-z")" - if [ ! -f "/sys/class/net/$IFACE_LOWER/operstate" ] || \ - [ "$(cat "/sys/class/net/$IFACE_LOWER/operstate")" = "down" ] + + interface_UP=false + if echo "$IFACE_NAME" | grep -q "^WGVPN" + then + if _Check_WG_ClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + else + if _CheckNetClientInterfaceUP_ "$IFACE_LOWER" + then interface_UP=true ; fi + fi + + if ! "$interface_UP" then Print_Output true "$IFACE not up, please check. Skipping speedtest for $IFACE_NAME" "$WARN" continue @@ -2666,7 +2718,7 @@ Run_Speedtest() elif [ "$mode" = "webui_onetime" ]; then mode="user" fi - + if [ "$mode" = "schedule" ] then if PreferredServer check "$IFACE_NAME" @@ -3140,7 +3192,7 @@ Process_Upgrade() ##----------------------------------------## ## Modified by Martinski W. [2025-Feb-28] ## ##----------------------------------------## -#$1 iface name +#$1 IFACE Name Generate_LastXResults() { for IFACE_NAME in $FULL_IFACELIST;