diff --git a/.gitattributes b/.gitattributes index 4354e73b..b8ba9ce8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,12 @@ -*.tar.gz binary +* text eol=lf *.sh text eol=lf *.md text eol=lf *.asp text eol=lf +*.js text eol=lf +*.css text eol=lf +*.gz binary +*.png binary +*.jpg binary +*.jpeg binary +*.zip binary +*.tar.gz binary diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index abfac586..fcef25d6 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -19,7 +19,7 @@ jobs: steps: # 1--- Check out master so we tag the exact merge commit - name: Checkout source code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v5.0.0 with: fetch-depth: 0 ref: 'master' @@ -97,7 +97,7 @@ jobs: # 7--- Publish a GitHub Release with auto-generated notes - name: Create Release with Automated Release Notes - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@v2.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.nextver.outputs.tag }} diff --git a/README.md b/README.md index b4a09666..edb26798 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin -## v4.4.14 -### Updated on 2025-July-11 +## v4.4.15 +### Updated on 2025-Nov-07 ## 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 5799083a..ad853a18 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Jul-11 +# Last Modified: 2025-Nov-05 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -38,9 +38,9 @@ ### Start of script variables ### readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" -readonly SCRIPT_VERSION="v4.4.14" -readonly SCRIPT_VERSTAG="25071123" -SCRIPT_BRANCH="master" +readonly SCRIPT_VERSION="v4.4.15" +readonly SCRIPT_VERSTAG="25110522" +SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" readonly SCRIPT_WEBPAGE_DIR="$(readlink -f /www/user)" @@ -56,9 +56,13 @@ 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" +[ -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 + ##-------------------------------------## ## Added by Martinski W. [2025-Feb-28] ## ##-------------------------------------## +readonly theUserName="$(nvram get http_username)" readonly scriptVersRegExp="v[0-9]{1,2}([.][0-9]{1,2})([.][0-9]{1,2})" readonly webPageMenuAddons="menuName: \"Addons\"," readonly webPageHelpSupprt="tabName: \"Help & Support\"}," @@ -67,8 +71,9 @@ readonly webPageLineTabExp="\{url: \"$webPageFileRegExp\", tabName: " readonly webPageLineRegExp="${webPageLineTabExp}\"$SCRIPT_NAME\"\}," readonly BEGIN_MenuAddOnsTag="/\*\*BEGIN:_AddOns_\*\*/" readonly ENDIN_MenuAddOnsTag="/\*\*ENDIN:_AddOns_\*\*/" -readonly scriptVERINFO="[${SCRIPT_VERSION}_${SCRIPT_VERSTAG}, Branch: $SCRIPT_BRANCH]" -readonly theUserName="$(nvram get http_username)" +readonly branchxStr_TAG="[Branch: $SCRIPT_BRANCH]" +readonly versionDev_TAG="${SCRIPT_VERSION}_${SCRIPT_VERSTAG}" +readonly versionMod_TAG="$SCRIPT_VERSION on $ROUTER_MODEL" # For daily CRON job to trim database # readonly defTrimDB_Hour=3 @@ -95,9 +100,6 @@ readonly sqlDBLogFileName="${SCRIPT_NAME}_DBSQL_DEBUG.LOG" # 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 - if [ "$(uname -m)" = "aarch64" ]; then ARCH="aarch64" else @@ -155,7 +157,7 @@ Print_Output() "$PASS") prioNum=6 ;; #INFO# *) prioNum=5 ;; #NOTICE# esac - logger -t "$SCRIPT_NAME" -p $prioNum "$2" + logger -t "${SCRIPT_NAME}_[$$]" -p $prioNum "$2" fi printf "${BOLD}${3}%s${CLEARFORMAT}\n\n" "$2" } @@ -170,8 +172,13 @@ Firmware_Version_Check() } ### Code for these functions inspired by https://github.com/Adamm00 - credit to @Adamm ### +##----------------------------------------## +## Modified by Martinski W. [2025-Nov-03] ## +##----------------------------------------## Check_Lock() { + local doExit=false + if [ -f "/tmp/$SCRIPT_NAME.lock" ] then ageoflock="$(($(/bin/date "+%s") - $(/bin/date "+%s" -r "/tmp/$SCRIPT_NAME.lock")))" @@ -183,18 +190,23 @@ Check_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 [ $# -eq 0 ] || [ -z "$1" ] then - exit 1 + doExit=true else if [ "$1" = "webui" ] then echo 'var spdteststatus = "LOCKED";' > /tmp/detect_spdtest.js - exit 1 + doExit=true fi - return 1 fi + if "$doExit" + then + Print_Output true "Lock file found (age: $ageoflock seconds) - stopping to prevent duplicate runs" "$ERR" + exit 1 + fi + Print_Output true "Lock file found (age: $ageoflock seconds)" "$WARN" + return 1 fi else echo "$$" > "/tmp/$SCRIPT_NAME.lock" @@ -613,7 +625,7 @@ _Check_WG_ClientInterfaceUP_() ##---------------------------------=---## ## Added by Martinski W. [2025-Jun-23] ## ##-------------------------------------## -_Set_All_Interface_States_() +_Set_All_InterfacesUser_Status_() { local interfaceCount COUNTER @@ -621,30 +633,31 @@ _Set_All_Interface_States_() COUNTER=1 until [ "$COUNTER" -gt "$interfaceCount" ] do - Set_Interface_State "$COUNTER" + Set_InterfacesUser_State "$COUNTER" COUNTER="$((COUNTER + 1))" done } -##---------------------------------=---## -## Added by Martinski W. [2025-Jun-23] ## -##-------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2025-Sep-06] ## +##----------------------------------------## _Check_All_Interface_States_() { [ -f "$SCRIPT_INTERFACES" ] && \ - cp -a "$SCRIPT_INTERFACES" "${SCRIPT_INTERFACES}.bak" + cp -a "$SCRIPT_INTERFACES" "$SCRIPT_INTERFACES_BAK" printf "WAN\n" > "$SCRIPT_INTERFACES" local ifaceTagStr + local excludedButUPstr=" #excluded#" local excludedNotUPstr=" #excluded - interface not up#" for index in 1 2 3 4 5 do - ifaceTagStr="$excludedNotUPstr" + ifaceTagStr="$excludedNotUPstr" if _CheckNetClientInterfaceUP_ "$index" then - ifaceTagStr="" #Assumes interface is included# + ifaceTagStr="$excludedButUPstr" fi printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done @@ -654,28 +667,61 @@ _Check_All_Interface_States_() ifaceTagStr="$excludedNotUPstr" if _Check_WG_ClientInterfaceUP_ "$index" then - ifaceTagStr="" #Assumes interface is included# + ifaceTagStr="$excludedButUPstr" fi printf "WGVPN%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done } +##---------------------------------=---## +## Added by Martinski W. [2025-Oct-12] ## +##-------------------------------------## +_CheckFor_Duplicate_Interfaces_() +{ + if [ $# -eq 0 ] || [ -z "$1" ] || [ ! -s "$1" ] + then return 0 + fi + local dupTempFile="${1}.DUPTMP.TXT" + + setIFaceUserStatus=false + cat "$1" | sort -u > "$dupTempFile" + grep -E -m1 '^WAN.*' "$dupTempFile" > "$1" + + for ifaceID in VPNC WGVPN + do + for ifaceNum in 1 2 3 4 5 + do + grep -E -m1 "^${ifaceID}${ifaceNum}.*" "$dupTempFile" >> "$1" + done + done + + if ! diff -q "$1" "$dupTempFile" >/dev/null 2>&1 + then + setIFaceUserStatus=true + fi + rm -f "$dupTempFile" +} + ##---------------------------------=---## ## Added by Martinski W. [2025-Jun-23] ## ##-------------------------------------## _Startup_All_Interface_States_() { + local theIFaceID ifaceCount _Check_All_Interface_States_ + _CheckFor_Duplicate_Interfaces_ "$SCRIPT_INTERFACES_USER" - while IFS='' read -r line || [ -n "$line" ] + while IFS='' read -r theLine || [ -n "$theLine" ] do - if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ] + theIFaceID="$(echo "$theLine" | cut -d'#' -f1 | sed 's/ *$//')" + ifaceCount="$(grep -wc "^$theIFaceID" "$SCRIPT_INTERFACES_USER")" + if [ "$ifaceCount" -eq 0 ] then - printf "%s\n" "$line" >> "$SCRIPT_INTERFACES_USER" + printf "%s\n" "$theLine" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" - _Set_All_Interface_States_ + _Set_All_InterfacesUser_Status_ } ##----------------------------------------## @@ -732,6 +778,27 @@ Create_Symlinks() fi } +##-------------------------------------## +## Added by Martinski W. [2025-Sep-06] ## +##-------------------------------------## +Save_InterfacesUser_SAVEDBAK() +{ + local doCheck=false + + if [ $# -gt 0 ] && [ "$1" = "check" ] + then doCheck=true + fi + [ ! -s "$SCRIPT_INTERFACES_USER" ] && return 1 + + if ! "$doCheck" || [ ! -s "$SCRIPT_INTERFACES_USER_SAVBAK" ] + then + cp -a "$SCRIPT_INTERFACES_USER" "$SCRIPT_INTERFACES_USER_SAVBAK" + fi +} + +Delete_InterfacesUser_SAVEDBAK() +{ rm -f "$SCRIPT_INTERFACES_USER_SAVBAK" ;} + ##----------------------------------------## ## Modified by Martinski W. [2025-Jul-11] ## ##----------------------------------------## @@ -814,13 +881,15 @@ Conf_FromSettings() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-23] ## +## Modified by Martinski W. [2025-Sep-06] ## ##----------------------------------------## Interfaces_FromSettings() { SETTINGSFILE="/jffs/addons/custom_settings.txt" - local ifaceTagStr interface_UP + local ifaceTagStr interface_UP ifaceLineIndx interfaceLine + local doUpdateSavedBak=false + local excludedButUPstr=" #excluded#" local excludedNotUPstr=" #excluded - interface not up#" if [ -f "$SETTINGSFILE" ] @@ -828,8 +897,8 @@ Interfaces_FromSettings() 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" "${SCRIPT_INTERFACES}.bak" - cp -a "$SCRIPT_INTERFACES_USER" "${SCRIPT_INTERFACES_USER}.bak" + cp -a "$SCRIPT_INTERFACES" "$SCRIPT_INTERFACES_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" @@ -840,7 +909,7 @@ Interfaces_FromSettings() ifaceTagStr="$excludedNotUPstr" if _CheckNetClientInterfaceUP_ "$index" then - ifaceTagStr=" #excluded#" + ifaceTagStr="$excludedButUPstr" fi printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done @@ -850,26 +919,26 @@ Interfaces_FromSettings() ifaceTagStr="$excludedNotUPstr" if _Check_WG_ClientInterfaceUP_ "$index" then - ifaceTagStr=" #excluded#" + ifaceTagStr="$excludedButUPstr" fi printf "WGVPN%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done - echo "" > "$SCRIPT_INTERFACES_USER" + printf '' > "$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 + then # Add new interface # printf "%s\n" "$line" >> "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" - _Set_All_Interface_States_ + _Set_All_InterfacesUser_Status_ for IFACEname in $(echo "$SETTINGVALUE" | sed "s/,/ /g") do - ifacelinenumber="$(grep -n "$IFACEname" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" - interfaceLine="$(sed "$ifacelinenumber!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + ifaceLineIndx="$(grep -n "$IFACEname" "$SCRIPT_INTERFACES_USER" | cut -f1 -d':')" + interfaceLine="$(sed "${ifaceLineIndx}!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')" @@ -887,21 +956,23 @@ Interfaces_FromSettings() then if "$interface_UP" then - sed -i "${ifacelinenumber}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" - sed -i "${ifacelinenumber}s/ #excluded#//" "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceLineIndx}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceLineIndx}s/ #excluded#//" "$SCRIPT_INTERFACES_USER" else - sed -i "${ifacelinenumber}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceLineIndx}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi else if ! "$interface_UP" then - sed -i "$ifacelinenumber"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceLineIndx}s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi + doUpdateSavedBak=true done awk 'NF' "$SCRIPT_INTERFACES_USER" > /tmp/spd-interfaces mv -f /tmp/spd-interfaces "$SCRIPT_INTERFACES_USER" + "$doUpdateSavedBak" && Save_InterfacesUser_SAVEDBAK Print_Output true "Merge of updated interfaces from WebUI completed successfully" "$PASS" else @@ -1065,7 +1136,7 @@ Conf_Exists() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jan-19] ## +## Modified by Martinski W. [2025-Oct-28] ## ##----------------------------------------## Auto_ServiceEvent() { @@ -1075,7 +1146,7 @@ Auto_ServiceEvent() 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)" + STARTUPLINECOUNTEX="$(grep -cx 'if echo "$2" | /bin/grep -qE "('"$SCRIPT_NAME_LOWER"'|vpnclient)" ; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" /jffs/scripts/service-event)" if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ] ; } then @@ -1084,13 +1155,13 @@ Auto_ServiceEvent() if [ "$STARTUPLINECOUNTEX" -eq 0 ] then { - echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" + echo 'if echo "$2" | /bin/grep -qE "('"$SCRIPT_NAME_LOWER"'|vpnclient)" ; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" } >> /jffs/scripts/service-event fi else { echo "#!/bin/sh" ; echo - echo 'if echo "$2" | /bin/grep -q "'"$SCRIPT_NAME_LOWER"'"; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" + echo 'if echo "$2" | /bin/grep -qE "('"$SCRIPT_NAME_LOWER"'|vpnclient)" ; then { '"$theScriptFilePath"' service_event "$@" & }; fi # '"$SCRIPT_NAME" echo } > /jffs/scripts/service-event chmod 0755 /jffs/scripts/service-event @@ -1108,14 +1179,42 @@ Auto_ServiceEvent() esac } +##-------------------------------------## +## Added by Martinski W. [2025-Jul-13] ## +##-------------------------------------## +_CheckForOpenVPN_ClientsAvailable_() +{ + local retCode=1 + local nvramTempFile="/tmp/${SCRIPT_NAME}_nvramShow_$$.txt" + + nvram show 2>/dev/null | grep -E "^vpn_client[1-5]_.+" > "$nvramTempFile" + if [ ! -s "$nvramTempFile" ] + then + rm -f "$nvramTempFile" + return 1 #OpenVPN Clients NOT found# + fi + + if grep -qE "^vpn_client[1-5]_state=[1-3]$" "$nvramTempFile" || \ + grep -qE "^vpn_client[1-5]_(username|password)=.+$" "$nvramTempFile" || \ + grep -qE "^vpn_client[1-5]_addr=([0-9]{1,3}\.){3}[0-9]{1,3}$" "$nvramTempFile" + then retCode=0 + fi + + rm -f "$nvramTempFile" + return "$retCode" +} + ##-------------------------------------## ## Added by Martinski W. [2025-Jul-11] ## ##-------------------------------------## -Auto_OpenVpnEvent() +Auto_OpenVPN_Event() { local theScriptFilePath="/jffs/scripts/$SCRIPT_NAME_LOWER" case $1 in create) + # Check if any OpenVPN Client is set up/available in NVRAM # + if ! _CheckForOpenVPN_ClientsAvailable_ ; then return 1 ; fi + if [ -f /jffs/scripts/openvpn-event ] then STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" /jffs/scripts/openvpn-event)" @@ -1152,6 +1251,92 @@ Auto_OpenVpnEvent() esac } +##-------------------------------------## +## Added by Martinski W. [2025-Jul-13] ## +##-------------------------------------## +_CheckForWireGuard_ClientsAvailable_() +{ + local retCode=1 + local nvramTempFile="/tmp/${SCRIPT_NAME}_nvramShow_$$.txt" + + if ! nvram get "rc_support" | grep -qwo "wireguard" + then return 1 #WireGuard NOT supported# + fi + + nvram show 2>/dev/null | grep -E "^wgc[1-5]_.+" > "$nvramTempFile" + if [ ! -s "$nvramTempFile" ] + then + rm -f "$nvramTempFile" + return 1 #WireGuard Clients NOT found# + fi + + if grep -qE "^wgc[1-5]_enable=[1-2]$" "$nvramTempFile" || \ + grep -qE "^wgc[1-5]_(ppub|priv)=.+$" "$nvramTempFile" || \ + grep -qE "^wgc[1-5]_addr=([0-9]{1,3}\.){3}[0-9]{1,3}" "$nvramTempFile" + then retCode=0 + fi + + rm -f "$nvramTempFile" + return "$retCode" +} + +##-------------------------------------## +## Added by Martinski W. [2025-Jul-13] ## +##-------------------------------------## +Auto_WG_ClientEvent() +{ + local wgClientFilePath + local theScriptFilePath="/jffs/scripts/$SCRIPT_NAME_LOWER" + + case $1 in + create) + # Check if any WireGuard Client is set up/available in NVRAM # + if ! _CheckForWireGuard_ClientsAvailable_ ; then return 1 ; fi + + for wgClientEvent in stop start + do + wgClientFilePath="/jffs/scripts/wgclient-$wgClientEvent" + if [ -f "$wgClientFilePath" ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" "$wgClientFilePath")" + STARTUPLINECOUNTEX="$(grep -cx '\[ -x '"$theScriptFilePath"' \] && '"$theScriptFilePath"' wgclient_event '"$wgClientEvent"' "$@" & # '"$SCRIPT_NAME" "$wgClientFilePath")" + + if [ "$STARTUPLINECOUNT" -gt 1 ] || { [ "$STARTUPLINECOUNTEX" -eq 0 ] && [ "$STARTUPLINECOUNT" -gt 0 ]; } + then + sed -i -e '/# '"$SCRIPT_NAME"'/d' "$wgClientFilePath" + fi + if [ "$STARTUPLINECOUNTEX" -eq 0 ] + then + { + echo '[ -x '"$theScriptFilePath"' ] && '"$theScriptFilePath"' wgclient_event '"$wgClientEvent"' "$@" & # '"$SCRIPT_NAME" + } >> "$wgClientFilePath" + fi + else + { + echo "#!/bin/sh" ; echo + echo '[ -x '"$theScriptFilePath"' ] && '"$theScriptFilePath"' wgclient_event '"$wgClientEvent"' "$@" & # '"$SCRIPT_NAME" + echo + } > "$wgClientFilePath" + chmod 0755 "$wgClientFilePath" + fi + done + ;; + delete) + for wgClientEvent in stop start + do + wgClientFilePath="/jffs/scripts/wgclient-$wgClientEvent" + if [ -f "$wgClientFilePath" ] + then + STARTUPLINECOUNT="$(grep -c '# '"$SCRIPT_NAME" "$wgClientFilePath")" + if [ "$STARTUPLINECOUNT" -gt 0 ]; then + sed -i -e '/# '"$SCRIPT_NAME"'/d' "$wgClientFilePath" + fi + fi + done + ;; + esac +} + ##----------------------------------------## ## Modified by Martinski W. [2025-Jan-19] ## ##----------------------------------------## @@ -1302,17 +1487,19 @@ Get_Interface_From_Name() echo "$IFACEname" } -##------------------------------------=---## -## Modified by Martinski W. [2025-Mar-08] ## ##----------------------------------------## -Set_Interface_State() +## Modified by Martinski W. [2025-Oct-26] ## +##----------------------------------------## +Set_InterfacesUser_State() { - local interfaceLine interface_UP index - interfaceLine="$(sed "$1!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + local ifaceLineStr interface_UP index="$1" + local savedIFaceLine setIncludeStatus=false - if echo "$interfaceLine" | grep -qE "^(VPNC|WGVPN)" + ifaceLineStr="$(sed "${index}!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + + if echo "$ifaceLineStr" | grep -qE "^(VPNC|WGVPN)" then - IFACE_NAME="$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" + IFACE_NAME="$(echo "$ifaceLineStr" | cut -f1 -d"#" | sed 's/ *$//')" IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" # Check if interface is 'up' vs 'down' # @@ -1327,52 +1514,62 @@ Set_Interface_State() fi # Decide how to update the '#excluded' marker based on up/down # - if echo "$interfaceLine" | grep -q "#excluded" + if echo "$ifaceLineStr" | 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' so ensure we have "#excluded - interface not up#" # - if echo "$interfaceLine" | grep -q "#excluded#$" + if [ -s "$SCRIPT_INTERFACES_USER_SAVBAK" ] + then + savedIFaceLine="$(grep "^$IFACE_NAME" "$SCRIPT_INTERFACES_USER_SAVBAK")" + if [ -n "$savedIFaceLine" ] && ! echo "$savedIFaceLine" | grep -q '#excluded' + then setIncludeStatus=true + fi + fi + if "$setIncludeStatus" # Remove any '#excluded' marker # then - sed -i "$1 s/#excluded#/#excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" + sed -i "${index}s/ #excluded#//" "$SCRIPT_INTERFACES_USER" + sed -i "${index}s/ #excluded - interface not up#//" "$SCRIPT_INTERFACES_USER" + else + # If it had '- interface not up#' remove it but keep it 'excluded' # + sed -i "${index}s/#excluded - interface not up#/#excluded#/" "$SCRIPT_INTERFACES_USER" fi + else + # The interface is 'down' so ensure we have '- interface not up#' # + sed -i "${index}s/#excluded#/#excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi else - # No '#excluded' marker => user wanted it included # + # No '#excluded' marker => user wanted it included when UP # 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" + sed -i "${index}s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi fi } ##----------------------------------------## -## Modified by Martinski W. [2025-Mar-08] ## +## Modified by Martinski W. [2025-Sep-06] ## ##----------------------------------------## Generate_Interface_List() { - local ifaceCount ifaceEntryNum interfaceLine interface_UP + local ifaceCount ifaceEntryNum ifaceLineStr interface_UP + local doUpdateSavedBak=false 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" - } + _GenerateIFaceList_() + { + ifaceCount="$(wc -l < "$SCRIPT_INTERFACES_USER")" + COUNTER=1 + until [ "$COUNTER" -gt "$ifaceCount" ] + do + Set_InterfacesUser_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 @@ -1393,8 +1590,8 @@ Generate_Interface_List() printf "\n${ERR}Please enter a number between 1 and ${ifaceCount}.${CLEARFORMAT}\n" PressEnter else - interfaceLine="$(sed "$ifaceEntryNum!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - IFACE_NAME="$(echo "$interfaceLine" | cut -f1 -d"#" | sed 's/ *$//')" + ifaceLineStr="$(sed "$ifaceEntryNum!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" + IFACE_NAME="$(echo "$ifaceLineStr" | cut -f1 -d"#" | sed 's/ *$//')" IFACE_LOWER="$(Get_Interface_From_Name "$IFACE_NAME" | tr 'A-Z' 'a-z')" interface_UP=false @@ -1407,27 +1604,30 @@ Generate_Interface_List() then interface_UP=true ; fi fi - if echo "$interfaceLine" | grep -q "#excluded" - then + if echo "$ifaceLineStr" | grep -q "#excluded" + then if "$interface_UP" then - sed -i "$ifaceEntryNum"'s/ #excluded - interface not up#//' "$SCRIPT_INTERFACES_USER" - sed -i "$ifaceEntryNum"'s/ #excluded#//' "$SCRIPT_INTERFACES_USER" + 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" + sed -i "${ifaceEntryNum}s/ #excluded#/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi else if "$interface_UP" then - sed -i "$ifaceEntryNum"'s/$/ #excluded#/' "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceEntryNum}s/$/ #excluded#/" "$SCRIPT_INTERFACES_USER" else - sed -i "$ifaceEntryNum"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" + sed -i "${ifaceEntryNum}s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi sed -i 's/ *$//' "$SCRIPT_INTERFACES_USER" + doUpdateSavedBak=true printf "\n" fi done + + "$doUpdateSavedBak" && Save_InterfacesUser_SAVEDBAK } ##----------------------------------------## @@ -1672,7 +1872,7 @@ CronTestSchedule() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-06] ## +## Modified by Martinski W. [2025-Sep-06] ## ##----------------------------------------## ScriptStorageLocation() { @@ -1683,10 +1883,13 @@ ScriptStorageLocation() mkdir -p "/opt/share/$SCRIPT_NAME_LOWER.d/" rm -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db-shm" rm -f "/jffs/addons/$SCRIPT_NAME_LOWER.d/spdstats.db-wal" + [ -d "/opt/share/$SCRIPT_NAME_LOWER.d/csv" ] && rm -fr "/opt/share/$SCRIPT_NAME_LOWER.d/csv" 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.bak" "/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/.interfaces_user.save.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 @@ -1703,10 +1906,13 @@ ScriptStorageLocation() printf "Please wait..." sed -i 's/^STORAGELOCATION=.*$/STORAGELOCATION=jffs/' "$SCRIPT_CONF" mkdir -p "/jffs/addons/$SCRIPT_NAME_LOWER.d/" + [ -d "/jffs/addons/$SCRIPT_NAME_LOWER.d/csv" ] && rm -fr "/jffs/addons/$SCRIPT_NAME_LOWER.d/csv" 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.bak" "/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/.interfaces_user.save.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 @@ -1736,7 +1942,10 @@ ScriptStorageLocation() SPEEDSTATS_DB="$SCRIPT_STORAGE_DIR/spdstats.db" CSV_OUTPUT_DIR="$SCRIPT_STORAGE_DIR/csv" SCRIPT_INTERFACES="$SCRIPT_STORAGE_DIR/.interfaces" + SCRIPT_INTERFACES_BAK="${SCRIPT_INTERFACES}.bak" SCRIPT_INTERFACES_USER="$SCRIPT_STORAGE_DIR/.interfaces_user" + SCRIPT_INTERFACES_USER_BAK="${SCRIPT_INTERFACES_USER}.bak" + SCRIPT_INTERFACES_USER_SAVBAK="${SCRIPT_INTERFACES_USER}.save.bak" if [ $# -gt 1 ] && [ "$2" = "true" ] then _UpdateJFFS_FreeSpaceInfo_ ; fi ;; @@ -2063,7 +2272,7 @@ WriteStats_ToJS() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jul-11] ## +## Modified by Martinski W. [2025-Jul-13] ## ##----------------------------------------## GenerateServerList() { @@ -2112,15 +2321,14 @@ GenerateServerList() printf "%2d) %6d|%s\n" "$COUNTER" "$serverIDnum" "$serverIDstr" COUNTER="$((COUNTER + 1))" done + maxServerCount="$serverCount" if [ "$promptforservername" = "onetime" ] then serverMsgStr="server" - maxServerCount="$serverCount" else - maxServerCount="$COUNTER" serverMsgStr="preferred server" - printf "\n%2d) Reset to ${GRNct}None configured${CLRct}" "$maxServerCount" + printf "\nrs) Reset to ${GRNct}None configured${CLRct}" fi printf "\n e) Go back\n" @@ -2131,10 +2339,15 @@ GenerateServerList() printf "\n${BOLD}Enter answer:${CLEARFORMAT} " read -r serverIndx - if [ "$serverIndx" = "e" ] + if echo "$serverIndx" | grep -qE "^(e|E)$" then serverNum="exit" break + elif [ "$serverIndx" = "rs" ] && [ "$promptforservername" = "update" ] + then + serverNum=0 + serverName="None configured" + echo ; break elif [ "$serverIndx" = "c" ] || [ "$serverIndx" = "C" ] then while true @@ -2193,11 +2406,6 @@ GenerateServerList() if [ "$serverIndx" -lt 1 ] || [ "$serverIndx" -gt "$maxServerCount" ] then printf "\n${ERR}Please enter a number between 1 and %d.${CLEARFORMAT}\n" "$maxServerCount" - elif [ "$serverIndx" -eq "$COUNTER" ] - then - serverNum=0 - serverName="None configured" - echo ; break else serverNum="$(echo "$serverList" | jq -r --argjson index "$((serverIndx-1))" '.servers[$index] | .id')" serverName="$(echo "$serverList" | jq -r --argjson index "$((serverIndx-1))" '.servers[$index] | .name + " (" + .location + ", " + .country + ")"')" @@ -2208,10 +2416,12 @@ GenerateServerList() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jul-11] ## +## Modified by Martinski W. [2025-Oct-12] ## ##----------------------------------------## GenerateServerList_WebUI() { + local setIFaceUserStatus=false + spdifacename="$1" serverlistfile="$2" rm -f "/tmp/${serverlistfile}.txt" @@ -2252,6 +2462,13 @@ GenerateServerList_WebUI() done < "$SCRIPT_INTERFACES_USER" IFACELIST="$(echo "$IFACELIST" | cut -c2-)" + _CheckFor_Duplicate_Interfaces_ "$SCRIPT_INTERFACES_USER" + if "$setIFaceUserStatus" + then + _Set_All_InterfacesUser_Status_ + Save_InterfacesUser_SAVEDBAK + fi + 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 @@ -2648,7 +2865,7 @@ _JFFS_WarnLowFreeSpace_() then JFFS_WarningLogTime update "$currTimeSecs" logMsgStr="${logTagStr} JFFS Available Free Space ($1) is getting LOW." - logger -t "$SCRIPT_NAME" -p $logPriNum "$logMsgStr" + logger -t "${SCRIPT_NAME}_[$$]" -p $logPriNum "$logMsgStr" fi } @@ -2712,7 +2929,7 @@ _SQLGetDBLogTimeStamp_() { printf "[$(date +"$sqlDBLogDateTime")]" ; } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-21] ## +## Modified by Martinski W. [2025-Jul-20] ## ##----------------------------------------## readonly errorMsgsRegExp="Parse error|Runtime error|Error:" readonly corruptedBinExp="Illegal instruction|SQLite header and source version mismatch" @@ -2800,7 +3017,7 @@ _ApplyDatabaseSQLCmds_() fi if "$foundError" || "$foundLocked" then - Print_Output true "SQLite process ${resultStr}" "$ERR" + Print_Output true "SQLite process[$callFlag] ${resultStr}" "$ERR" fi } @@ -2887,7 +3104,7 @@ _Trim_Database_() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-30] ## +## Modified by Martinski W. [2025-Oct-30] ## ##----------------------------------------## Run_Speedtest() { @@ -2917,7 +3134,8 @@ Run_Speedtest() else Auto_Cron delete 2>/dev/null fi Auto_ServiceEvent create 2>/dev/null - Auto_OpenVpnEvent create 2>/dev/null + Auto_OpenVPN_Event create 2>/dev/null + Auto_WG_ClientEvent create 2>/dev/null Shortcut_Script create ScriptStorageLocation load Create_Symlinks @@ -2933,6 +3151,7 @@ Run_Speedtest() local stoppedQoS nvramQoSenable nvramQoStype local spdIndx spdTestOK verboseNUM verboseARG + local serverLine serverIDno verboseNUM="$(_GetConfigParam_ VERBOSE_TEST 0)" if ! echo "$verboseNUM" | grep -qE "^[0-3]$" then verboseNUM=0 @@ -3098,7 +3317,7 @@ Run_Speedtest() fi echo 'var spdteststatus = "InProgress_'"$IFACE_NAME"'";' > /tmp/detect_spdtest.js - printf "" > "$tmpfile" + printf '' > "$tmpfile" if [ "$mode" = "auto" ] then @@ -3169,7 +3388,12 @@ Run_Speedtest() 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 ## + serverLine="$(grep -E '[[:blank:]]+Server:[[:blank:]]+.*[[:blank:]]+[(]id(:| =)[[:blank:]]+[0-9]+[)]' "$tmpfile")" + serverLine="$(echo "$serverLine" | sed 's/ *$//' | sed 's/^ *//' | tr -d '\r')" + serverName="$(echo "$serverLine" | sed 's/^Server: *//' | sed 's/ *(id[: =]\+ [0-9]\+)$//')" + serverIDno="$(echo "$serverLine" | grep -Eo '[(]id(:| =)[[:blank:]]+[0-9]+[)]' | awk -F' ' '{print $NF}' | tr -d ')')" + + ## 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 @@ -3186,9 +3410,6 @@ Run_Speedtest() datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,2)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,2)}')" - - 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}')" @@ -3202,15 +3423,14 @@ Run_Speedtest() datadownloadunit="$(grep "Download:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,2)}')" datauploadunit="$(grep "Upload:" "$tmpfile" | awk 'BEGIN { FS = "\r" } ;{print $NF};' | awk '{print substr($7,1,2)}')" - - 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 - if [ -z "$download" ] || [ -z "$upload" ] || [ -z "$datadownload" ] || [ -z "$dataupload" ] + if [ -z "$download" ] || [ -z "$upload" ] || \ + [ -z "$serverName" ] || [ -z "$serverIDno" ] || \ + [ -z "$datadownload" ] || [ -z "$dataupload" ] then cp -fp "$tmpfile" "$spdTestDBGFile" - Print_Output true "ERROR running speedtest for $IFACE_NAME [Empty Values]" "$CRIT" + Print_Output true "ERROR running speedtest for $IFACE_NAME [Empty or Bad Values]" "$CRIT" if [ -s "$spdTestLogFile" ] ; then echo ; cat "$spdTestLogFile" ; echo ; fi continue fi @@ -3246,7 +3466,7 @@ Run_Speedtest() curllatency=0 fi - curlresult=$(curl -fsL --retry 4 --retry-delay 5 -d "recommendedserverid=$serverid" \ + curlresult=$(curl -fsL --retry 4 --retry-delay 5 -d "recommendedserverid=$serverIDno" \ -d "ping=$(echo "$curllatency" | awk '{printf("%.0f\n", $1);}')" \ -d "screenresolution=" \ -d "promo=" \ @@ -3260,7 +3480,7 @@ Run_Speedtest() -d "accuracy=1" \ -d "bytesreceived=$(echo "$datadownload" | awk '{printf("%.0f\n", $1*1024);}')" \ -d "bytessent=$(echo "$dataupload" | awk '{printf("%.0f\n", $1*1024);}')" \ --d "serverid=$serverid" \ +-d "serverid=$serverIDno" \ -H "Referer: http://c.speedtest.net/flash/speedtest.swf" https://www.speedtest.net/api/api.php) resulturl="https://www.speedtest.net/result/$(echo "$curlresult" | cut -f2 -d'&' | cut -f2 -d'=')" @@ -3281,13 +3501,13 @@ Run_Speedtest() 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');" + 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,$serverIDno,'$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');" + 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,$serverIDno,'$serverName');" } > /tmp/spdTest-stats.sql fi _ApplyDatabaseSQLCmds_ /tmp/spdTest-stats.sql "spd2$spdIndx" @@ -3944,27 +4164,56 @@ PressEnter() return 0 } +##-------------------------------------## +## Added by Martinski W. [2025-Sep-06] ## +##-------------------------------------## +_CenterTextStr_() +{ + if [ $# -lt 2 ] || [ -z "$1" ] || [ -z "$2" ] || \ + ! echo "$2" | grep -qE "^[1-9][0-9]+$" + then echo ; return 1 + fi + local stringLen="${#1}" + local space1Len="$((($2 - stringLen)/2))" + local space2Len="$space1Len" + local totalLen="$((space1Len + stringLen + space2Len))" + + if [ "$totalLen" -lt "$2" ] + then space2Len="$((space2Len + 1))" + elif [ "$totalLen" -gt "$2" ] + then space1Len="$((space1Len - 1))" + fi + if [ "$space1Len" -gt 0 ] && [ "$space2Len" -gt 0 ] + then printf "%*s%s%*s" "$space1Len" '' "$1" "$space2Len" '' + else printf "%s" "$1" + fi +} + +##----------------------------------------## +## Modified by Martinski W. [2025-Sep-06] ## +##----------------------------------------## 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}## %9s on %-18s ##${CLEARFORMAT}\n" "$SCRIPT_VERSION" "$ROUTER_MODEL" - printf "${BOLD}## ##${CLEARFORMAT}\\n" - printf "${BOLD}## https://github.com/AMTM-OSR/spdMerlin ##${CLEARFORMAT}\\n" - printf "${BOLD}## Forked from https://github.com/jackyaz/spdMerlin ##${CLEARFORMAT}\\n" - printf "${BOLD}## ##${CLEARFORMAT}\\n" - printf "${BOLD}################################################################${CLEARFORMAT}\\n" - printf "\\n" + local spaceLen=58 colorCT + [ "$SCRIPT_BRANCH" = "master" ] && colorCT="$GRNct" || colorCT="$MGNTct" + echo + printf "${BOLD}################################################################${CLRct}\n" + printf "${BOLD}## _ __ __ _ _ ##${CLRct}\n" + printf "${BOLD}## | || \/ | | |(_) ##${CLRct}\n" + printf "${BOLD}## ___ _ __ __| || \ / | ___ _ __ | | _ _ __ ##${CLRct}\n" + printf "${BOLD}## / __|| '_ \ / _ || |\/| | / _ \| '__|| || || '_ \ ##${CLRct}\n" + printf "${BOLD}## \__ \| |_) || (_| || | | || __/| | | || || | | | ##${CLRct}\n" + printf "${BOLD}## |___/| .__/ \__,_||_| |_| \___||_| |_||_||_| |_| ##${CLRct}\n" + printf "${BOLD}## | | ##${CLRct}\n" + printf "${BOLD}## |_| ##${CLRct}\n" + printf "${BOLD}## ${GRNct}%s${CLRct}${BOLD} ##${CLRct}\n" "$(_CenterTextStr_ "$versionMod_TAG" "$spaceLen")" + printf "${BOLD}## ${colorCT}%s${CLRct}${BOLD} ##${CLRct}\n" "$(_CenterTextStr_ "$branchxStr_TAG" "$spaceLen")" + printf "${BOLD}## ##${CLRct}\n" + printf "${BOLD}## https://github.com/AMTM-OSR/spdMerlin ##${CLRct}\n" + printf "${BOLD}## Forked from https://github.com/jackyaz/spdMerlin ##${CLRct}\n" + printf "${BOLD}## ##${CLRct}\n" + printf "${BOLD}################################################################${CLRct}\n\n" } ##-------------------------------------## @@ -4116,7 +4365,7 @@ MainMenu() 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 " WebUI for %s is available at:\n ${SETTING}%s${CLRct}\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)" @@ -4452,7 +4701,8 @@ Menu_Install() Auto_Cron delete 2>/dev/null AutomaticMode check && Auto_Cron create 2>/dev/null Auto_ServiceEvent create 2>/dev/null - Auto_OpenVpnEvent create 2>/dev/null + Auto_OpenVPN_Event create 2>/dev/null + Auto_WG_ClientEvent create 2>/dev/null Shortcut_Script create Process_Upgrade @@ -4467,8 +4717,27 @@ Menu_Install() MainMenu } +##-------------------------------------## +## Added by Martinski W. [2025-Oct-26] ## +##-------------------------------------## +_SetParameters_() +{ + if [ -f "/opt/share/${SCRIPT_NAME_LOWER}.d/config" ] + then SCRIPT_STORAGE_DIR="/opt/share/${SCRIPT_NAME_LOWER}.d" + else SCRIPT_STORAGE_DIR="/jffs/addons/${SCRIPT_NAME_LOWER}.d" + fi + SCRIPT_CONF="$SCRIPT_STORAGE_DIR/config" + SPEEDSTATS_DB="$SCRIPT_STORAGE_DIR/spdstats.db" + CSV_OUTPUT_DIR="$SCRIPT_STORAGE_DIR/csv" + SCRIPT_INTERFACES="$SCRIPT_STORAGE_DIR/.interfaces" + SCRIPT_INTERFACES_BAK="${SCRIPT_INTERFACES}.bak" + SCRIPT_INTERFACES_USER="$SCRIPT_STORAGE_DIR/.interfaces_user" + SCRIPT_INTERFACES_USER_BAK="${SCRIPT_INTERFACES_USER}.bak" + SCRIPT_INTERFACES_USER_SAVBAK="${SCRIPT_INTERFACES_USER}.save.bak" +} + ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-23] ## +## Modified by Martinski W. [2025-Oct-26] ## ##----------------------------------------## Menu_Startup() { @@ -4488,6 +4757,8 @@ Menu_Startup() fi NTP_Ready + Entware_Ready + _SetParameters_ Check_Lock if [ "$1" != "force" ]; then @@ -4510,8 +4781,10 @@ Menu_Startup() then Auto_Cron create 2>/dev/null else Auto_Cron delete 2>/dev/null fi + Set_Version_Custom_Settings local "$SCRIPT_VERSION" Auto_ServiceEvent create 2>/dev/null - Auto_OpenVpnEvent create 2>/dev/null + Auto_OpenVPN_Event create 2>/dev/null + Auto_WG_ClientEvent create 2>/dev/null Shortcut_Script create Mount_WebUI Clear_Lock @@ -4528,8 +4801,10 @@ _Reset_Interface_States_() return 1 fi Print_Output true "Resetting interfaces for ${SCRIPT_NAME}..." "$PASS" - NTP_Ready - Check_Lock + NTP_Ready noLockCheck + Entware_Ready noLockCheck + _SetParameters_ + ##OFF## Check_Lock [NO LockCheck] ## Create_Dirs Conf_Exists ScriptStorageLocation load true @@ -5788,7 +6063,8 @@ Menu_Uninstall() Auto_Startup delete 2>/dev/null Auto_Cron delete 2>/dev/null Auto_ServiceEvent delete 2>/dev/null - Auto_OpenVpnEvent delete 2>/dev/null + Auto_OpenVPN_Event delete 2>/dev/null + Auto_WG_ClientEvent delete 2>/dev/null Shortcut_Script delete LOCKFILE=/tmp/addonwebui.lock @@ -5837,18 +6113,25 @@ Menu_Uninstall() } ##----------------------------------------## -## Modified by Martinski W. [2025-Feb-28] ## +## Modified by Martinski W. [2025-Oct-26] ## ##----------------------------------------## NTP_Ready() { - local theSleepDelay=15 ntpMaxWaitSecs=600 ntpWaitSecs + local theSleepDelay=15 ntpMaxWaitSecs=600 ntpWaitSecs doLockCheck=true + + if [ $# -gt 0 ] && [ "$1" = "noLockCheck" ] + then doLockCheck=false + fi if [ "$(nvram get ntp_ready)" -eq 0 ] then - Check_Lock - ntpWaitSecs=0 + if "$doLockCheck" + then Check_Lock + else theSleepDelay=5 + fi Print_Output true "Waiting for NTP to sync..." "$WARN" + ntpWaitSecs=0 while [ "$(nvram get ntp_ready)" -eq 0 ] && [ "$ntpWaitSecs" -lt "$ntpMaxWaitSecs" ] do if [ "$ntpWaitSecs" -gt 0 ] && [ "$((ntpWaitSecs % 30))" -eq 0 ] @@ -5858,31 +6141,38 @@ NTP_Ready() sleep "$theSleepDelay" ntpWaitSecs="$((ntpWaitSecs + theSleepDelay))" done + if [ "$ntpWaitSecs" -ge "$ntpMaxWaitSecs" ] then Print_Output true "NTP failed to sync after 10 minutes. Please resolve!" "$CRIT" - Clear_Lock + "$doLockCheck" && Clear_Lock exit 1 else - Print_Output true "NTP synced, $SCRIPT_NAME will now continue" "$PASS" - Clear_Lock + Print_Output true "NTP has synced [$ntpWaitSecs secs]. $SCRIPT_NAME will now continue." "$PASS" + "$doLockCheck" && Clear_Lock fi fi } -### function based on @Adamm00's Skynet USB wait function ### ##----------------------------------------## -## Modified by Martinski W. [2025-Feb-28] ## +## Modified by Martinski W. [2025-Oct-26] ## ##----------------------------------------## Entware_Ready() { - local theSleepDelay=5 maxSleepTimer=120 sleepTimerSecs + local theSleepDelay=10 maxSleepTimer=150 sleepTimerSecs doLockCheck=true + + if [ $# -gt 0 ] && [ "$1" = "noLockCheck" ] + then doLockCheck=false + fi if [ ! -f /opt/bin/opkg ] then - Check_Lock - sleepTimerSecs=0 + if "$doLockCheck" + then Check_Lock + else theSleepDelay=5 + fi + sleepTimerSecs=0 while [ ! -f /opt/bin/opkg ] && [ "$sleepTimerSecs" -lt "$maxSleepTimer" ] do if [ "$((sleepTimerSecs % 10))" -eq 0 ] @@ -5892,14 +6182,15 @@ Entware_Ready() 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" - Clear_Lock + "$doLockCheck" && Clear_Lock exit 1 else - Print_Output true "Entware found. $SCRIPT_NAME will now continue" "$PASS" - Clear_Lock + Print_Output true "Entware found [$sleepTimerSecs secs]. $SCRIPT_NAME will now continue." "$PASS" + "$doLockCheck" && Clear_Lock fi fi } @@ -5932,10 +6223,11 @@ EOF ### function based on @dave14305's FlexQoS show_help function ### ##----------------------------------------## -## Modified by Martinski W. [2025-Jul-11] ## +## Modified by Martinski W. [2025-Oct-27] ## ##----------------------------------------## Show_Help() { + printf " WebUI for %s is available at:\n ${SETTING}%s${CLRct}\n\n" "$SCRIPT_NAME" "$(Get_WebUI_URL)" printf "HELP ${MGNTct}${SCRIPT_VERS_INFO}${CLRct}\n" cat </dev/null else Auto_Cron delete 2>/dev/null fi + Set_Version_Custom_Settings local "$SCRIPT_VERSION" Auto_ServiceEvent create 2>/dev/null - Auto_OpenVpnEvent create 2>/dev/null + Auto_OpenVPN_Event create 2>/dev/null + Auto_WG_ClientEvent create 2>/dev/null Shortcut_Script create _CheckFor_WebGUI_Page_ ScriptHeader @@ -6066,8 +6354,9 @@ case "$1" in exit 0 ;; service_event) + [ "$2" != "start" ] && exit 0 updateJFFS_SpaceInfo=true - if [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}spdtest" + if echo "$3" | grep -q "${SCRIPT_NAME_LOWER}spdtest" then rm -f /tmp/detect_spdtest.js rm -f /tmp/spd-result.txt @@ -6077,28 +6366,36 @@ case "$1" in Run_Speedtest_WebUI "$3" updateJFFS_SpaceInfo=false Clear_Lock - elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlistmanual" + elif echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlistmanual" then Check_Lock webui spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlistmanual_//" | cut -f1 -d'_' | tr 'a-z' 'A-Z')"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_manual_serverlist" Clear_Lock - elif [ "$2" = "start" ] && echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlist" + elif echo "$3" | grep -q "${SCRIPT_NAME_LOWER}serverlist" then Check_Lock webui spdifacename="$(echo "$3" | sed "s/${SCRIPT_NAME_LOWER}serverlist_//" | cut -f1 -d'_' | tr 'a-z' 'A-Z')"; GenerateServerList_WebUI "$spdifacename" "spdmerlin_serverlist_$spdifacename" Clear_Lock - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}config" ] + elif [ "$3" = "${SCRIPT_NAME_LOWER}config" ] then Interfaces_FromSettings Conf_FromSettings - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}checkupdate" ] + elif [ "$3" = "${SCRIPT_NAME_LOWER}checkupdate" ] then Update_Check - elif [ "$2" = "start" ] && [ "$3" = "${SCRIPT_NAME_LOWER}doupdate" ] + elif [ "$3" = "${SCRIPT_NAME_LOWER}doupdate" ] then Update_Version force unattended + elif echo "$3" | grep -qE '^vpnclient[1-5]' + then + vpnClientUpIDstr="$(echo "$3" | sed 's/^vpnclient//')" + Print_Output true "VPN Client tunnel is coming up." "$PASS" + vpnClientUpEvent=true ; vpnClientDownEvent=false + sleep 2 + _Reset_Interface_States_ force + Clear_Lock fi "$updateJFFS_SpaceInfo" && _UpdateJFFS_FreeSpaceInfo_ exit 0 @@ -6110,17 +6407,37 @@ case "$1" in if [ "$3" = "route-pre-down" ] then Print_Output true "VPN Client tunnel is going down." "$PASS" - sleep 3 + vpnClientDownEvent=true ; vpnClientUpEvent=false + Save_InterfacesUser_SAVEDBAK check elif [ "$3" = "route-up" ] then Print_Output true "VPN Client tunnel is coming up." "$PASS" - sleep 2 + vpnClientUpEvent=true ; vpnClientDownEvent=false + vpnClientUpIDstr="$2" + sleep 3 fi - _Reset_Interface_States_ force "$2" + _Reset_Interface_States_ force Clear_Lock fi exit 0 ;; + wgclient_event) + if [ "$2" = "stop" ] + then + Print_Output true "WireGuard Client tunnel is going down." "$PASS" + vpnClientDownEvent=true ; vpnClientUpEvent=false + Save_InterfacesUser_SAVEDBAK check + elif [ "$2" = "start" ] + then + Print_Output true "WireGuard Client tunnel is coming up." "$PASS" + vpnClientUpEvent=true ; vpnClientDownEvent=false + vpnClientUpIDstr="wg$3" + sleep 3 + fi + _Reset_Interface_States_ force + Clear_Lock + exit 0 + ;; outputcsv) NTP_Ready Entware_Ready @@ -6166,7 +6483,8 @@ case "$1" in else Auto_Cron delete 2>/dev/null fi Auto_ServiceEvent create 2>/dev/null - Auto_OpenVpnEvent create 2>/dev/null + Auto_OpenVPN_Event create 2>/dev/null + Auto_WG_ClientEvent create 2>/dev/null Shortcut_Script create Set_Version_Custom_Settings local "$SCRIPT_VERSION" Set_Version_Custom_Settings server "$SCRIPT_VERSION" diff --git a/spdstats_www.asp b/spdstats_www.asp index 88ae0b3d..0da58e4d 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -11,7 +11,7 @@ @@ -26,14 +26,12 @@ p{font-weight:bolder}thead.collapsible-jquery{color:#fff;padding:0;width:100%;bo - - @@ -90,27 +88,27 @@ var daysofweek=["Mon","Tues","Wed","Thurs","Fri","Sat","Sun"],maxNoCharts=0,curr
Stats last updated:
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.
- +
- + - +
Utilities (click to expand/collapse)
Version informationVersion information         - + - +    
ExportExport - +
diff --git a/spdstats_www.css b/spdstats_www.css index 77545871..337b54ba 100644 --- a/spdstats_www.css +++ b/spdstats_www.css @@ -92,7 +92,7 @@ td.nodata { border-left: solid 1px black; background-color: #1F2D35 !important; background: #2F3A3E !important; - width: 35% !important; + width: 34% !important; } .SettingsTable td.settingvalue { diff --git a/spdstats_www.js b/spdstats_www.js index c52d381e..219690e8 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1,14 +1,14 @@ /**----------------------------**/ -/** Last Modified: 2025-Jul-10 **/ +/** Last Modified: 2025-Oct-14 **/ /**----------------------------**/ var daysofweek = ['Mon','Tues','Wed','Thurs','Fri','Sat','Sun']; var maxNoCharts = 0; var currentNoCharts = 0; -var interfacelist = ''; -var interfacescomplete = []; -var interfacesdisabled = []; +let interfaceList = ''; +let interfacesComplete = []; +let interfacesDisabled = []; var arraysortlistlinesWAN = []; var sortnameWAN = 'Time'; @@ -696,7 +696,7 @@ function round(value,decimals){ } function ToggleLines(){ - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); if(ShowLines == ''){ ShowLines = 'line'; SetCookie('ShowLines','line'); @@ -722,7 +722,7 @@ function ToggleLines(){ } function ToggleFill(){ - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); if(ShowFill == 'origin'){ ShowFill = 'false'; SetCookie('ShowFill','false'); @@ -747,7 +747,7 @@ function ToggleFill(){ } function RedrawAllCharts(){ - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); var i; for(var i2 = 0; i2 < chartlist.length; i2++){ for(var i3 = 0; i3 < interfacetextarray.length; i3++){ @@ -765,7 +765,7 @@ function SetGlobalDataset(txtchartname,dataobject){ window[txtchartname] = dataobject; currentNoCharts++; if(currentNoCharts == maxNoCharts){ - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); for(var i = 0; i < interfacetextarray.length; i++){ $('#'+interfacetextarray[i]+'_Interval_Combined').val(GetCookie(interfacetextarray[i]+'_Interval_Combined','number')); $('#'+interfacetextarray[i]+'_Interval_Quality').val(GetCookie(interfacetextarray[i]+'_Interval_Quality','number')); @@ -972,7 +972,7 @@ function getChartScale(scale){ } function ResetZoom(){ - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); for(var i = 0; i < interfacetextarray.length; i++){ for(var i2 = 0; i2 < typelist.length; i2++){ var chartobj = window['LineChart_'+interfacetextarray[i]+'_'+typelist[i2]]; @@ -1001,7 +1001,7 @@ function ToggleDragZoom(button){ buttonvalue = 'Drag Zoom On'; } - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); for(var i = 0; i < interfacetextarray.length; i++){ for(var i2 = 0; i2 < typelist.length; i2++){ var chartobj = window['LineChart_'+interfacetextarray[i]+'_'+typelist[i2]]; @@ -1014,31 +1014,38 @@ function ToggleDragZoom(button){ } } -function ExportCSV(){ +function ExportCSV() +{ location.href = '/ext/spdmerlin/csv/spdmerlindata.zip'; return 0; } -function update_status(){ +function update_status() +{ $.ajax({ url: '/ext/spdmerlin/detect_update.js', dataType: 'script', error: function(xhr){ setTimeout(update_status,1000); }, - success: function(){ - if(updatestatus == 'InProgress'){ + success: function() + { + if (updatestatus == 'InProgress') + { setTimeout(update_status,1000); } - else{ + else + { document.getElementById('imgChkUpdate').style.display = 'none'; showhide('spdmerlin_version_server',true); - if(updatestatus != 'None'){ + if (updatestatus != 'None') + { $('#spdmerlin_version_server').text('Updated version available: '+updatestatus); showhide('btnChkUpdate',false); showhide('btnDoUpdate',true); } - else{ + else + { $('#spdmerlin_version_server').text('No update available'); showhide('btnChkUpdate',true); showhide('btnDoUpdate',false); @@ -1048,7 +1055,8 @@ function update_status(){ }); } -function CheckUpdate(){ +function CheckUpdate() +{ showhide('btnChkUpdate',false); document.formScriptActions.action_script.value='start_spdmerlincheckupdate' document.formScriptActions.submit(); @@ -1056,31 +1064,36 @@ function CheckUpdate(){ setTimeout(update_status,2000); } -function DoUpdate(){ +function DoUpdate() +{ document.form.action_script.value = 'start_spdmerlindoupdate'; document.form.action_wait.value = 10; showLoading(); document.form.submit(); } -function getAllIndexes(arr,val){ +function getAllIndexes(arr,val) +{ var indexes = []; - for(var i = 0; i < arr.length; i++){ - if(arr[i].id == val){ + for (var i = 0; i < arr.length; i++) + { + if (arr[i].id == val){ indexes.push(i); } } return indexes; } -function get_spdtestservers_file(ifacename){ +function get_spdtestservers_file(ifacename) +{ $.ajax({ url: '/ext/spdmerlin/spdmerlin_serverlist_'+ifacename.toUpperCase()+'.htm?cachebuster='+new Date().getTime(), dataType: 'text', error: function(xhr){ setTimeout(get_spdtestservers_file,1000,ifacename); }, - success: function(data){ + success: function(data) + { var servers = []; $.each(data.split('\n').filter(Boolean),function (key,entry){ var obj = {}; @@ -1105,14 +1118,16 @@ function get_spdtestservers_file(ifacename){ }); } -function get_manualspdtestservers_file(){ +function get_manualspdtestservers_file() +{ $.ajax({ url: '/ext/spdmerlin/spdmerlin_manual_serverlist.htm?cachebuster='+new Date().getTime(), dataType: 'text', error: function(xhr){ setTimeout(get_manualspdtestservers_file,2000); }, - success: function(data){ + success: function(data) + { var servers = []; $.each(data.split('\n').filter(Boolean),function (key,entry){ var obj = {}; @@ -1121,16 +1136,18 @@ function get_manualspdtestservers_file(){ servers.push(obj); }); - if(document.form.spdtest_enabled.value == 'All'){ + if (document.form.spdtest_enabled.value == 'All') + { var arrifaceindex = getAllIndexes(servers,'-----'); - for(var i = 0; i < arrifaceindex.length; i++){ + for (var i = 0; i < arrifaceindex.length; i++) + { let dropdown = $($('select[name^=spdtest_serverprefselect]')[i]); dropdown.empty(); var arrtmp = []; - if(i == 0){ + if (i == 0){ arrtmp = servers.slice(0,arrifaceindex[i]); } - else if(i == arrifaceindex.length-1){ + else if (i == arrifaceindex.length-1){ arrtmp = servers.slice(arrifaceindex[i-1]+1,servers.length-1); } else{ @@ -1150,7 +1167,8 @@ function get_manualspdtestservers_file(){ }); showhide('imgManualServerList',false); } - else{ + else + { let dropdown = $('select[name=spdtest_serverprefselect]'); dropdown.empty(); $.each(servers,function (key,entry){ @@ -1160,10 +1178,12 @@ function get_manualspdtestservers_file(){ showhide('spdtest_serverprefselect',true); showhide('imgManualServerList',false); } - for(var i = 0; i < interfacescomplete.length; i++){ - if(interfacesdisabled.includes(interfacescomplete[i]) == false){ - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); + for (var i = 0; i < interfacesComplete.length; i++) + { + if (interfacesDisabled.includes(interfacesComplete[i]) == false) + { + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).prop('disabled',false); + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).removeClass('disabled'); } } $.each($('input[name=spdtest_serverpref]'),function(){ @@ -1174,7 +1194,8 @@ function get_manualspdtestservers_file(){ }); } -function get_spdtestresult_file(){ +function get_spdtestresult_file() +{ $.ajax({ url: '/ext/spdmerlin/spd-result.htm', dataType: 'text', @@ -1190,22 +1211,24 @@ function get_spdtestresult_file(){ }); } -function get_spdtest_file(){ +function get_spdtest_file() +{ $.ajax({ url: '/ext/spdmerlin/spd-stats.htm', dataType: 'text', error: function(xhr){ //do nothing }, - success: function(data){ + success: function(data) + { var lines = data.trim().split('\n'); var arrlastLine = lines.slice(-1)[0].split('%').filter(Boolean); - if(speedtestbinary == "builtin"){ + if (speedtestbinary == "builtin"){ lines.unshift(""); lines.unshift("Speedtest by Ookla") } - if(lines.length > 5){ + if (lines.length > 5){ $('#spdtest_output').html(lines[0]+'\n'+lines[1]+'\n'+lines[2]+'\n'+lines[3]+'\n'+lines[4]+'\n'+arrlastLine[arrlastLine.length-1]+'%'); } else{ @@ -1370,14 +1393,14 @@ function SaveConfig() if (validateAll()) { $('[name*=spdmerlin_]').prop('disabled',false); - for (var indx = 0; indx < interfacescomplete.length; indx++) + 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'); + $('#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') @@ -1547,7 +1570,7 @@ function getConfigFile() } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Mar-04] **/ +/** Modified by Martinski W. [2025-Oct-14] **/ /**----------------------------------------**/ function getInterfacesFile() { @@ -1564,13 +1587,22 @@ function getInterfacesFile() showhide('btnRunSpeedtest',true); showhide('databaseSize_text',true); - var interfaces = data.split('\n'); - const styleLeftMarginIndx = [2, 3, 4, 5]; - let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle; - interfaces = interfaces.filter(Boolean); - interfacelist = ''; - interfacescomplete = []; - interfacesdisabled = []; + const styleLeftMarginIndx = [2, 3, 4, 5, 7, 8, 9, 10]; + let interfaceName, ifaceExcluded, ifaceNameUpper, ifaceNameLower; + let changelabel, ifaceLabel, ifaceStyle, foundIndex, indxCount; + + var theInterfaces = data.split('\n'); + theInterfaces = theInterfaces.filter(Boolean); + theInterfaces.sort(); + foundIndex = theInterfaces.findIndex(item => item.startsWith('WAN')); + if (foundIndex > 0) + { //Make sure 'WAN' is always FIRST// + const wanIFace = theInterfaces.splice(foundIndex, 1)[0]; + theInterfaces.unshift(wanIFace); + } + interfaceList = ''; + interfacesComplete = []; + interfacesDisabled = []; var interfacecharttablehtml='
 
'; interfacecharttablehtml += ''; @@ -1587,99 +1619,153 @@ function getInterfacesFile() speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; - const ifaceMaxCount = interfaces.length; + indxCount = 0; + const ifaceMaxCount = theInterfaces.length; const breakStartIndex = 1, breakStopIndex = 5; for (var ifaceIndx = 0; ifaceIndx < ifaceMaxCount; ifaceIndx++) { - interfacename = ''; - if (interfaces[ifaceIndx].indexOf('#') !== -1) + if (theInterfaces[ifaceIndx].indexOf('#') === -1) + { + ifaceExcluded = false; + interfaceName = theInterfaces[ifaceIndx].trim(); + } + else + { + ifaceExcluded = true; + interfaceName = theInterfaces[ifaceIndx].substring(0,theInterfaces[ifaceIndx].indexOf('#')).trim(); + } + if (interfacesComplete.includes(interfaceName)) + { + console.log(`ERROR: getInterfacesFile(): Duplicate Interface Found [${interfaceName}]`); + continue; + } + indxCount = interfacesComplete.length; + interfacesComplete.push(interfaceName); + ifaceNameUpper = interfaceName.toUpperCase(); + ifaceNameLower = interfaceName.toLowerCase(); + + if (ifaceExcluded) { - interfacename = interfaces[ifaceIndx].substring(0,interfaces[ifaceIndx].indexOf('#')).trim(); - interfacescomplete.push(interfacename); - ifaceNameUpper = interfacename.toUpperCase(); - ifaceNameLower = interfacename.toLowerCase(); ifaceStyle = 'style="margin-left:0px;"'; ifaceLabel = ifaceNameUpper; - var changelabel = 'Change?'; - var interfacedisabled = ''; + changelabel = 'Change?'; + var interfaceDisabled = ''; - if (interfaces[ifaceIndx].indexOf('interface not up') !== -1) + if (theInterfaces[ifaceIndx].indexOf('interface not up') !== -1) { - interfacesdisabled.push(interfacename); - interfacedisabled = 'disabled'; + interfacesDisabled.push(interfaceName); + interfaceDisabled = 'disabled'; ifaceLabel = ''+ifaceNameUpper+''; changelabel = 'Change?'; } + else + { + ifaceLabel = ''+ifaceNameUpper+''; + changelabel = 'Change?'; + } // For AUTOMATIC Speedtests // - if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } - interfaceconfigtablehtml += ''; + if (breakStartIndex === indxCount) { interfaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } + interfaceconfigtablehtml += ''; interfaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // - if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } - prefserverconfigtablehtml += ''; + if (breakStartIndex === indxCount) { prefserverconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } + prefserverconfigtablehtml += ''; prefserverconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { prefserverconfigtablehtml += '
'; } // Select a Preferred Server // prefserverselecttablehtml += ''+ifaceNameUpper+':
'; - prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; prefserverselecttablehtml += ''; prefserverselecttablehtml += ''; prefserverselecttablehtml += '
'; // For MANUAL Speedtests // - if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } - speedtestifaceconfigtablehtml += ''; + if (breakStartIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } + speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } } - else + else //Interface UP and INCLUDED// { - interfacename = interfaces[ifaceIndx].trim(); - interfacescomplete.push(interfacename); - ifaceNameUpper = interfacename.toUpperCase(); - ifaceNameLower = interfacename.toLowerCase(); ifaceStyle = 'style="margin-left:0px;"'; - ifaceLabel = ''+ifaceNameUpper+''; + ifaceLabel = ''+ifaceNameUpper+''; + changelabel = 'Change?'; // For AUTOMATIC Speedtests // - if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (breakStartIndex === indxCount) { interfaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } interfaceconfigtablehtml += ''; interfaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // - if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (breakStartIndex === indxCount) { prefserverconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } prefserverconfigtablehtml += ''; prefserverconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { prefserverconfigtablehtml += '
'; } // Select a Preferred Server // prefserverselecttablehtml += ''+ifaceNameUpper+':
'; prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; prefserverselecttablehtml += ''; prefserverselecttablehtml += '
'; // For MANUAL Speedtests // - if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (breakStartIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) + { + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { ifaceStyle = 'style="margin-left:6px !important;"'; } + else + { ifaceStyle = 'style="margin-left:18px !important;"'; } + } speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } } - interfacecharttablehtml += BuildInterfaceTable(interfacename); - interfacelist += interfacename+','; + interfacecharttablehtml += BuildInterfaceTable(interfaceName); + interfaceList += interfaceName+','; } interfacecharttablehtml += '
'; @@ -1696,16 +1782,16 @@ function getInterfacesFile() 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; + maxNoCharts = interfaceList.split(',').length*3*3*2; RedrawAllCharts(); AddEventHandlers(); - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); for (var indx = 0; indx < interfacetextarray.length; indx++) { $('#sortTable'+interfacetextarray[indx]).empty(); @@ -1783,7 +1869,7 @@ 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(','); @@ -1813,7 +1899,8 @@ function SortTable(tableid,arrayid,sorttext,sortname,sortdir) window[sortname] = sorttext.replace('↑','').replace('↓','').trim(); var sorttype = 'number'; var sortfield = window[sortname]; - switch(window[sortname]){ + switch(window[sortname]) + { case 'Time': sorttype = 'date'; break @@ -1822,8 +1909,9 @@ function SortTable(tableid,arrayid,sorttext,sortname,sortdir) sorttype = 'string'; break } - - if(sorttype == 'string'){ + + if (sorttype == 'string') + { if(sorttext.indexOf('↓') == -1 && sorttext.indexOf('↑') == -1){ eval(arrayid+' = '+arrayid+'.sort((a,b) => (a.'+sortfield+'.toLowerCase() > b.'+sortfield+'.toLowerCase()) ? 1 : ((b.'+sortfield+'.toLowerCase() > a.'+sortfield+'.toLowerCase()) ? -1 : 0));'); window[sortdir] = 'asc'; @@ -1837,12 +1925,13 @@ function SortTable(tableid,arrayid,sorttext,sortname,sortdir) window[sortdir] = 'desc'; } } - else if(sorttype == 'number'){ - if(sorttext.indexOf('↓') == -1 && sorttext.indexOf('↑') == -1){ + else if (sorttype == 'number') + { + if (sorttext.indexOf('↓') == -1 && sorttext.indexOf('↑') == -1){ eval(arrayid+' = '+arrayid+'.sort((a,b) => parseFloat(a.'+sortfield+'.replace("m","000")) - parseFloat(b.'+sortfield+'.replace("m","000")));'); window[sortdir] = 'asc'; } - else if(sorttext.indexOf('↓') != -1){ + else if (sorttext.indexOf('↓') != -1){ eval(arrayid+' = '+arrayid+'.sort((a,b) => parseFloat(a.'+sortfield+'.replace("m","000")) - parseFloat(b.'+sortfield+'.replace("m","000")));'); window[sortdir] = 'asc'; } @@ -1851,12 +1940,13 @@ function SortTable(tableid,arrayid,sorttext,sortname,sortdir) window[sortdir] = 'desc'; } } - else if(sorttype == 'date'){ - if(sorttext.indexOf('↓') == -1 && sorttext.indexOf('↑') == -1){ + else if (sorttype == 'date') + { + if (sorttext.indexOf('↓') == -1 && sorttext.indexOf('↑') == -1){ eval(arrayid+' = '+arrayid+'.sort((a, b) => new Date(a.'+sortfield+') - new Date(b.'+sortfield+'));'); window[sortdir] = 'asc'; } - else if(sorttext.indexOf('↓') != -1){ + else if (sorttext.indexOf('↓') != -1){ eval(arrayid+' = '+arrayid+'.sort((a, b) => new Date(a.'+sortfield+') - new Date(b.'+sortfield+'));'); window[sortdir] = 'asc'; } @@ -1865,13 +1955,13 @@ function SortTable(tableid,arrayid,sorttext,sortname,sortdir) window[sortdir] = 'desc'; } } - + $('#'+tableid).empty(); $('#'+tableid).append(BuildLastXTable(tableid.replace('sortTable',''))); - + $('#'+tableid).find('.sortable').each(function(index,element){ if(element.innerHTML.replace(/ \(.*\)/,'').replace(' ','') == window[sortname]){ - if(window[sortdir] == 'asc'){ + if (window[sortdir] == 'asc'){ element.innerHTML = element.innerHTML+' ↑'; } else{ @@ -1907,7 +1997,7 @@ function BuildLastXTable(name) tablehtml += ''; tablehtml += ''; tablehtml += ''; - + tablehtml += ''; tablehtml += ''; tablehtml += 'Time'; @@ -1924,7 +2014,7 @@ function BuildLastXTable(name) tablehtml += ''; tablehtml += ''; tablehtml += ''; - + for (var i = 0; i < window['arraysortlistlines'+name].length; i++) { tablehtml += ''; @@ -1946,7 +2036,7 @@ function BuildLastXTable(name) tablehtml += ''+window['arraysortlistlines'+name][i].ServerName.replace("null","")+''; tablehtml += ''; } - + tablehtml += ''; tablehtml += ''; return tablehtml; @@ -1957,8 +2047,9 @@ function changeAllCharts(e) value = e.value * 1; name = e.id.substring(0,e.id.indexOf('_')); SetCookie(e.id,value); - var interfacetextarray = interfacelist.split(','); - for(var i = 0; i < interfacetextarray.length; i++){ + var interfacetextarray = interfaceList.split(','); + for (var i = 0; i < interfacetextarray.length; i++) + { Draw_Chart(interfacetextarray[i],'Combined'); Draw_Chart(interfacetextarray[i],'Quality'); } @@ -1969,16 +2060,18 @@ function changeChart(e) value = e.value * 1; name = e.id.substring(0,e.id.indexOf('_')); SetCookie(e.id,value); - if(e.id.indexOf('Combined') != -1){ + if (e.id.indexOf('Combined') != -1) + { Draw_Chart(name,'Combined'); } - else if(e.id.indexOf('Quality') != -1){ + else if (e.id.indexOf('Quality') != -1) + { Draw_Chart(name,'Quality'); } } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Mar-03] **/ +/** Modified by Martinski W. [2025-Oct-11] **/ /**----------------------------------------**/ function SettingHint (hintID, formField) { @@ -1986,24 +2079,26 @@ function SettingHint (hintID, formField) for (var i = 0; i < tag_name.length; i++) { tag_name[i].onmouseout=nd; } - let hintMsg = ''; + let hintMsg = '', ifaceNum = 0; if (hintID === 1) { - if (formField.name.match(/^WGVPN[1-9]/) !== null) - { hintMsg = 'WireGuard interface is not enabled.'; } - else if (formField.name.match(/^VPNC[1-9]/) !== null) - { hintMsg = 'OpenVPN client interface is not enabled.'; } - else - { hintMsg = 'Interface is not enabled.'; } + ifaceNum = formField.name.replace(/\D/g,''); + if (formField.name.match(/^WGVPN[1-5]/) !== null) + { hintMsg = `WireGuard client ${ifaceNum} interface is not enabled.`; } + else if (formField.name.match(/^VPNC[1-5]/) !== null) + { hintMsg = `OpenVPN client ${ifaceNum} interface is not enabled.`; } + else + { hintMsg = 'Interface is not enabled.'; } } else if (hintID === 2) { - if (formField.name.match(/^WGVPN[1-9]/) !== null) - { hintMsg = 'WireGuard interface is enabled.'; } - else if (formField.name.match(/^VPNC[1-9]/) !== null) - { hintMsg = 'OpenVPN client interface is enabled.'; } - else - { hintMsg = 'Interface is enabled.'; } + ifaceNum = formField.name.replace(/\D/g,''); + if (formField.name.match(/^WGVPN[1-5]/) !== null) + { hintMsg = `WireGuard client ${ifaceNum} interface is enabled.`; } + else if (formField.name.match(/^VPNC[1-5]/) !== null) + { hintMsg = `OpenVPN client ${ifaceNum} interface is enabled.`; } + else + { hintMsg = 'Interface is enabled.'; } } else if (hintID === 3) { hintMsg = 'Hour(s) of day to run speedtests.
Use an asterisk (*) to run every hour.
Valid numbers are between 0 and 23.
Use commas (,) for multiple entries.
Use a hyphen (-) to indicate a range.'; } @@ -2139,20 +2234,20 @@ function AutomaticInterfaceEnableDisable(forminput) var inputname = forminput.name; var inputvalue = forminput.value; var prefix = inputname.substring(0,inputname.lastIndexOf('_')); - + var fieldNames = ['schhours','schmins']; var fieldnames2 = ['schedulemode','everyxselect','everyxvalue']; - + if (inputvalue == 'false') { - for (var i = 0; i < interfacescomplete.length; i++) + 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); - $('#'+prefix+'_usepreferred_'+interfacescomplete[i].toLowerCase()).addClass('disabled'); - $('#changepref_'+interfacescomplete[i].toLowerCase()).prop('disabled',true); - $('#changepref_'+interfacescomplete[i].toLowerCase()).addClass('disabled'); + $('#'+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); + $('#'+prefix+'_usepreferred_'+interfacesComplete[i].toLowerCase()).addClass('disabled'); + $('#changepref_'+interfacesComplete[i].toLowerCase()).prop('disabled',true); + $('#changepref_'+interfacesComplete[i].toLowerCase()).addClass('disabled'); } for (var i = 0; i < fieldNames.length; i++) { @@ -2171,16 +2266,16 @@ function AutomaticInterfaceEnableDisable(forminput) } else if (inputvalue == 'true') { - for (var i = 0; i < interfacescomplete.length; i++) + for (var i = 0; i < interfacesComplete.length; i++) { - if (interfacesdisabled.includes(interfacescomplete[i]) == false) + 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); - $('#'+prefix+'_usepreferred_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); - $('#changepref_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#changepref_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); + $('#'+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); + $('#'+prefix+'_usepreferred_'+interfacesComplete[i].toLowerCase()).removeClass('disabled'); + $('#changepref_'+interfacesComplete[i].toLowerCase()).prop('disabled',false); + $('#changepref_'+interfacesComplete[i].toLowerCase()).removeClass('disabled'); } } for (var i = 0; i < fieldNames.length; i++) @@ -2204,7 +2299,7 @@ function ScheduleModeToggle(forminput) { var inputname = forminput.name; var inputvalue = forminput.value; - + if (inputvalue == 'EveryX') { showhide('schfrequency',true); @@ -2231,7 +2326,7 @@ function EveryXToggle(forminput) { var inputname = forminput.name; var inputvalue = forminput.value; - + if (inputvalue == 'hours') { showhide('spanxhours',true); @@ -2283,7 +2378,6 @@ function Toggle_ChangePrefServer(forminput) { var inputname = forminput.name; var inputvalue = forminput.checked; - var ifacename = inputname.split('_')[1]; if (inputvalue == true) @@ -2314,15 +2408,15 @@ function Toggle_SpdTestServerPref(forminput) { var inputname = forminput.name; var inputvalue = forminput.value; - + if (inputvalue == 'onetime') { document.formScriptActions.action_script.value='start_spdmerlinserverlistmanual_'+document.form.spdtest_enabled.value; document.formScriptActions.submit(); - for (var i = 0; i < interfacescomplete.length; i++) + for (var i = 0; i < interfacesComplete.length; i++) { - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',true); - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).addClass('disabled'); + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).prop('disabled',true); + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).addClass('disabled'); } $.each($('input[name=spdtest_serverpref]'),function(){ $(this).prop('disabled',true); @@ -2330,7 +2424,7 @@ function Toggle_SpdTestServerPref(forminput) }); showhide('rowmanualserverprefselect',true); showhide('imgManualServerList',true); - + if (document.form.spdtest_enabled.value == 'All') { $.each($('select[name^=spdtest_serverprefselect]'),function(){ @@ -2363,20 +2457,34 @@ function Toggle_SpdTestServerPref(forminput) } } +/**----------------------------------------**/ +/** Modified by Martinski W. [2025-Jul-13] **/ +/**----------------------------------------**/ function GenerateManualSpdTestServerPrefSelect() { $('#rowmanualserverprefselect').remove(); var serverprefhtml = ''; serverprefhtml += 'Choose a server'; - + + let ifaceNameUpper, ifaceNameLower, styleSetting; + if (document.form.spdtest_enabled.value == 'All') { - for (var i = 0; i < interfacescomplete.length; i++) + for (var i = 0; i < interfacesComplete.length; i++) { - if (interfacesdisabled.includes(interfacescomplete[i]) == false) + if (interfacesDisabled.includes(interfacesComplete[i]) == false) { - var interfacename = interfacescomplete[i].toLowerCase(); - serverprefhtml += '
'; + ifaceNameUpper = interfacesComplete[i].toUpperCase(); + ifaceNameLower = interfacesComplete[i].toLowerCase(); + + if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) + { styleSetting = 'style="width:58px; display:none;"'; } + else if (ifaceNameUpper.match(/^VPNC[1-5]/) !== null) + { styleSetting = 'style="width:46px;display:none;"'; } + else + { styleSetting = 'style="width:33px; display:none;"'; } + + serverprefhtml += ''+interfacesComplete[i]+':
'; } } } @@ -2384,7 +2492,7 @@ function GenerateManualSpdTestServerPrefSelect() { serverprefhtml += ''; } - + serverprefhtml += ''; $('#rowmanualserverpref').after(serverprefhtml); }