From fab5303ab3aef24914fa98ba0ecf79188bfc13be Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:58:27 -0700 Subject: [PATCH 01/21] Improvements 1) Whenever any WireGuard or OpenVPN client is set to enabled or disabled by the user from the VPN WebUI tab, new code will update the corresponding interfaces in the configuration file. 2) Cosmetic changes/improvements on the WebUI page to show preferred servers entries that were longer than the width of the reserved space. --- README.md | 4 +- spdmerlin.sh | 175 +++++++++++++++++++++++++++++++----- spdstats_www.asp | 18 ++-- spdstats_www.css | 2 +- spdstats_www.js | 225 ++++++++++++++++++++++++++++++++--------------- 5 files changed, 319 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index b4a09666..8c574702 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-July-14 ## 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 a148bfc9..8cbaa03f 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-Jul-14 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -38,8 +38,8 @@ ### 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" +readonly SCRIPT_VERSION="v4.4.15" +readonly SCRIPT_VERSTAG="25071401" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -1108,14 +1108,43 @@ Auto_ServiceEvent() esac } +##-------------------------------------## +## Added by Martinski W. [2025-Jul-13] ## +##-------------------------------------## +_CheckForOpenVPN_ClientsAvailable_() +{ + local retCode + 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 + else retCode=1 + 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 +1181,93 @@ Auto_OpenVpnEvent() esac } +##-------------------------------------## +## Added by Martinski W. [2025-Jul-13] ## +##-------------------------------------## +_CheckForWireGuard_ClientsAvailable_() +{ + local retCode + local nvramTempFile="/tmp/${SCRIPT_NAME}_nvramShow_$$.txt" + + if ! nvram get "rc_support" | grep -qio "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]_ep_addr=([0-9]{1,3}\.){3}[0-9]{1,3}$" "$nvramTempFile" + then retCode=0 + else retCode=1 + 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] ## ##----------------------------------------## @@ -2063,7 +2179,7 @@ WriteStats_ToJS() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jul-11] ## +## Modified by Martinski W. [2025-Jul-13] ## ##----------------------------------------## GenerateServerList() { @@ -2112,15 +2228,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 +2246,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 +2313,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 + ")"')" @@ -2917,7 +3032,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 @@ -4452,7 +4568,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 @@ -4511,7 +4628,8 @@ Menu_Startup() 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 Mount_WebUI Clear_Lock @@ -5788,7 +5906,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 @@ -6019,7 +6138,8 @@ then 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 _CheckFor_WebGUI_Page_ ScriptHeader @@ -6121,6 +6241,20 @@ case "$1" in fi exit 0 ;; + wgclient_event) + if [ "$2" = "stop" ] + then + Print_Output true "WireGuard Client tunnel is going down." "$PASS" + sleep 3 + elif [ "$2" = "start" ] + then + Print_Output true "WireGuard Client tunnel is coming up." "$PASS" + sleep 2 + fi + _Reset_Interface_States_ force "$2" + Clear_Lock + exit 0 + ;; outputcsv) NTP_Ready Entware_Ready @@ -6166,7 +6300,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..8f21aaaf 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -11,7 +11,7 @@ @@ -33,7 +33,7 @@ p{font-weight:bolder}thead.collapsible-jquery{color:#fff;padding:0;width:100%;bo @@ -90,27 +90,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..03096540 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1,5 +1,5 @@ /**----------------------------**/ -/** Last Modified: 2025-Jul-10 **/ +/** Last Modified: 2025-Jul-13 **/ /**----------------------------**/ var daysofweek = ['Mon','Tues','Wed','Thurs','Fri','Sat','Sun']; @@ -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,8 +1178,10 @@ 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){ + 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'); } @@ -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{ @@ -1547,7 +1570,7 @@ function getConfigFile() } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Mar-04] **/ +/** Modified by Martinski W. [2025-Jul-13] **/ /**----------------------------------------**/ function getInterfacesFile() { @@ -1565,8 +1588,8 @@ function getInterfacesFile() showhide('databaseSize_text',true); var interfaces = data.split('\n'); - const styleLeftMarginIndx = [2, 3, 4, 5]; - let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle; + const styleLeftMarginIndx = [2, 3, 4, 5, 7, 8, 9, 10]; + let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle; interfaces = interfaces.filter(Boolean); interfacelist = ''; interfacescomplete = []; @@ -1614,14 +1637,26 @@ function getInterfacesFile() // For AUTOMATIC Speedtests // if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } // For PREFERRED Servers // if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } @@ -1635,7 +1670,13 @@ function getInterfacesFile() // For MANUAL Speedtests // if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } @@ -1651,14 +1692,26 @@ function getInterfacesFile() // For AUTOMATIC Speedtests // if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } // For PREFERRED Servers // if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) { ifaceStyle = 'style="margin-left:15px !important;"'; } + if (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } @@ -1666,13 +1719,19 @@ function getInterfacesFile() // 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 (styleLeftMarginIndx.includes(ifaceIndx)) + { + 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 += '
'; } @@ -1783,7 +1842,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 +1872,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 +1882,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 +1898,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 +1913,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 +1928,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 +1970,7 @@ function BuildLastXTable(name) tablehtml += ''; tablehtml += ''; tablehtml += ''; - + tablehtml += ''; tablehtml += ''; tablehtml += 'Time'; @@ -1924,7 +1987,7 @@ function BuildLastXTable(name) tablehtml += ''; tablehtml += ''; tablehtml += ''; - + for (var i = 0; i < window['arraysortlistlines'+name].length; i++) { tablehtml += ''; @@ -1946,7 +2009,7 @@ function BuildLastXTable(name) tablehtml += ''+window['arraysortlistlines'+name][i].ServerName.replace("null","")+''; tablehtml += ''; } - + tablehtml += ''; tablehtml += ''; return tablehtml; @@ -1958,7 +2021,8 @@ function changeAllCharts(e) name = e.id.substring(0,e.id.indexOf('_')); SetCookie(e.id,value); var interfacetextarray = interfacelist.split(','); - for(var i = 0; i < interfacetextarray.length; i++){ + for (var i = 0; i < interfacetextarray.length; i++) + { Draw_Chart(interfacetextarray[i],'Combined'); Draw_Chart(interfacetextarray[i],'Quality'); } @@ -1969,10 +2033,12 @@ 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'); } } @@ -1989,18 +2055,18 @@ function SettingHint (hintID, formField) let hintMsg = ''; if (hintID === 1) { - if (formField.name.match(/^WGVPN[1-9]/) !== null) + if (formField.name.match(/^WGVPN[1-5]/) !== null) { hintMsg = 'WireGuard interface is not enabled.'; } - else if (formField.name.match(/^VPNC[1-9]/) !== null) + else if (formField.name.match(/^VPNC[1-5]/) !== null) { hintMsg = 'OpenVPN client interface is not enabled.'; } else { hintMsg = 'Interface is not enabled.'; } } else if (hintID === 2) { - if (formField.name.match(/^WGVPN[1-9]/) !== null) + if (formField.name.match(/^WGVPN[1-5]/) !== null) { hintMsg = 'WireGuard interface is enabled.'; } - else if (formField.name.match(/^VPNC[1-9]/) !== null) + else if (formField.name.match(/^VPNC[1-5]/) !== null) { hintMsg = 'OpenVPN client interface is enabled.'; } else { hintMsg = 'Interface is enabled.'; } @@ -2139,10 +2205,10 @@ 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++) @@ -2204,7 +2270,7 @@ function ScheduleModeToggle(forminput) { var inputname = forminput.name; var inputvalue = forminput.value; - + if (inputvalue == 'EveryX') { showhide('schfrequency',true); @@ -2231,7 +2297,7 @@ function EveryXToggle(forminput) { var inputname = forminput.name; var inputvalue = forminput.value; - + if (inputvalue == 'hours') { showhide('spanxhours',true); @@ -2283,7 +2349,6 @@ function Toggle_ChangePrefServer(forminput) { var inputname = forminput.name; var inputvalue = forminput.checked; - var ifacename = inputname.split('_')[1]; if (inputvalue == true) @@ -2314,7 +2379,7 @@ 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; @@ -2330,7 +2395,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 +2428,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++) { 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 +2463,7 @@ function GenerateManualSpdTestServerPrefSelect() { serverprefhtml += ''; } - + serverprefhtml += ''; $('#rowmanualserverpref').after(serverprefhtml); } From 82b85086dbfb40754a214eaab316dc87d49a755a Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:11:38 -0700 Subject: [PATCH 02/21] Minor change in messaging. --- spdmerlin.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spdmerlin.sh b/spdmerlin.sh index 8cbaa03f..f942a65b 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Jul-14 +# Last Modified: 2025-Jul-15 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25071401" +readonly SCRIPT_VERSTAG="25071523" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -6068,8 +6068,8 @@ Available commands: $SCRIPT_NAME_LOWER outputcsv create CSVs from database, used by WebUI and export $SCRIPT_NAME_LOWER enable enable automatic speedtests $SCRIPT_NAME_LOWER disable disable automatic speedtests - $SCRIPT_NAME_LOWER develop switch to development branch - $SCRIPT_NAME_LOWER stable switch to stable branch + $SCRIPT_NAME_LOWER develop switch to development branch version + $SCRIPT_NAME_LOWER stable switch to stable/production branch version EOF printf "\n" } From 6d5dbad6ec22ed4d54439e0cbe3bb82040f99587 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:33:08 -0700 Subject: [PATCH 03/21] Improved Messaging Minor changes for improved messaging. --- README.md | 2 +- spdmerlin.sh | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8c574702..018c2d7d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-July-14 +### Updated on 2025-Jul-20 ## 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 f942a65b..593d879d 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Jul-15 +# Last Modified: 2025-Jul-20 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25071523" +readonly SCRIPT_VERSTAG="25072023" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -59,6 +59,7 @@ readonly FULL_IFACELIST="WAN VPNC1 VPNC2 VPNC3 VPNC4 VPNC5 WGVPN1 WGVPN2 WGVPN3 ##-------------------------------------## ## 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 +68,8 @@ 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 branchx_TAG="Branch: $SCRIPT_BRANCH" +readonly version_TAG="${SCRIPT_VERSION}_${SCRIPT_VERSTAG}" # For daily CRON job to trim database # readonly defTrimDB_Hour=3 @@ -2827,7 +2828,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" @@ -2915,7 +2916,7 @@ _ApplyDatabaseSQLCmds_() fi if "$foundError" || "$foundLocked" then - Print_Output true "SQLite process ${resultStr}" "$ERR" + Print_Output true "SQLite process[$callFlag] ${resultStr}" "$ERR" fi } @@ -6100,9 +6101,9 @@ SCRIPT_INTERFACES_USER="$SCRIPT_STORAGE_DIR/.interfaces_user" JFFS_LowFreeSpaceStatus="OK" updateJFFS_SpaceInfo=false -if [ "$SCRIPT_BRANCH" != "develop" ] -then SCRIPT_VERS_INFO="" -else SCRIPT_VERS_INFO="$scriptVERINFO" +if [ "$SCRIPT_BRANCH" = "master" ] +then SCRIPT_VERS_INFO="[$branchx_TAG]" +else SCRIPT_VERS_INFO="[$version_TAG, $branchx_TAG]" fi ##----------------------------------------## From 2bbed1705fbf79d3721b847b850eff4831da81fd Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Fri, 25 Jul 2025 23:16:44 -0700 Subject: [PATCH 04/21] Improved Messaging Minor changes for improved messaging. --- README.md | 2 +- spdmerlin.sh | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 018c2d7d..c505bd7a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Jul-20 +### Updated on 2025-Jul-25 ## 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 593d879d..9b972589 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Jul-20 +# Last Modified: 2025-Jul-25 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25072023" +readonly SCRIPT_VERSTAG="25072522" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -5978,13 +5978,14 @@ 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 exit 1 else - Print_Output true "NTP synced, $SCRIPT_NAME will now continue" "$PASS" + Print_Output true "NTP has synced [$ntpWaitSecs secs]. $SCRIPT_NAME will now continue." "$PASS" Clear_Lock fi fi @@ -6012,13 +6013,14 @@ 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 exit 1 else - Print_Output true "Entware found. $SCRIPT_NAME will now continue" "$PASS" + Print_Output true "Entware found [$sleepTimerSecs secs]. $SCRIPT_NAME will now continue." "$PASS" Clear_Lock fi fi From 4919e64393a281bac47ee37fd606a2573314d11e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:39:38 +0000 Subject: [PATCH 05/21] Bump actions/checkout from 4.2.2 to 5.0.0 in the all-actions group Bumps the all-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.2.2 to 5.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.2.2...v5.0.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/Create-NewReleases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index abfac586..439e9b55 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' From a615ddc91341deff9ffe2c6e5b4a844493547169 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:25:26 -0700 Subject: [PATCH 06/21] Code Improvements - Improved cleanup of files when switching from JFFS to USB and vice-versa. - Standardized the formatting of some 'sed' calls. --- README.md | 2 +- spdmerlin.sh | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c505bd7a..3441bc8a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Jul-25 +### Updated on 2025-Sep-06 ## 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 9b972589..d4d7d4e8 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Jul-25 +# Last Modified: 2025-Sep-06 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25072522" +readonly SCRIPT_VERSTAG="25090600" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -642,7 +642,7 @@ _Check_All_Interface_States_() for index in 1 2 3 4 5 do - ifaceTagStr="$excludedNotUPstr" + ifaceTagStr="$excludedNotUPstr" if _CheckNetClientInterfaceUP_ "$index" then ifaceTagStr="" #Assumes interface is included# @@ -896,7 +896,7 @@ Interfaces_FromSettings() else if ! "$interface_UP" then - sed -i "$ifacelinenumber"'s/$/ #excluded - interface not up#/' "$SCRIPT_INTERFACES_USER" + sed -i "${ifacelinenumber}s/$/ #excluded - interface not up#/" "$SCRIPT_INTERFACES_USER" fi fi done @@ -1528,17 +1528,17 @@ Generate_Interface_List() 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" @@ -1789,7 +1789,7 @@ CronTestSchedule() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-06] ## +## Modified by Martinski W. [2025-Sep-05] ## ##----------------------------------------## ScriptStorageLocation() { @@ -1800,8 +1800,10 @@ 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/.databaseupgraded" "/opt/share/$SCRIPT_NAME_LOWER.d/" 2>/dev/null @@ -1820,8 +1822,10 @@ 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/.databaseupgraded" "/jffs/addons/$SCRIPT_NAME_LOWER.d/" 2>/dev/null From 3f5fb189d236207b03b8d63875feb1d68a27b806 Mon Sep 17 00:00:00 2001 From: Martinski <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 6 Sep 2025 03:34:14 -0700 Subject: [PATCH 07/21] Code Improvements Modified code that determines if an interface is excluded when coming UP. --- spdmerlin.sh | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/spdmerlin.sh b/spdmerlin.sh index d4d7d4e8..bacfa615 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25090600" +readonly SCRIPT_VERSTAG="25090601" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -627,9 +627,9 @@ _Set_All_Interface_States_() done } -##---------------------------------=---## -## Added by Martinski W. [2025-Jun-23] ## -##-------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2025-Sep-06] ## +##----------------------------------------## _Check_All_Interface_States_() { [ -f "$SCRIPT_INTERFACES" ] && \ @@ -638,6 +638,7 @@ _Check_All_Interface_States_() printf "WAN\n" > "$SCRIPT_INTERFACES" local ifaceTagStr + local excludedButUPstr=" #excluded#" local excludedNotUPstr=" #excluded - interface not up#" for index in 1 2 3 4 5 @@ -645,7 +646,7 @@ _Check_All_Interface_States_() ifaceTagStr="$excludedNotUPstr" if _CheckNetClientInterfaceUP_ "$index" then - ifaceTagStr="" #Assumes interface is included# + ifaceTagStr="$excludedButUPstr" fi printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done @@ -655,7 +656,7 @@ _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 @@ -668,6 +669,9 @@ _Startup_All_Interface_States_() { _Check_All_Interface_States_ + [ -f "$SCRIPT_INTERFACES_USER" ] && \ + cp -a "$SCRIPT_INTERFACES_USER" "${SCRIPT_INTERFACES_USER}.bak" + while IFS='' read -r line || [ -n "$line" ] do if [ "$(grep -c "$(echo "$line" | cut -f1 -d"#" | sed 's/ *$//')" "$SCRIPT_INTERFACES_USER")" -eq 0 ] @@ -815,13 +819,14 @@ 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 excludedButUPstr=" #excluded#" local excludedNotUPstr=" #excluded - interface not up#" if [ -f "$SETTINGSFILE" ] @@ -841,7 +846,7 @@ Interfaces_FromSettings() ifaceTagStr="$excludedNotUPstr" if _CheckNetClientInterfaceUP_ "$index" then - ifaceTagStr=" #excluded#" + ifaceTagStr="$excludedButUPstr" fi printf "VPNC%s%s\n" "$index" "$ifaceTagStr" >> "$SCRIPT_INTERFACES" done @@ -851,7 +856,7 @@ 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 From 736ad92c839f37903c5d578728cad7781b75b20b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:28:50 +0000 Subject: [PATCH 08/21] Bump softprops/action-gh-release in the all-actions group Bumps the all-actions group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `softprops/action-gh-release` from 2.3.2 to 2.3.3 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2.3.2...v2.3.3) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/Create-NewReleases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 439e9b55..ce2dcc33 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -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.3.3 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.nextver.outputs.tag }} From 155e103d4152123b04d1cd01a4b25bc7f711a209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:29:49 +0000 Subject: [PATCH 09/21] Bump softprops/action-gh-release in the all-actions group Bumps the all-actions group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `softprops/action-gh-release` from 2.3.3 to 2.3.4 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2.3.3...v2.3.4) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.3.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/Create-NewReleases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index ce2dcc33..8d7a95a2 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -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.3 + uses: softprops/action-gh-release@v2.3.4 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.nextver.outputs.tag }} From 481806a791e7bcd28d7b4f651b712438a2a3e9f5 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:58:41 -0700 Subject: [PATCH 10/21] Code Improvements 1) Whenever any WireGuard or OpenVPN client is set to enabled or disabled by the user from the built-in VPN WebUI tab, new code will update the corresponding interfaces in the configuration file for spdMerlin. 2) Cosmetic changes/improvements on the WebUI page to show preferred servers entries that were longer than the width of the reserved space. 3) More checks to clean up files when switching from JFFS to USB and vice versa. 4) Added checks for duplicate entries in the interface list. 5) Miscellaneous improvements and fine-tuning. --- README.md | 2 +- spdmerlin.sh | 328 ++++++++++++++++++++++++++++++++--------------- spdstats_www.asp | 4 +- spdstats_www.js | 236 ++++++++++++++++++---------------- 4 files changed, 355 insertions(+), 215 deletions(-) diff --git a/README.md b/README.md index 3441bc8a..1fb62fd3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Sep-06 +### Updated on 2025-Oct-12 ## 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 bacfa615..cb824cc1 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Sep-06 +# Last Modified: 2025-Oct-12 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25090601" +readonly SCRIPT_VERSTAG="25101202" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -56,6 +56,9 @@ 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] ## ##-------------------------------------## @@ -68,8 +71,9 @@ readonly webPageLineTabExp="\{url: \"$webPageFileRegExp\", tabName: " readonly webPageLineRegExp="${webPageLineTabExp}\"$SCRIPT_NAME\"\}," readonly BEGIN_MenuAddOnsTag="/\*\*BEGIN:_AddOns_\*\*/" readonly ENDIN_MenuAddOnsTag="/\*\*ENDIN:_AddOns_\*\*/" -readonly branchx_TAG="Branch: $SCRIPT_BRANCH" -readonly version_TAG="${SCRIPT_VERSION}_${SCRIPT_VERSTAG}" +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 @@ -96,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 @@ -614,7 +615,7 @@ _Check_WG_ClientInterfaceUP_() ##---------------------------------=---## ## Added by Martinski W. [2025-Jun-23] ## ##-------------------------------------## -_Set_All_Interface_States_() +_Set_All_InterfacesUser_Status_() { local interfaceCount COUNTER @@ -622,7 +623,7 @@ _Set_All_Interface_States_() COUNTER=1 until [ "$COUNTER" -gt "$interfaceCount" ] do - Set_Interface_State "$COUNTER" + Set_InterfacesUser_State "$COUNTER" COUNTER="$((COUNTER + 1))" done } @@ -633,7 +634,7 @@ _Set_All_Interface_States_() _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" @@ -662,25 +663,56 @@ _Check_All_Interface_States_() 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 | sort -d -t ' ' -k 1 > "$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 theIFACE _Check_All_Interface_States_ + _CheckFor_Duplicate_Interfaces_ "$SCRIPT_INTERFACES_USER" - [ -f "$SCRIPT_INTERFACES_USER" ] && \ - cp -a "$SCRIPT_INTERFACES_USER" "${SCRIPT_INTERFACES_USER}.bak" - - 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 ] + theIFACE="$(echo "$theLine" | cut -d'#' -f1 | sed 's/ *$//')" + if [ "$(grep -wc "^$theIFACE" "$SCRIPT_INTERFACES_USER")" -eq 0 ] then - printf "%s\n" "$line" >> "$SCRIPT_INTERFACES_USER" + printf "%s\n" "$theLine" >> "$SCRIPT_INTERFACES_USER" + else + sed -i "s/^${theIFACE}.*/${theLine}/g" "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" - _Set_All_Interface_States_ + _Set_All_InterfacesUser_Status_ } ##----------------------------------------## @@ -737,6 +769,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] ## ##----------------------------------------## @@ -825,7 +878,8 @@ 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#" @@ -834,8 +888,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" @@ -861,21 +915,21 @@ Interfaces_FromSettings() 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')" @@ -893,21 +947,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 @@ -1119,21 +1175,20 @@ Auto_ServiceEvent() ##-------------------------------------## _CheckForOpenVPN_ClientsAvailable_() { - local retCode + 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# + 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 - else retCode=1 fi rm -f "$nvramTempFile" @@ -1192,10 +1247,10 @@ Auto_OpenVPN_Event() ##-------------------------------------## _CheckForWireGuard_ClientsAvailable_() { - local retCode + local retCode=1 local nvramTempFile="/tmp/${SCRIPT_NAME}_nvramShow_$$.txt" - if ! nvram get "rc_support" | grep -qio "wireguard" + if ! nvram get "rc_support" | grep -qwo "wireguard" then return 1 #WireGuard NOT supported# fi @@ -1203,14 +1258,13 @@ _CheckForWireGuard_ClientsAvailable_() if [ ! -s "$nvramTempFile" ] then rm -f "$nvramTempFile" - return 1 #WireGuard clients NOT found# + return 1 #WireGuard Clients NOT found# fi - if grep -qE "^wgc[1-5]_enable=[1-2]$" "$nvramTempFile" || \ + if grep -qE "^wgc[1-5]_enable=[1-2]$" "$nvramTempFile" || \ grep -qE "^wgc[1-5]_(ppub|priv)=.+$" "$nvramTempFile" || \ - grep -qE "^wgc[1-5]_ep_addr=([0-9]{1,3}\.){3}[0-9]{1,3}$" "$nvramTempFile" + grep -qE "^wgc[1-5]_addr=([0-9]{1,3}\.){3}[0-9]{1,3}" "$nvramTempFile" then retCode=0 - else retCode=1 fi rm -f "$nvramTempFile" @@ -1424,17 +1478,19 @@ Get_Interface_From_Name() echo "$IFACEname" } -##------------------------------------=---## -## Modified by Martinski W. [2025-Mar-08] ## ##----------------------------------------## -Set_Interface_State() +## Modified by Martinski W. [2025-Sep-06] ## +##----------------------------------------## +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 + + ifaceLineStr="$(sed "${index}!d" "$SCRIPT_INTERFACES_USER" | awk '{$1=$1};1')" - if echo "$interfaceLine" | grep -qE "^(VPNC|WGVPN)" + 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' # @@ -1449,52 +1505,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 "$vpnClientUpEvent" && [ -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 @@ -1515,8 +1581,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 @@ -1529,8 +1595,8 @@ 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" @@ -1547,9 +1613,12 @@ Generate_Interface_List() fi fi sed -i 's/ *$//' "$SCRIPT_INTERFACES_USER" + doUpdateSavedBak=true printf "\n" fi done + + "$doUpdateSavedBak" && Save_InterfacesUser_SAVEDBAK } ##----------------------------------------## @@ -1794,7 +1863,7 @@ CronTestSchedule() } ##----------------------------------------## -## Modified by Martinski W. [2025-Sep-05] ## +## Modified by Martinski W. [2025-Sep-06] ## ##----------------------------------------## ScriptStorageLocation() { @@ -1811,6 +1880,7 @@ ScriptStorageLocation() 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 @@ -1833,6 +1903,7 @@ ScriptStorageLocation() 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 @@ -1862,7 +1933,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 ;; @@ -2333,10 +2407,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" @@ -2377,6 +2453,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 @@ -3224,7 +3307,7 @@ Run_Speedtest() fi echo 'var spdteststatus = "InProgress_'"$IFACE_NAME"'";' > /tmp/detect_spdtest.js - printf "" > "$tmpfile" + printf '' > "$tmpfile" if [ "$mode" = "auto" ] then @@ -4070,27 +4153,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" } ##-------------------------------------## @@ -4595,7 +4707,7 @@ Menu_Install() } ##----------------------------------------## -## Modified by Martinski W. [2025-Jun-23] ## +## Modified by Martinski W. [2025-Oct-12] ## ##----------------------------------------## Menu_Startup() { @@ -4637,6 +4749,7 @@ 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_OpenVPN_Event create 2>/dev/null Auto_WG_ClientEvent create 2>/dev/null @@ -6108,17 +6221,23 @@ 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" JFFS_LowFreeSpaceStatus="OK" updateJFFS_SpaceInfo=false +vpnClientUpIDstr="" +vpnClientUpEvent=false +vpnClientDownEvent=false if [ "$SCRIPT_BRANCH" = "master" ] -then SCRIPT_VERS_INFO="[$branchx_TAG]" -else SCRIPT_VERS_INFO="[$version_TAG, $branchx_TAG]" +then SCRIPT_VERS_INFO="" +else SCRIPT_VERS_INFO="[$versionDev_TAG]" fi ##----------------------------------------## -## Modified by Martinski W. [2025-Feb-28] ## +## Modified by Martinski W. [2025-Oct-12] ## ##----------------------------------------## if [ $# -eq 0 ] || [ -z "$1" ] then @@ -6149,6 +6268,7 @@ then 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_OpenVPN_Event create 2>/dev/null Auto_WG_ClientEvent create 2>/dev/null @@ -6242,13 +6362,16 @@ case "$1" in if [ "$3" = "route-pre-down" ] then Print_Output true "VPN Client tunnel is going down." "$PASS" + vpnClientDownEvent=true + Save_InterfacesUser_SAVEDBAK check sleep 3 elif [ "$3" = "route-up" ] then Print_Output true "VPN Client tunnel is coming up." "$PASS" + vpnClientUpEvent=true ; vpnClientUpIDstr="$2" sleep 2 fi - _Reset_Interface_States_ force "$2" + _Reset_Interface_States_ force Clear_Lock fi exit 0 @@ -6257,13 +6380,16 @@ case "$1" in if [ "$2" = "stop" ] then Print_Output true "WireGuard Client tunnel is going down." "$PASS" + vpnClientDownEvent=true + Save_InterfacesUser_SAVEDBAK check sleep 3 elif [ "$2" = "start" ] then Print_Output true "WireGuard Client tunnel is coming up." "$PASS" + vpnClientUpEvent=true ; vpnClientUpIDstr="wg$2" sleep 2 fi - _Reset_Interface_States_ force "$2" + _Reset_Interface_States_ force Clear_Lock exit 0 ;; diff --git a/spdstats_www.asp b/spdstats_www.asp index 8f21aaaf..e94b05fe 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -33,7 +33,7 @@ p{font-weight:bolder;}thead.collapsible-jquery{color:white;padding:0;width:100%; diff --git a/spdstats_www.js b/spdstats_www.js index 03096540..be354dce 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1,14 +1,14 @@ /**----------------------------**/ -/** Last Modified: 2025-Jul-13 **/ +/** Last Modified: 2025-Oct-11 **/ /**----------------------------**/ 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]]; @@ -1178,12 +1178,12 @@ function get_manualspdtestservers_file() showhide('spdtest_serverprefselect',true); showhide('imgManualServerList',false); } - 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) { - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).prop('disabled',false); - $('#spdtest_enabled_'+interfacescomplete[i].toLowerCase()).removeClass('disabled'); + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).prop('disabled',false); + $('#spdtest_enabled_'+interfacesComplete[i].toLowerCase()).removeClass('disabled'); } } $.each($('input[name=spdtest_serverpref]'),function(){ @@ -1393,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') @@ -1570,7 +1570,7 @@ function getConfigFile() } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Jul-13] **/ +/** Modified by Martinski W. [2025-Oct-11] **/ /**----------------------------------------**/ function getInterfacesFile() { @@ -1587,13 +1587,13 @@ function getInterfacesFile() showhide('btnRunSpeedtest',true); showhide('databaseSize_text',true); - var interfaces = data.split('\n'); + var theInterfaces = data.split('\n'); const styleLeftMarginIndx = [2, 3, 4, 5, 7, 8, 9, 10]; - let interfacename, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle; - interfaces = interfaces.filter(Boolean); - interfacelist = ''; - interfacescomplete = []; - interfacesdisabled = []; + let interfaceName, ifaceExcluded, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle, indxCount; + theInterfaces = theInterfaces.filter(Boolean); + interfaceList = ''; + interfacesComplete = []; + interfacesDisabled = []; var interfacecharttablehtml='
 
'; interfacecharttablehtml += ''; @@ -1610,89 +1610,101 @@ 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 = ''; + 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?'; } // For AUTOMATIC Speedtests // - if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) + 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 += ''; interfaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // - if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) + 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 += ''; 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)) + 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 += ''; 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+''; // For AUTOMATIC Speedtests // - if (breakStartIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) + if (breakStartIndex === indxCount) { interfaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) { if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) { ifaceStyle = 'style="margin-left:6px !important;"'; } @@ -1701,11 +1713,11 @@ function getInterfacesFile() } interfaceconfigtablehtml += ''; interfaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { interfaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { interfaceconfigtablehtml += '
'; } // For PREFERRED Servers // - if (breakStartIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) + if (breakStartIndex === indxCount) { prefserverconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) { if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) { ifaceStyle = 'style="margin-left:6px !important;"'; } @@ -1714,7 +1726,7 @@ function getInterfacesFile() } prefserverconfigtablehtml += ''; prefserverconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { prefserverconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { prefserverconfigtablehtml += '
'; } // Select a Preferred Server // prefserverselecttablehtml += ''+ifaceNameUpper+':
'; @@ -1724,8 +1736,8 @@ function getInterfacesFile() prefserverselecttablehtml += '
'; // For MANUAL Speedtests // - if (breakStartIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } - if (styleLeftMarginIndx.includes(ifaceIndx)) + if (breakStartIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } + if (styleLeftMarginIndx.includes(indxCount)) { if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) { ifaceStyle = 'style="margin-left:6px !important;"'; } @@ -1734,11 +1746,11 @@ function getInterfacesFile() } speedtestifaceconfigtablehtml += ''; speedtestifaceconfigtablehtml += ''; - if (breakStopIndex === ifaceIndx) { speedtestifaceconfigtablehtml += '
'; } + if (breakStopIndex === indxCount) { speedtestifaceconfigtablehtml += '
'; } } - interfacecharttablehtml += BuildInterfaceTable(interfacename); - interfacelist += interfacename+','; + interfacecharttablehtml += BuildInterfaceTable(interfaceName); + interfaceList += interfaceName+','; } interfacecharttablehtml += '
'; @@ -1755,16 +1767,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(); @@ -2020,7 +2032,7 @@ function changeAllCharts(e) value = e.value * 1; name = e.id.substring(0,e.id.indexOf('_')); SetCookie(e.id,value); - var interfacetextarray = interfacelist.split(','); + var interfacetextarray = interfaceList.split(','); for (var i = 0; i < interfacetextarray.length; i++) { Draw_Chart(interfacetextarray[i],'Combined'); @@ -2044,7 +2056,7 @@ function changeChart(e) } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Mar-03] **/ +/** Modified by Martinski W. [2025-Oct-11] **/ /**----------------------------------------**/ function SettingHint (hintID, formField) { @@ -2052,24 +2064,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-5]/) !== null) - { hintMsg = 'WireGuard interface is not enabled.'; } - else if (formField.name.match(/^VPNC[1-5]/) !== 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-5]/) !== null) - { hintMsg = 'WireGuard interface is enabled.'; } - else if (formField.name.match(/^VPNC[1-5]/) !== 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.'; } @@ -2211,14 +2225,14 @@ function AutomaticInterfaceEnableDisable(forminput) 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++) { @@ -2237,16 +2251,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++) @@ -2384,10 +2398,10 @@ function Toggle_SpdTestServerPref(forminput) { 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); @@ -2441,12 +2455,12 @@ function GenerateManualSpdTestServerPrefSelect() 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) { - ifaceNameUpper = interfacescomplete[i].toUpperCase(); - ifaceNameLower = interfacescomplete[i].toLowerCase(); + ifaceNameUpper = interfacesComplete[i].toUpperCase(); + ifaceNameLower = interfacesComplete[i].toLowerCase(); if (ifaceNameUpper.match(/^WGVPN[1-5]/) !== null) { styleSetting = 'style="width:58px; display:none;"'; } @@ -2455,7 +2469,7 @@ function GenerateManualSpdTestServerPrefSelect() else { styleSetting = 'style="width:33px; display:none;"'; } - serverprefhtml += ''+interfacescomplete[i]+':
'; + serverprefhtml += ''+interfacesComplete[i]+':
'; } } } From 7c1bb9586ace41d20070fcf01f0c57ea71a4ce55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:59:04 +0000 Subject: [PATCH 11/21] Bump softprops/action-gh-release in the all-actions group Bumps the all-actions group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Updates `softprops/action-gh-release` from 2.3.4 to 2.4.1 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v2.3.4...v2.4.1) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-version: 2.4.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/Create-NewReleases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Create-NewReleases.yml b/.github/workflows/Create-NewReleases.yml index 8d7a95a2..fcef25d6 100644 --- a/.github/workflows/Create-NewReleases.yml +++ b/.github/workflows/Create-NewReleases.yml @@ -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.4 + uses: softprops/action-gh-release@v2.4.1 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ steps.nextver.outputs.tag }} From 7ef9065e89411f44028489d340ca660cc5cd5bb5 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Tue, 14 Oct 2025 02:56:01 -0700 Subject: [PATCH 12/21] Code Improvements & Fine-Tuning More code improvements & fine-tuning. --- README.md | 2 +- spdmerlin.sh | 9 +++++---- spdstats_www.asp | 4 ++-- spdstats_www.js | 27 +++++++++++++++++++++------ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1fb62fd3..2fc855b6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Oct-12 +### Updated on 2025-Oct-14 ## 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 cb824cc1..919a5d09 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Oct-12 +# Last Modified: 2025-Oct-14 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25101202" +readonly SCRIPT_VERSTAG="25101422" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -674,7 +674,7 @@ _CheckFor_Duplicate_Interfaces_() local dupTempFile="${1}.DUPTMP.TXT" setIFaceUserStatus=false - cat "$1" | sort -u | sort -d -t ' ' -k 1 > "$dupTempFile" + cat "$1" | sort -u > "$dupTempFile" grep -E -m1 '^WAN.*' "$dupTempFile" > "$1" for ifaceID in VPNC WGVPN @@ -707,7 +707,8 @@ _Startup_All_Interface_States_() if [ "$(grep -wc "^$theIFACE" "$SCRIPT_INTERFACES_USER")" -eq 0 ] then printf "%s\n" "$theLine" >> "$SCRIPT_INTERFACES_USER" - else + elif [ "$theIFACE" != "WAN" ] + then sed -i "s/^${theIFACE}.*/${theLine}/g" "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" diff --git a/spdstats_www.asp b/spdstats_www.asp index e94b05fe..9012f692 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -33,7 +33,7 @@ p{font-weight:bolder;}thead.collapsible-jquery{color:white;padding:0;width:100%; diff --git a/spdstats_www.js b/spdstats_www.js index be354dce..219690e8 100644 --- a/spdstats_www.js +++ b/spdstats_www.js @@ -1,5 +1,5 @@ /**----------------------------**/ -/** Last Modified: 2025-Oct-11 **/ +/** Last Modified: 2025-Oct-14 **/ /**----------------------------**/ var daysofweek = ['Mon','Tues','Wed','Thurs','Fri','Sat','Sun']; @@ -1570,7 +1570,7 @@ function getConfigFile() } /**----------------------------------------**/ -/** Modified by Martinski W. [2025-Oct-11] **/ +/** Modified by Martinski W. [2025-Oct-14] **/ /**----------------------------------------**/ function getInterfacesFile() { @@ -1587,10 +1587,19 @@ function getInterfacesFile() showhide('btnRunSpeedtest',true); showhide('databaseSize_text',true); - var theInterfaces = data.split('\n'); const styleLeftMarginIndx = [2, 3, 4, 5, 7, 8, 9, 10]; - let interfaceName, ifaceExcluded, ifaceNameUpper, ifaceNameLower, ifaceLabel, ifaceStyle, indxCount; + 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 = []; @@ -1640,7 +1649,7 @@ function getInterfacesFile() { ifaceStyle = 'style="margin-left:0px;"'; ifaceLabel = ifaceNameUpper; - var changelabel = 'Change?'; + changelabel = 'Change?'; var interfaceDisabled = ''; if (theInterfaces[ifaceIndx].indexOf('interface not up') !== -1) @@ -1650,6 +1659,11 @@ function getInterfacesFile() ifaceLabel = ''+ifaceNameUpper+''; changelabel = 'Change?'; } + else + { + ifaceLabel = ''+ifaceNameUpper+''; + changelabel = 'Change?'; + } // For AUTOMATIC Speedtests // if (breakStartIndex === indxCount) { interfaceconfigtablehtml += '
'; } @@ -1701,6 +1715,7 @@ function getInterfacesFile() { ifaceStyle = 'style="margin-left:0px;"'; ifaceLabel = ''+ifaceNameUpper+''; + changelabel = 'Change?'; // For AUTOMATIC Speedtests // if (breakStartIndex === indxCount) { interfaceconfigtablehtml += '
'; } @@ -1731,7 +1746,7 @@ function getInterfacesFile() // Select a Preferred Server // prefserverselecttablehtml += ''+ifaceNameUpper+':
'; prefserverselecttablehtml += ''; - prefserverselecttablehtml += ''; + prefserverselecttablehtml += ''; prefserverselecttablehtml += ''; prefserverselecttablehtml += '
'; From 4a5e85ac1b5e9ab117cdb56e1b795702981d002e Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sun, 19 Oct 2025 06:23:57 -0700 Subject: [PATCH 13/21] Fixed Bug When Resetting VPNs Fixed bug when resetting VPN interfaces for automatic speed tests. --- README.md | 2 +- spdmerlin.sh | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2fc855b6..e8f718af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Oct-14 +### Updated on 2025-Oct-19 ## 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 919a5d09..6381aa90 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Oct-14 +# Last Modified: 2025-Oct-19 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25101422" +readonly SCRIPT_VERSTAG="25101906" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -697,19 +697,17 @@ _CheckFor_Duplicate_Interfaces_() ##-------------------------------------## _Startup_All_Interface_States_() { - local theIFACE + local theIFaceID ifaceCount _Check_All_Interface_States_ _CheckFor_Duplicate_Interfaces_ "$SCRIPT_INTERFACES_USER" while IFS='' read -r theLine || [ -n "$theLine" ] do - theIFACE="$(echo "$theLine" | cut -d'#' -f1 | sed 's/ *$//')" - if [ "$(grep -wc "^$theIFACE" "$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" "$theLine" >> "$SCRIPT_INTERFACES_USER" - elif [ "$theIFACE" != "WAN" ] - then - sed -i "s/^${theIFACE}.*/${theLine}/g" "$SCRIPT_INTERFACES_USER" fi done < "$SCRIPT_INTERFACES" From b30e8908e2268facdd81176d46867b792b3ad6b7 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 25 Oct 2025 00:51:27 -0700 Subject: [PATCH 14/21] Update .gitattributes Explicit binaries. --- .gitattributes | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 4354e73b..8d510d02 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,9 @@ -*.tar.gz binary *.sh text eol=lf *.md text eol=lf *.asp text eol=lf +*.gz binary +*.png binary +*.jpg binary +*.jpeg binary +*.zip binary +*.tar.gz binary From 1b9db72917ac4b8d6667eb7977a30cdfe98094a4 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:27:35 -0700 Subject: [PATCH 15/21] Update .gitattributes Updated attributes. --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitattributes b/.gitattributes index 8d510d02..b8ba9ce8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,9 @@ +* 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 From 25004b620f9ffee78849bc45fc9bffdd70a3cb7b Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:37:08 -0700 Subject: [PATCH 16/21] Code Improvement --- README.md | 2 +- spdmerlin.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e8f718af..b92ea1eb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Oct-19 +### Updated on 2025-Oct-25 ## 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 6381aa90..21dbb56d 100644 --- a/spdmerlin.sh +++ b/spdmerlin.sh @@ -14,7 +14,7 @@ ## Forked from https://github.com/jackyaz/spdMerlin ## ## ## ############################################################## -# Last Modified: 2025-Oct-19 +# Last Modified: 2025-Oct-25 #------------------------------------------------------------- ############## Shellcheck directives ############# @@ -39,7 +39,7 @@ readonly SCRIPT_NAME="spdMerlin" readonly SCRIPT_NAME_LOWER="$(echo "$SCRIPT_NAME" | tr 'A-Z' 'a-z')" readonly SCRIPT_VERSION="v4.4.15" -readonly SCRIPT_VERSTAG="25101906" +readonly SCRIPT_VERSTAG="25102522" SCRIPT_BRANCH="develop" SCRIPT_REPO="https://raw.githubusercontent.com/AMTM-OSR/$SCRIPT_NAME/$SCRIPT_BRANCH" readonly SCRIPT_DIR="/jffs/addons/$SCRIPT_NAME_LOWER.d" @@ -4193,8 +4193,8 @@ ScriptHeader() 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" From 355d24a7936f7387dc202bfbb5a1cfc0fb2309a2 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:57:54 -0700 Subject: [PATCH 17/21] Removed Tomato JS Dependencies - Removed old Tomato JavaScript file references. (No Tomato JS functions were actually used) --- README.md | 2 +- spdstats_www.asp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b92ea1eb..82d2ebd7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spdMerlin ## v4.4.15 -### Updated on 2025-Oct-25 +### Updated on 2025-Oct-30 ## 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 9012f692..0da58e4d 100644 --- a/spdstats_www.asp +++ b/spdstats_www.asp @@ -26,14 +26,12 @@ p{font-weight:bolder;}thead.collapsible-jquery{color:white;padding:0;width:100%; - -