diff --git a/README.md b/README.md index 88e392610..6c28192c0 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,17 @@ -## This project is part of +
-https://github.com/arevindh/pihole-speedtest +# Pi-hole Speedtest Modded Web -## About the project +[![Join the chat at https://gitter.im/pihole-speedtest/community](https://badges.gitter.im/pihole-speedtest/community.svg)](https://gitter.im/pihole-speedtest/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/TW9TfyM) -This project is just another fun project integrating speedtest to PiHole Web UI. +Test your connection speed directly in the Pi-hole web interface! -It will be using speedtest.net on background for testing. More frequent the speed tests more data will used. +
-What does this mod have in extra ? +--- -1. Speedtest results of 1/2/4/7/30 days as graph. -2. Custom speed test server selection. -3. Detailed speedtest results page. -4. Ability to schedule speedtest interval. - -## Wiki - -Wiki is available here https://github.com/arevindh/pihole-speedtest/wiki +Please go to the [main repository](https://github.com/arevindh/pihole-speedtest) for more information, including (un)installation instructions, pull requests, and issues. ## Disclaimer -We are not affiliated or endorced by [Pi-hole](https://github.com/pi-hole/AdminLTE) - -## Use Official CLI Mode for best results. - -[Uninstall Instructions](https://github.com/arevindh/pihole-speedtest/wiki/Uninstalling-Speedtest-Mod) +We are not affiliated with or endorsed by [Pi-hole](https://github.com/pi-hole/web) diff --git a/api_speedtest.php b/api_speedtest.php index abfba81f6..5d6f6c717 100644 --- a/api_speedtest.php +++ b/api_speedtest.php @@ -10,28 +10,72 @@ exit('Direct call to api_PHP.php is not allowed!'); } -// $data = array(); +date_default_timezone_set(exec('date +%Z')); $dbSpeedtest = '/etc/pihole/speedtest.db'; +$dbSpeedtestOld = '/etc/pihole/speedtest.db.old'; $setupVars = parse_ini_file('/etc/pihole/setupVars.conf'); -if (isset($_GET['getSpeedData24hrs']) && $auth) { - $data = array_merge($data, getSpeedData24hrs($dbSpeedtest)); -} +$cmdLog = '[[ -f /tmp/pimod.log ]] && cat /tmp/pimod.log || { [[ -f /var/log/pihole/mod.log ]] && cat /var/log/pihole/mod.log || echo ""; }'; +$cmdServers = '/usr/bin/speedtest -h | grep -q official && sudo /usr/bin/speedtest -L || /usr/bin/speedtest --secure --list 2>&1'; +$cmdRun = '[[ -f /tmp/speedtest.log ]] && cat /tmp/speedtest.log || { [[ -f /etc/pihole/speedtest.log ]] && cat /etc/pihole/speedtest.log || echo ""; }'; +$cmdServersCurl = "curl 'https://c.speedtest.net/speedtest-servers-static.php' --compressed -H 'Upgrade-Insecure-Requests: 1' -H 'DNT: 1' -H 'Sec-GPC: 1'"; +$cmdServersJSON = "curl 'https://www.speedtest.net/api/js/servers' --compressed -H 'Upgrade-Insecure-Requests: 1' -H 'DNT: 1' -H 'Sec-GPC: 1'"; -if (isset($_GET['getLastSpeedtestResult']) && $auth) { - $data = array_merge($data, getLastSpeedtestResult($dbSpeedtest)); +if ($auth) { + if (isset($_GET['hasSpeedTestBackup'])) { + $data = array_merge($data, hasSpeedTestBackup($dbSpeedtestOld)); + } + if (isset($_GET['getSpeedData'])) { + $data = array_merge($data, getSpeedData($dbSpeedtest, $_GET['getSpeedData'])); + } + if (isset($_GET['getAllSpeedTestData'])) { + $data = array_merge($data, getAllSpeedTestData($dbSpeedtest)); + } + if (isset($_GET['getLatestLog'])) { + $data = array_merge($data, speedtestExecute($cmdLog)); + } + if (isset($_GET['getClosestServers'])) { + $data = array_merge($data, getServers($cmdServers)); + } + if (isset($_GET['getSpeedTestStatus'])) { + $data = array_merge($data, speedtestExecute(getStatusCmd())); + } + if (isset($_GET['getLatestRun'])) { + $data = array_merge($data, speedtestExecute($cmdRun)); + } + if (isset($_GET['curlClosestServers'])) { + $data = array_merge($data, curlServers($cmdServersCurl)); + } + if (isset($_GET['JSONClosestServers'])) { + $data = array_merge($data, JSONServers($cmdServersJSON)); + } + if (isset($_GET['getNumberOfDaysInDB'])) { + $data = array_merge($data, getNumberOfDaysInDB($dbSpeedtest)); + } + if (isset($_GET['whichSpeedtest'])) { + $data = array_merge($data, array('data' => whichSpeedtest())); + } } -if (isset($_GET['getAllSpeedTestData']) && $auth) { - $data = array_merge($data, getAllSpeedTestData($dbSpeedtest)); +function hasSpeedTestBackup($dbSpeedtestOld) +{ + $exists = file_exists($dbSpeedtestOld); + + if ($exists) { + $data = getAllSpeedTestData($dbSpeedtestOld); + } else { + $data = array(); + } + + return array('data' => !empty($data) && !empty($data['data']) ? true : false); } function getAllSpeedTestData($dbSpeedtest) { $data = getSpeedTestData($dbSpeedtest, -1); - if (isset($data['errr'])) { + if (isset($data['error'])) { return array(); } $newarr = array(); @@ -42,97 +86,54 @@ function getAllSpeedTestData($dbSpeedtest) return array('data' => $newarr); } -function getLastSpeedtestResult($dbSpeedtest) -{ - if (!file_exists($dbSpeedtest)) { - // create db of not exists - exec('sudo pihole -a -sn'); - - return array(); - } - - $db = new SQLite3($dbSpeedtest); - if (!$db) { - return array('error' => 'Unable to open DB'); - } else { - // return array("status"=>"success"); - } - - $curdate = date('Y-m-d H:i:s'); - $date = new DateTime(); - // $date->modify('-'.$durationdays.' day'); - $start_date = $date->format('Y-m-d H:i:s'); - - $sql = 'SELECT * from speedtest order by id DESC limit 1'; - - $dbResults = $db->query($sql); - - $dataFromSpeedDB = array(); - - if (!empty($dbResults)) { - while ($row = $dbResults->fetchArray(SQLITE3_ASSOC)) { - array_push($dataFromSpeedDB, $row); - } - - return $dataFromSpeedDB; - } else { - return array('error' => 'No Results'); - } - $db->close(); -} - function getSpeedTestData($dbSpeedtest, $durationdays = '1') { if (!file_exists($dbSpeedtest)) { - // create db of not exists - exec('sudo pihole -a -sn'); - return array(); } $db = new SQLite3($dbSpeedtest); - if (!$db) { - return array('error' => 'Unable to open DB'); - } else { - // return array("status"=>"success"); + if (!$db || !$db->querySingle('SELECT count(*) FROM sqlite_master WHERE type="table" AND name="speedtest"')) { + return array(); } - $curdate = date('Y-m-d H:i:s'); - $date = new DateTime(); - $date->modify('-'.$durationdays.' day'); - $start_date = $date->format('Y-m-d H:i:s'); - - if ($durationdays == -1) { + if ((int) $durationdays == -1) { $sql = 'SELECT * from speedtest order by id asc'; } else { - $sql = "SELECT * from speedtest where start_time between '{$start_date}' and '{$curdate}' order by id asc;"; + $curdate = new DateTime('now', new DateTimeZone('UTC')); + $daysago = new DateTime('now', new DateTimeZone('UTC')); + $daysago->modify('-'.$durationdays.' day'); + $curdate = $curdate->format('Y-m-d H:i:s'); + $daysago = $daysago->format('Y-m-d H:i:s'); + $sql = "SELECT * from speedtest where start_time between '{$daysago}' and '{$curdate}' order by id asc"; } $dbResults = $db->query($sql); - $dataFromSpeedDB = array(); - if (!empty($dbResults)) { while ($row = $dbResults->fetchArray(SQLITE3_ASSOC)) { array_push($dataFromSpeedDB, $row); } - - return $dataFromSpeedDB; - } else { - return array('error' => 'No Results'); } $db->close(); + + return $dataFromSpeedDB; } -function getSpeedData24hrs($dbSpeedtest) +function getSpeedData($dbSpeedtest, $durationdays = '-2') { - global $log, $setupVars; - if (isset($setupVars['SPEEDTEST_CHART_DAYS'])) { - $dataFromSpeedDB = getSpeedTestData($dbSpeedtest, $setupVars['SPEEDTEST_CHART_DAYS']); + global $setupVars; + if (isset($setupVars['SPEEDTEST_CHART_DAYS']) && $durationdays == '-2') { + $durationdays = $setupVars['SPEEDTEST_CHART_DAYS']; } else { - $dataFromSpeedDB = getSpeedTestData($dbSpeedtest); + $durationdays = (int) $durationdays < -1 ? '1' : $durationdays; } - return $dataFromSpeedDB; + $data = getSpeedTestData($dbSpeedtest, $durationdays); + if (isset($data['error'])) { + return array(); + } + + return $data; } if (!empty($_GET['csv-export'])) { @@ -177,9 +178,190 @@ function exportData() // Fetch the next line $row = $query->fetch(PDO::FETCH_ASSOC); } + + // Close the connection + $conn = null; } function print_titles($row) { echo implode(',', array_keys($row))."\n"; } + +function speedtestExecute($command) +{ + $output = array(); + $return_status = -1; + exec('/bin/bash -c \''.$command.'\'', $output, $return_status); + + if ($return_status !== 0) { + trigger_error("Executing {$command} failed.", E_USER_WARNING); + } + + return array('data' => implode("\n", $output)); +} + +function getServers($cmdServers) +{ + $array = speedtestExecute($cmdServers); + $servers = $array['data']; + + $output = explode("\n", $servers); + $output = array_filter($output); + if (count($output) > 1) { + array_shift($output); + } + $servers = implode("\n", $output); + + if ($servers === false) { + return array('error' => 'Error fetching servers'); + } else { + return array('data' => $servers); + } +} + +function curlServers($cmdServersCurl) +{ + $array = speedtestExecute($cmdServersCurl); + $xmlContent = $array['data']; + + if ($xmlContent === false) { + return array('error' => 'Error fetching XML'); + } else { + $xml = simplexml_load_string($xmlContent); + if ($xml === false) { + return array('error' => 'Error parsing XML'); + } + $serverList = array(); + foreach ($xml->servers->server as $server) { + $serverList[] = str_pad($server['id'], 5, ' ', STR_PAD_LEFT).') '.$server['sponsor'].' ('.$server['name'].', '.$server['cc'].') ('.$server['lat'].', '.$server['lon'].')'; + } + + return array('data' => implode("\n", $serverList)); + } +} + +function JSONServers($cmdServersJSON) +{ + $array = speedtestExecute($cmdServersJSON); + $jsonContent = $array['data']; + + if ($jsonContent === false) { + return array('error' => 'Error fetching JSON'); + } else { + $json = json_decode($jsonContent); + if ($json === false) { + return array('error' => 'Error parsing JSON'); + } + + $serverList = array(); + foreach ($json as $server) { + $serverList[] = str_pad($server->id, 5, ' ', STR_PAD_LEFT).') '.$server->sponsor.' ('.$server->name.', '.$server->cc.') [Distance '.$server->distance.']'; + } + + return array('data' => implode("\n", $serverList)); + } +} + +function getRemainingTime() +{ + $interval_seconds = -1; + + if (file_exists('/opt/pihole/speedtestmod/schedule_check.sh')) { + $interval_seconds = speedtestExecute("grep 'interval_seconds=' /opt/pihole/speedtestmod/schedule_check.sh | cut -d'=' -f2")['data']; + } + + // if interval_seconds is "nan", then schedule has never been set + if (strpos($interval_seconds, 'nan') !== false) { + return -1; + } + + $interval_seconds = (int) $interval_seconds; + + // if interval_seconds is less than 0, then schedule is disabled + if ($interval_seconds < 0) { + return -1; + } + + $last_run_time = -1; + if (file_exists('/etc/pihole/last_speedtest')) { + $last_run_time = file_get_contents('/etc/pihole/last_speedtest'); + $last_run_time = (int) $last_run_time; + } + + // if last_run_time is -1, then speedtest has never been run + if ($last_run_time == -1) { + return 0; + } + + return max(0, $interval_seconds - (time() - $last_run_time)); +} + +function getNumberOfDaysInDB($dbSpeedtest) +{ + $db = new SQLite3($dbSpeedtest); + if (!$db || !$db->querySingle('SELECT count(*) FROM sqlite_master WHERE type="table" AND name="speedtest"')) { + return array('data' => 0); + } + + $sql = 'SELECT start_time from speedtest order by id asc'; + $dbResults = $db->query($sql); + $dataFromSpeedDB = array(); + if (!empty($dbResults)) { + while ($row = $dbResults->fetchArray(SQLITE3_ASSOC)) { + array_push($dataFromSpeedDB, $row); + } + } + $db->close(); + + if (empty($dataFromSpeedDB)) { + return array('data' => 0); + } + + $first_date = new DateTime($dataFromSpeedDB[0]['start_time']); + $last_date = new DateTime('now', new DateTimeZone('UTC')); + $diff = $first_date->diff($last_date); + + return array('data' => $diff->days + 1); +} + +function getStatusCmd() +{ + $cmdStatus = 'echo ""'; + if (file_exists('/opt/pihole/speedtestmod/schedule_check.sh')) { + $remaining_seconds = getRemainingTime(); + if ($remaining_seconds >= 0) { + $remaining_date = sprintf('%dd %dh %dmin %ds', $remaining_seconds / 86400, $remaining_seconds / 3600 % 24, $remaining_seconds / 60 % 60, $remaining_seconds % 60); + $remaining_date = preg_replace('/^0d |(^|(?<= ))0h |(^|(?<= ))0min /', '', $remaining_date); // remove 0d 0h 0min + $remaining_date = preg_replace('/\s(\d+s)/', '', $remaining_date); // remove seconds if not needed + $cmdStatus = 'echo '.$remaining_date; + } + } elseif (file_exists('/bin/systemctl')) { + $cmdStatus = 'systemctl status pihole-speedtest.timer'; + } + + return $cmdStatus; +} + +function whichSpeedtest() +{ + if (file_exists('/usr/bin/speedtest')) { + $officialInstalled = speedtestExecute('SKIP_MOD=true ; . /opt/pihole/speedtestmod/mod.sh ; notInstalled speedtest && echo "false" || echo "true"')['data']; + + if ($officialInstalled === 'true') { + return 'official'; + } + + $version = speedtestExecute('/usr/bin/speedtest --version')['data']; + + if (strpos($version, 'LibreSpeed') !== false) { + return 'LibreSpeed'; + } + + if (strpos($version, 'Python') !== false) { + return 'sivel\'s'; + } + } + + return 'official'; +} diff --git a/img/st-chart.png b/img/st-chart.png new file mode 100644 index 000000000..c038da224 Binary files /dev/null and b/img/st-chart.png differ diff --git a/img/st-pref.png b/img/st-pref.png new file mode 100644 index 000000000..e09fa940e Binary files /dev/null and b/img/st-pref.png differ diff --git a/index.php b/index.php index 39ef7dcda..3b1016a3f 100644 --- a/index.php +++ b/index.php @@ -106,32 +106,6 @@ ?> - -
-
-
-
-

Speedtest results over last

-
-
-
- > -
-
- -
- -
-
-
-
- -
@@ -151,6 +125,27 @@
+ +
+
+
+
+

Speedtest results over last

+
+
+
+ > +
+ +
+
+ +
+
+
+
+ +
diff --git a/package-lock.json b/package-lock.json index 8b145c3ab..1160757ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "eslint-plugin-compat": "^4.2.0", "postcss": "^8.4.31", "postcss-cli": "^10.1.0", - "prettier": "3.1.0", + "prettier": "^3.1.0", "xo": "^0.56.0" } }, diff --git a/package.json b/package.json index 102cd2eae..cf5af2df8 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "eslint-plugin-compat": "^4.2.0", "postcss": "^8.4.31", "postcss-cli": "^10.1.0", - "prettier": "3.1.0", + "prettier": "^3.1.0", "xo": "^0.56.0" }, "browserslist": [ @@ -104,6 +104,5 @@ "unicorn/switch-case-braces": "off", "unicorn/no-negated-condition": "off" } - }, - "dependencies": {} + } } diff --git a/scripts/pi-hole/js/index.js b/scripts/pi-hole/js/index.js index a2294baa4..68f67c680 100644 --- a/scripts/pi-hole/js/index.js +++ b/scripts/pi-hole/js/index.js @@ -1230,3 +1230,5 @@ $(function () { window.addEventListener("resize", function () { $(".chartjs-tooltip").remove(); }); + +localStorage.setItem("speedtest_days", "-2"); diff --git a/scripts/pi-hole/js/settings.js b/scripts/pi-hole/js/settings.js index eb2f204bb..9547aed6b 100644 --- a/scripts/pi-hole/js/settings.js +++ b/scripts/pi-hole/js/settings.js @@ -472,19 +472,377 @@ $(function () { // Speedtest toggles $(function () { + const speedtestDays = $("#speedtestdays"); + const speedtestTest = $("#speedtesttest"); + const speedtestStatus = $("#speedteststatus"); + const speedtestStatusBtn = $("#speedteststatusBtn"); + + const speedtestServer = $("#speedtestserver"); + const speedtestServerBtn = $("#closestServersBtn"); + const speedtestServerCtr = $("#closestServers"); + const speedtestChartType = $("#speedtestcharttype"); const speedtestChartTypeSave = $("#speedtestcharttypesave"); - let type = localStorage?.getItem("speedtest_chart_type") || speedtestChartType.attr("value"); - - speedtestChartType.prop("checked", type === "bar"); - localStorage.setItem("speedtest_chart_type", type); + const speedtestChartPreview = $("#speedtestchartpreview"); + const speedtestChartPreviewBtn = $("#speedtestchartpreviewBtn"); const speedtestUpdate = $("#speedtestupdate"); const speedtestUninstall = $("#speedtestuninstall"); const speedtestDelete = $("#speedtestdelete"); - const speedtestTest = $("#speedtesttest"); + const speedtestDeleteLabel = speedtestDelete.parent().children("label"); + + const speedtestLog = $("#latestLog"); + const speedtestLogBtn = $("#latestLogBtn"); + + const speedtestSubmit = $("#st-submit"); + const defaultClass = "btn-primary"; + const colorClasses = ["btn-success", "btn-warning", "btn-danger"]; + + let type = localStorage?.getItem("speedtest_chart_type") || speedtestChartType.attr("value"); + speedtestChartType.prop("checked", type === "bar"); + localStorage.setItem("speedtest_chart_type", type); + + const preCode = content => { + const pre = document.createElement("pre"); + const code = document.createElement("code"); + if (typeof content === "string") { + code.textContent = content; + } else { + code.append(content); + } + + code.style.whiteSpace = "pre"; + code.style.overflowWrap = "normal"; + pre.style.width = "100%"; + pre.style.maxWidth = "100%"; + pre.style.maxHeight = "500px"; + pre.style.overflow = "auto"; + pre.style.whiteSpace = "pre"; + pre.style.marginTop = "1vw"; + pre.append(code); + return pre; + }; + + const codeBlock = (element, text, button, output) => { + if (element.find("pre").length > 0) { + element.find("pre code").text(text); + } else { + button.text("Hide " + output); + element.append(preCode(text)); + } + }; + + const whichSpeedtest = () => { + $.ajax({ + url: "api.php?whichSpeedtest", + dataType: "json", + }) + .done(function (data) { + const speedtest = data?.data; + if (speedtest) { + // set in localStorage for use in other functions + localStorage.setItem("speedtest", speedtest); + } else { + localStorage.setItem("speedtest", "official"); + } + }) + .fail(function () { + localStorage.setItem("speedtest", "unknown"); + }); + }; + + const serviceStatus = () => { + whichSpeedtest(); + const speedtestVersion = localStorage.getItem("speedtest") || "unknown"; + $.ajax({ + url: "api.php?getSpeedTestStatus", + dataType: "json", + }) + .done(function (data) { + const status = data?.data; + let scheduleStatusText = "inactive"; + let triggerText = speedtestTest.attr("value") ? " awaiting confirmation" : " disabled"; + if (status) { + if (!status.includes("timer")) { + scheduleStatusText = "active"; + if (!speedtestTest.attr("value")) { + const triggerPattern = /(\d+s)/; + const triggerMatch = status.match(triggerPattern); + + let statusText = status; + if (triggerMatch) { + const now = new Date(); + const secondsUntilNextMinute = 60 - now.getSeconds(); + const statusSeconds = parseInt(triggerMatch[0].replace("s", ""), 10); + statusText = + statusSeconds > secondsUntilNextMinute + ? `${statusSeconds - secondsUntilNextMinute}s` + : "0s"; + } + + triggerText = statusText === "0s" ? " queued" : ` in ${status}`; + } + } else { + const scheduleStatusPattern = /pihole-speedtest\.timer.*?Active:\s+(\w+)/s; + const triggerPattern = /Trigger:.*?;\s*([\d\s\w]+)\s+left/s; + + const scheduleStatusMatch = status.match(scheduleStatusPattern); + const triggerMatch = status.match(triggerPattern); + + scheduleStatusText = scheduleStatusMatch ? scheduleStatusMatch[1] : "missing"; + if (!speedtestTest.attr("value")) { + if (triggerMatch) { + triggerText = ` in ${triggerMatch[1]}`; + } else if (scheduleStatusText === "active") { + triggerText = " running"; + } + } + } + } + + $.ajax({ + url: "api.php?getLatestRun", + dataType: "json", + }) + .done(function (data) { + const lastRun = data?.data; + let lastRunText = "Latest run is unavailable"; + if (lastRun) { + lastRunText = `Latest run:\n\n${lastRun}`; + } + + const statusText = `Using ${speedtestVersion} CLI\nSchedule is ${scheduleStatusText}\nNext run is${triggerText}\n${lastRunText}`; + codeBlock(speedtestStatus, statusText, speedtestStatusBtn, "status"); + }) + .fail(function () { + const lastRunText = "Latest run is unavailable"; + const statusText = `Using ${speedtestVersion} CLI\nSchedule is ${scheduleStatusText}\nNext run is${triggerText}\n${lastRunText}`; + codeBlock(speedtestStatus, statusText, speedtestStatusBtn, "status"); + }); + }) + .fail(function () { + const triggerText = speedtestTest.attr("value") ? "awaiting confirmation" : "unknown"; + const lastRunText = "Latest run is unavailable"; + const statusText = `Using ${speedtestVersion} CLI\nSchedule is unavailable\nNext run is ${triggerText}\n${lastRunText}`; + codeBlock(speedtestStatus, statusText, speedtestStatusBtn, "status"); + }); + }; + + const drawChart = (days, type) => { + if (days === "-1") { + days = "however many"; + } + + if (days === "1") { + days = "24 hours"; + } else { + days += " days"; + } + + const colDiv = document.createElement("div"); + const boxDiv = document.createElement("div"); + const boxHeaderDiv = document.createElement("div"); + const h3 = document.createElement("h3"); + const boxBodyDiv = document.createElement("div"); + const chartDiv = document.createElement("div"); + const canvas = document.createElement("canvas"); + const overlayDiv = document.createElement("div"); + const i = document.createElement("i"); + + colDiv.className = "col-md-12"; + colDiv.style.marginTop = "1vw"; + boxDiv.className = "box"; + boxDiv.id = "queries-over-time"; + boxHeaderDiv.className = "box-header with-border"; + h3.className = "box-title"; + h3.textContent = `Speedtest results over last ${days}`; + boxBodyDiv.className = "box-body"; + chartDiv.className = "chart"; + chartDiv.style.position = "relative"; + chartDiv.style.width = "100%"; + chartDiv.style.height = "180px"; + canvas.id = "speedOverTimeChart"; + canvas.setAttribute("value", type); + overlayDiv.className = "overlay"; + overlayDiv.id = "speedOverTimeChartOverlay"; + i.className = "fa fa-sync fa-spin"; + + colDiv.append(boxDiv); + boxDiv.append(boxHeaderDiv); + boxDiv.append(boxBodyDiv); + boxDiv.append(overlayDiv); + boxHeaderDiv.append(h3); + boxBodyDiv.append(chartDiv); + overlayDiv.append(i); + chartDiv.append(canvas); + + speedtestChartPreview.find("div").remove(); + speedtestChartPreview.append(colDiv); + }; + + const previewChart = preview => { + if (!preview) { + localStorage.setItem("speedtest_preview_hidden", "true"); + localStorage.setItem("speedtest_preview_shown", "false"); + speedtestChartPreview.find("div").remove(); + } else { + const speedtestdays = speedtestDays.val(); + localStorage.setItem("speedtest_days", speedtestdays); + localStorage.setItem("speedtest_chart_type", type); + localStorage.setItem("speedtest_preview_shown", "true"); + + $.ajax({ + url: "api.php?getNumberOfDaysInDB", + dataType: "json", + }) + .done(function (data) { + drawChart(speedtestdays === "-1" && data ? data.data : speedtestdays, type); + }) + .fail(function () { + drawChart(speedtestdays, type); + }); + } + + speedtestChartPreviewBtn.text(preview ? "Hide preview" : "Show chart preview"); + }; + + const latestLog = () => { + $.ajax({ + url: "api.php?getLatestLog", + dataType: "json", + }) + .done(function (data) { + const log = data?.data; + if (log) { + speedtestLog.find("p").remove(); + codeBlock(speedtestLog, log, speedtestLogBtn, "log"); + } else { + codeBlock( + speedtestLog, + "tmux a -t pimod; cat /var/log/pihole/mod.log", + speedtestLogBtn, + "log" + ); + if (speedtestLog.find("p").length === 0) { + speedtestLog.append( + `

Use this command to get the log while the connection is reestablished

` + ); + } + } + }) + .fail(function () { + codeBlock( + speedtestLog, + "tmux a -t pimod; cat /var/log/pihole/mod.log", + speedtestLogBtn, + "log" + ); + if (speedtestLog.find("p").length === 0) { + speedtestLog.append( + `

Use this command to get the log while the connection is reestablished

` + ); + } + }); + }; + + const closestServers = cmds => { + const tryNextCmd = () => { + if (cmds.length === 1) { + speedtestServerBtn.text("Failed to display servers"); + if (speedtestServerCtr.find("p").length === 0) { + speedtestServerCtr.append( + `

Please download the results: XML | JSON

` + ); + } + } else { + closestServers(cmds.slice(1)); + } + }; + + if (!cmds || cmds.length === 0) { + whichSpeedtest(); + const speedtestVersion = localStorage.getItem("speedtest"); + if (speedtestVersion === "LibreSpeed") { + closestServers(["getClosestServers"]); + } else { + closestServers(["JSONClosestServers", "getClosestServers", "curlClosestServers"]); + } + } else { + $.ajax({ + url: `api.php?${cmds[0]}`, + dataType: "json", + }) + .done(function (data) { + const serversInfo = data?.data; + if (serversInfo) { + speedtestServerCtr.find("p").remove(); + codeBlock(speedtestServerCtr, serversInfo, speedtestServerBtn, "servers"); + } else { + tryNextCmd(); + } + }) + .fail(function () { + tryNextCmd(); + }); + } + }; + + const hasBackup = callback => { + $.ajax({ + url: "api.php?hasSpeedTestBackup", + dataType: "json", + }) + .done(function (backupExists) { + callback(true, backupExists); + }) + .fail(function () { + callback(true, false); + }); + }; + + const hasHistory = callback => { + $.ajax({ + url: "api.php?getAllSpeedTestData", + dataType: "json", + }) + .done(function (results) { + callback(null, results?.data?.length !== 0); + }) + .fail(function () { + callback(true, false); + }); + }; + + const canRestore = () => { + hasBackup((errorBackup, backupExists) => { + hasHistory((errorHistory, historyExists) => { + if (errorBackup && errorHistory && !errorHistory) { + return; + } + + const didFlush = backupExists && !historyExists; + let newClass = defaultClass; + speedtestDeleteLabel.text( + didFlush ? "Restore History (available until the next speedtest)" : "Clear History" + ); + + if (speedtestUninstall.attr("value")) { + newClass = + (didFlush && speedtestDelete.attr("value")) || + (historyExists && !speedtestDelete.attr("value")) + ? colorClasses[1] + : colorClasses[2]; + } else if (speedtestDelete.attr("value")) { + newClass = didFlush ? colorClasses[0] : colorClasses[1]; + } + + speedtestSubmit.removeClass([...colorClasses, defaultClass].join(" ")).addClass(newClass); + }); + }); + }; document.addEventListener("DOMContentLoaded", function () { + speedtestDays.attr("value", speedtestDays.val()); speedtestChartTypeSave.attr("value", null); speedtestUpdate.attr("value", null); speedtestUninstall.attr("value", null); @@ -492,41 +850,117 @@ $(function () { speedtestTest.attr("value", null); }); + localStorage.setItem("speedtest_days", speedtestDays.val()); + speedtestDays.on("change", function () { + speedtestDays.attr("value", speedtestDays.val()); + if (speedtestDays.val()) { + localStorage.setItem("speedtest_days", speedtestDays.val()); + previewChart(speedtestChartPreview.find("div").length > 0); + } + }); + speedtestChartType.on("click", function () { // if type null, set to "bar", else toggle type = type ? (type === "bar" ? "line" : "bar") : "bar"; speedtestChartType.attr("value", type); localStorage.setItem("speedtest_chart_type", type); - // Call check messages to make new setting effective checkMessages(); + previewChart(speedtestChartPreview.find("div").length > 0); }); speedtestChartTypeSave.on("click", function () { speedtestChartTypeSave.attr("value", speedtestChartTypeSave.attr("value") ? null : type); }); + speedtestChartPreviewBtn.on("click", function () { + previewChart(speedtestChartPreview.find("div").length === 0); + }); + speedtestUpdate.on("click", function () { speedtestUpdate.attr("value", speedtestUpdate.attr("value") ? null : "up"); }); + speedtestTest.on("click", function () { + speedtestTest.attr("value", speedtestTest.attr("value") ? null : "yes"); + const status = speedtestStatus.find("pre"); + if (status.length > 0) { + serviceStatus(); + } + }); + + speedtestStatusBtn.on("click", function () { + const status = speedtestStatus.find("pre"); + if (status.length > 0) { + speedtestStatusBtn.text("Show service status"); + status.remove(); + } else { + speedtestStatusBtn.text("Hide status"); + serviceStatus(); + } + }); + + speedtestServer.on("change", function () { + speedtestServer.attr("value", speedtestServer.val()); + }); + + speedtestLogBtn.on("click", function () { + const log = speedtestLog.find("pre"); + const info = speedtestLog.find("p"); + if (log.length > 0 || info.length > 0) { + log.remove(); + info.remove(); + speedtestLogBtn.text("Show latest log"); + } else { + latestLog(); + } + }); + + speedtestServerBtn.on("click", function () { + const closestServersList = speedtestServerCtr.find("pre"); + if (closestServersList.length > 0) { + closestServersList.remove(); + speedtestServerBtn.text("Show available servers"); + } else { + speedtestServerBtn.text("Retrieving servers..."); + closestServers(); + } + }); + speedtestUninstall.on("click", function () { speedtestUninstall.attr("value", speedtestUninstall.attr("value") ? null : "un"); + canRestore(); }); speedtestDelete.on("click", function () { speedtestDelete.attr("value", speedtestDelete.attr("value") ? null : "db"); - $("#st-submit").toggleClass("btn-primary"); - $("#st-submit").toggleClass("btn-danger"); + canRestore(); }); - speedtestTest.on("click", function () { - speedtestTest.attr("value", speedtestTest.attr("value") ? null : "yes"); - }); + setInterval(() => { + if (speedtestStatus.find("pre").length > 0) { + serviceStatus(); + } - const speedtestServer = $("#speedtestserver"); + if (speedtestLog.find("pre").length > 0) { + latestLog(); + } - speedtestServer.on("change", function () { - speedtestServer.attr("value", speedtestServer.val()); - }); + // if speedtestLog has a p element, cycle through ellipsis + const info = speedtestLog.find("p"); + if (info.length > 0) { + const text = info.text(); + if (text.includes("...")) { + info.text(text.replace(/\.{3}/, "")); + } else { + info.text(text + "."); + } + } + + if (speedtestServerCtr.find("p").length > 0) { + closestServers(); + } + + canRestore(); + }, 1000); }); diff --git a/scripts/pi-hole/js/speedresults.js b/scripts/pi-hole/js/speedresults.js index 01b54c44f..9160d7aed 100644 --- a/scripts/pi-hole/js/speedresults.js +++ b/scripts/pi-hole/js/speedresults.js @@ -26,13 +26,13 @@ $(document).ready(function () { // Do we want to filter queries? var GETDict = {}; location.search - .substr(1) + .slice(1) .split("&") .forEach(function (item) { GETDict[item.split("=")[0]] = item.split("=")[1]; }); - var APIstring = "api.php?getAllSpeedTestData&PHP"; + var APIstring = "api.php?getAllSpeedTestData"; tableApi = $("#all-queries").DataTable({ dom: @@ -48,12 +48,20 @@ $(document).ready(function () { null, { render: function (data, type, _full, _meta) { - return type === "display" ? moment(data).format("Y-MM-DD HH:mm:ss z") : data; + if (type === "display") { + data = moment.utc(data, "YYYY-MM-DD HH:mm:ss").local().format(); + } + + return data; }, }, { render: function (data, type, _full, _meta) { - return type === "display" ? moment(data).format("Y-MM-DD HH:mm:ss z") : data; + if (type === "display") { + data = moment.utc(data, "YYYY-MM-DD HH:mm:ss").local().format(); + } + + return data; }, }, null, diff --git a/scripts/pi-hole/js/speedtest.js b/scripts/pi-hole/js/speedtest.js index d798b770b..b4c52cda1 100644 --- a/scripts/pi-hole/js/speedtest.js +++ b/scripts/pi-hole/js/speedtest.js @@ -1,53 +1,35 @@ -/* global Chart:false, moment:false, utils:false */ +/* global Chart:false, moment:false */ -$(function () { - var speedlabels = []; - var downloadspeed = []; - var uploadspeed = []; - var serverPing = []; - - function updateSpeedTestData() { - function formatDate(itemdate, results) { - // if the the first and last time are 24 hours apart or more - // then return the date and time, otherwise return the time - let format = "HH:mm"; - if (results.length > 1) { - const first = moment(results[0].start_time); - const last = moment(results.at(-1).start_time); - if (last.diff(first, "hours") >= 24) format = "Do " + format; - } - - return moment(itemdate).format(format); - } - - $.ajax({ - url: "api.php?getSpeedData24hrs&PHP", - dataType: "json", - }).done(function (results) { - results.forEach(function (packet) { - // console.log(speedlabels.indexOf(formatDate(packet.start_time))); - if (speedlabels.indexOf(formatDate(packet.start_time, results)) === -1) { - speedlabels.push(formatDate(packet.start_time, results)); - uploadspeed.push(parseFloat(packet.upload)); - downloadspeed.push(parseFloat(packet.download)); - serverPing.push(parseFloat(packet.server_ping)); - } - }); - speedChart.update(); - }); +function getGraphType(speedtest = 0) { + // Only return line if `barchart_chkbox` is explicitly set to false. Else return bar + if (!speedtest) { + return localStorage?.getItem("barchart_chkbox") === "false" ? "line" : "bar"; } - setInterval(function () { - // console.log('updateSpeedTestData'); - updateSpeedTestData(); - }, 6000); + return localStorage?.getItem("speedtest_chart_type") || "line"; +} - var gridColor = $(".graphs-grid").css("background-color"); - var ticksColor = $(".graphs-ticks").css("color"); +function getCSSval(cssclass, cssproperty) { + var elem = $("
"), + val = elem.appendTo("body").css(cssproperty); + elem.remove(); + return val; +} - var speedChartctx = document.getElementById("speedOverTimeChart").getContext("2d"); - var speedChart = new Chart(speedChartctx, { - type: utils.getGraphType(1), +var speedChart = null; +var speedlabels = []; +var downloadspeed = []; +var uploadspeed = []; +var serverPing = []; +function createChart() { + var gridColor = getCSSval("graphs-grid", "background-color"); + var ticksColor = getCSSval("graphs-ticks", "color"); + const chartElement = document.getElementById("speedOverTimeChart"); + if (chartElement === null || chartElement === undefined) return; + var speedChartctx = chartElement.getContext("2d"); + if (speedChartctx === null || speedChartctx === undefined) return; + speedChart = new Chart(speedChartctx, { + type: getGraphType(1), data: { labels: speedlabels, datasets: [ @@ -56,27 +38,33 @@ $(function () { data: downloadspeed, backgroundColor: "rgba(0, 123, 255, 0.5)", borderColor: "rgba(0, 123, 255, 1)", - borderWidth: 1, - cubicInterpolationMode: "monotone", + borderWidth: 3, yAxisID: "y-axis-1", + tension: 0.4, + pointHitRadius: 5, + pointHoverRadius: 5, }, { label: "Mbps Upload", data: uploadspeed, backgroundColor: "rgba(40, 167, 69, 0.5)", borderColor: "rgba(40, 167, 69, 1)", - borderWidth: 1, - cubicInterpolationMode: "monotone", + borderWidth: 3, yAxisID: "y-axis-1", + tension: 0.4, + pointHitRadius: 5, + pointHoverRadius: 5, }, { label: "ms Ping", data: serverPing, backgroundColor: "rgba(108, 117, 125, 0.5)", borderColor: "rgba(108, 117, 125, 1)", - borderWidth: 1, - cubicInterpolationMode: "monotone", + borderWidth: 3, yAxisID: "y-axis-2", + tension: 0.4, + pointHitRadius: 5, + pointHoverRadius: 5, }, ], }, @@ -98,16 +86,13 @@ $(function () { }, tooltip: { mode: "index", - intersect: utils.getGraphType(1) === "bar", + intersect: getGraphType(1) === "bar", yAlign: "bottom", callbacks: { label: function (context) { - const varParsed = - context.parsed !== "undefined" && context.parsed !== "undefined" - ? context.parsed.y - : null; - const varLabel = context.dataset !== "undefined" ? context.dataset.label : null; - return Math.round(varParsed) + " " + varLabel; + return ( + Math.round(context?.parsed?.y ?? 0) + " " + (context?.dataset?.label ?? "") || null + ); }, }, }, @@ -136,8 +121,97 @@ $(function () { position: "right", }, }, + elements: { + point: { + radius: speedlabels.length > 1 ? 0 : 6, + }, + }, }, }); +} + +function formatDate(itemdate, results) { + let output = "HH:mm"; + if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { + return moment.utc(itemdate, "YYYY-MM-DD HH:mm:ss").local().format(output); + } + + const first = moment(results.at(0).start_time, "YYYY-MM-DD HH:mm:ssZ"); + if (moment.utc().diff(first, "hours") > 24) { + output = "Do HH:mm"; + } + + return moment.utc(itemdate, "YYYY-MM-DD HH:mm:ss").local().format(output); +} + +function updateSpeedTestData() { + const daysIsTheSame = days === localStorage?.getItem("speedtest_days"); + const typeIsTheSame = type === localStorage?.getItem("speedtest_chart_type"); + const beenHidden = localStorage?.getItem("speedtest_preview_hidden") === "true"; + days = localStorage?.getItem("speedtest_days") || "-2"; + type = localStorage?.getItem("speedtest_chart_type") || "line"; + + speedlabels = []; + downloadspeed = []; + uploadspeed = []; + serverPing = []; + + $.ajax({ + url: "api.php?getSpeedData=" + days, + dataType: "json", + }).done(function (results) { + results?.forEach(function (packet) { + // console.log(speedlabels.indexOf(formatDate(packet.start_time))); + if (speedlabels.indexOf(formatDate(packet.start_time, results)) === -1) { + speedlabels.push(formatDate(packet.start_time, results)); + uploadspeed.push(parseFloat(packet.upload)); + downloadspeed.push(parseFloat(packet.download)); + serverPing.push(parseFloat(packet.server_ping)); + } + }); + if ( + speedChart && + (!daysIsTheSame || + !typeIsTheSame || + beenHidden || + (type === "line" && speedChart.data?.labels?.length < 2 && speedlabels?.length > 1)) && + days !== "-2" + ) { + speedChart.destroy(); + speedChart = null; + } + + if (!speedChart) { + localStorage.setItem( + "speedtest_preview_hidden", + !localStorage?.getItem("speedtest_preview_shown") + ); + createChart(); + } + + if ( + speedChart && + speedChart !== null && + speedChart !== undefined && + speedChart.data.labels !== speedlabels + ) { + speedChart.data.labels = speedlabels; + speedChart.data.datasets[0].data = downloadspeed; + speedChart.data.datasets[1].data = uploadspeed; + speedChart.data.datasets[2].data = serverPing; + speedChart.update(); + } + + $("#speedOverTimeChartOverlay").css("display", "none"); + }); +} +var days = ""; +var type = ""; + +$(function () { updateSpeedTestData(); + setInterval(function () { + updateSpeedTestData(); + }, 1000); }); diff --git a/scripts/pi-hole/php/footer.php b/scripts/pi-hole/php/footer.php index 15dd17297..b4033ec90 100644 --- a/scripts/pi-hole/php/footer.php +++ b/scripts/pi-hole/php/footer.php @@ -87,13 +87,20 @@ · Update available! +
  • + Speedtest + + · Update available! +
  • To install updates, replace this old container with a fresh upgraded image. - To install updates, run pihole -up. + To install updates, uninstall Speedtest Mod, run pihole -up if there's a new official Pi-hole release, and reinstall Speedtest Mod. The Reinstall button in [Settings/Speedtest] can do this for you! + + To install updates, reinstall Speedtest Mod. The Reinstall button in [Settings/Speedtest] can do this for you!

    diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php index 74e1f55b3..a23938a22 100644 --- a/scripts/pi-hole/php/func.php +++ b/scripts/pi-hole/php/func.php @@ -211,7 +211,7 @@ function getCustomDNSEntries() continue; } - $data = new \stdClass(); + $data = new stdClass(); $data->ip = $explodedLine[0]; $data->domain = $explodedLine[1]; $data->domains = array_slice($explodedLine, 0, -1); @@ -289,7 +289,7 @@ function addCustomDNSEntry($ip = '', $domain = '', $reload = '', $json = true, $ } return returnSuccess('', $json); - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage(), $json); } } @@ -328,7 +328,7 @@ function deleteCustomDNSEntry() pihole_execute('-a removecustomdns '.$ip.' '.$domain); return returnSuccess(); - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage()); } } @@ -345,7 +345,7 @@ function deleteAllCustomDNSEntries($reload = '') foreach ($existingEntries as $entry) { pihole_execute('-a removecustomdns '.$entry->ip.' '.$entry->domain.' '.$reload); } - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage()); } @@ -389,7 +389,7 @@ function getCustomCNAMEEntries() continue; } - $data = new \stdClass(); + $data = new stdClass(); $data->domains = array_slice($explodedLine, 0, -1); $data->domain = implode(',', $data->domains); $data->target = $explodedLine[count($explodedLine) - 1]; @@ -471,7 +471,7 @@ function addCustomCNAMEEntry($domain = '', $target = '', $reload = '', $json = t } return returnSuccess('', $json); - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage(), $json); } } @@ -510,7 +510,7 @@ function deleteCustomCNAMEEntry() pihole_execute('-a removecustomcname '.$domain.' '.$target); return returnSuccess(); - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage()); } } @@ -527,7 +527,7 @@ function deleteAllCustomCNAMEEntries($reload = '') foreach ($existingEntries as $entry) { pihole_execute('-a removecustomcname '.$entry->domain.' '.$entry->target.' '.$reload); } - } catch (\Exception $ex) { + } catch (Exception $ex) { return returnError($ex->getMessage()); } diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php index 7cd418609..67b125091 100644 --- a/scripts/pi-hole/php/groups.php +++ b/scripts/pi-hole/php/groups.php @@ -51,7 +51,7 @@ function verify_ID_array($arr) header('Content-type: application/json'); echo json_encode(array('data' => $data)); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'add_group') { @@ -93,7 +93,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'edit_group') { @@ -134,7 +134,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'delete_group') { @@ -167,7 +167,7 @@ function verify_ID_array($arr) } $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'get_clients') { @@ -247,7 +247,7 @@ function verify_ID_array($arr) header('Content-type: application/json'); echo json_encode(array('data' => $data)); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'get_unconfigured_clients') { @@ -329,7 +329,7 @@ function verify_ID_array($arr) header('Content-type: application/json'); echo json_encode($ips); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'add_client') { @@ -372,7 +372,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'edit_client') { @@ -443,7 +443,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'delete_client') { @@ -481,7 +481,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'get_domains') { @@ -525,7 +525,7 @@ function verify_ID_array($arr) header('Content-type: application/json'); echo json_encode(array('data' => $data)); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'add_domain' || $_POST['action'] == 'replace_domain') { @@ -693,7 +693,7 @@ function verify_ID_array($arr) } $reload = true; JSON_success($msg); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'edit_domain') { @@ -778,7 +778,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'delete_domain') { @@ -817,7 +817,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'delete_domain_string') { @@ -869,7 +869,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'get_adlists') { @@ -897,7 +897,7 @@ function verify_ID_array($arr) header('Content-type: application/json'); echo json_encode(array('data' => $data)); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'add_adlist') { @@ -978,7 +978,7 @@ function verify_ID_array($arr) $msg = $added_list.'
    Total: '.$total.' adlist(s) processed.'; JSON_success($msg); } - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'edit_adlist') { @@ -1059,7 +1059,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'delete_adlist') { @@ -1100,7 +1100,7 @@ function verify_ID_array($arr) $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } elseif ($_POST['action'] == 'add_audit') { @@ -1157,7 +1157,7 @@ function verify_ID_array($arr) // Reloading isn't necessary for audit domains (no effect on blocking) $reload = false; JSON_success($msg); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } else { diff --git a/scripts/pi-hole/php/header_authenticated.php b/scripts/pi-hole/php/header_authenticated.php index c35a9398d..1a66cbbdd 100644 --- a/scripts/pi-hole/php/header_authenticated.php +++ b/scripts/pi-hole/php/header_authenticated.php @@ -149,15 +149,52 @@ function getTemperature() // Get memory usage $memory_usage = getMemUsage(); +$dbSpeedtest = '/etc/pihole/speedtest.db'; +function getNumberOfDaysInDB($dbSpeedtest) +{ + $db = new SQLite3($dbSpeedtest); + if (!$db || !$db->querySingle('SELECT count(*) FROM sqlite_master WHERE type="table" AND name="speedtest"')) { + return 0; + } + + $sql = 'SELECT start_time from speedtest order by id asc'; + $dbResults = $db->query($sql); + $dataFromSpeedDB = array(); + if (!empty($dbResults)) { + while ($row = $dbResults->fetchArray(SQLITE3_ASSOC)) { + array_push($dataFromSpeedDB, $row); + } + } + $db->close(); + + if (empty($dataFromSpeedDB)) { + return 0; + } + + $first_date = new DateTime($dataFromSpeedDB[0]['start_time']); + $last_date = new DateTime('now', new DateTimeZone('UTC')); + $diff = $first_date->diff($last_date); + + return $diff->days + 1; +} + +$speedtestschedule = false; +$speedtestdays = ''; +$speedtestcharttype = 'line'; if (isset($setupVars['SPEEDTESTSCHEDULE'])) { - $speedtestshedule = $setupVars['SPEEDTESTSCHEDULE']; -} else { - $speedtestshedule = false; + $speedtestschedule = $setupVars['SPEEDTESTSCHEDULE']; } -if (isset($setupVars['SPEEDTEST_CHART_DAYS']) && $setupVars['SPEEDTEST_CHART_DAYS'] > 1) { - $speedtestdays = $setupVars['SPEEDTEST_CHART_DAYS'].' Days'; -} else { - $speedtestdays = '24 Hours'; +if (isset($setupVars['SPEEDTEST_CHART_DAYS'])) { + if ($setupVars['SPEEDTEST_CHART_DAYS'] == -1) { + $speedtestdays = getNumberOfDaysInDB($dbSpeedtest).' days'; + } elseif ($setupVars['SPEEDTEST_CHART_DAYS'] == 1) { + $speedtestdays = '24 hours'; + } else { + $speedtestdays = $setupVars['SPEEDTEST_CHART_DAYS'].' days'; + } +} +if (isset($setupVars['SPEEDTEST_CHART_TYPE'])) { + $speedtestcharttype = $setupVars['SPEEDTEST_CHART_TYPE']; } $piholeFTLConf = piholeFTLConfig(); diff --git a/scripts/pi-hole/php/message.php b/scripts/pi-hole/php/message.php index 7ecfccea6..3f54bba37 100644 --- a/scripts/pi-hole/php/message.php +++ b/scripts/pi-hole/php/message.php @@ -50,7 +50,7 @@ $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } else { diff --git a/scripts/pi-hole/php/network.php b/scripts/pi-hole/php/network.php index 4b570cea4..be6b68811 100644 --- a/scripts/pi-hole/php/network.php +++ b/scripts/pi-hole/php/network.php @@ -57,7 +57,7 @@ $reload = true; JSON_success(); - } catch (\Exception $ex) { + } catch (Exception $ex) { JSON_error($ex->getMessage()); } } else { diff --git a/scripts/pi-hole/php/savesettings.php b/scripts/pi-hole/php/savesettings.php index f223c0953..6cd8fe0ba 100644 --- a/scripts/pi-hole/php/savesettings.php +++ b/scripts/pi-hole/php/savesettings.php @@ -583,12 +583,8 @@ function addStaticDHCPLease($mac, $ip, $hostname) break; case 'speedtest': - if (isset($_POST['speedtestmode'])) { - pihole_execute('-a -sm '.trim($_POST['speedtestmode'])); - } - if (isset($_POST['speedtestschedule'])) { - pihole_execute('-a -s '.trim($_POST['speedtestschedule'])); + pihole_execute('-a -s '.trim($_POST['speedtestschedule']), true); } if (isset($_POST['clearspeedtests'])) { @@ -601,6 +597,10 @@ function addStaticDHCPLease($mac, $ip, $hostname) pihole_execute('-a -ss '.trim($_POST['speedtestserver'])); } + if (isset($_POST['speedtestserver']) && empty($_POST['speedtestserver'])) { + pihole_execute('-a -ss auto'); + } + if (isset($_POST['speedtestdays'])) { pihole_execute('-a -sd '.trim($_POST['speedtestdays'])); } @@ -622,7 +622,7 @@ function addStaticDHCPLease($mac, $ip, $hostname) if (isset($_POST['speedtesttest'])) { $success .= ' and a speedtest has been started'; - pihole_execute('-a -sn'); + pihole_execute('-a -sn', !isset($_POST['speedtestuninstall'])); } if (isset($_POST['speedtestupdate'])) { @@ -630,14 +630,14 @@ function addStaticDHCPLease($mac, $ip, $hostname) if (isset($_POST['speedtestuninstall'])) { $success .= ', but the Mod will be uninstalled'; if (isset($_POST['speedtestdelete'])) { - $success .= ' and the database will be deleted'; + $success .= ' and its history will be deleted'; pihole_execute('-a -up un db', true); } else { pihole_execute('-a -up un', true); } } else { if (isset($_POST['speedtestdelete'])) { - $success .= ' and the database will be flushed'; + $success .= ' and its history will be modified'; pihole_execute('-a -up db', true); } else { pihole_execute('-a -up', true); @@ -646,13 +646,13 @@ function addStaticDHCPLease($mac, $ip, $hostname) } elseif (isset($_POST['speedtestuninstall'])) { $success .= ' and the Mod will be uninstalled'; if (isset($_POST['speedtestdelete'])) { - $success .= ' and the database will be deleted'; + $success .= ' and its history will be deleted'; pihole_execute('-a -un db', true); } else { pihole_execute('-a -un', true); } } elseif (isset($_POST['speedtestdelete'])) { - $success .= ' and the database will be flushed'; + $success .= ' and its history will be modified'; pihole_execute('-a -db', true); } break; diff --git a/scripts/pi-hole/php/update_checker.php b/scripts/pi-hole/php/update_checker.php index cd2ce2586..e2be46944 100644 --- a/scripts/pi-hole/php/update_checker.php +++ b/scripts/pi-hole/php/update_checker.php @@ -22,6 +22,10 @@ function checkUpdate($currentVersion, $latestVersion) $web_current = 'N/A'; $web_update = false; + $speedtest_branch = 'master'; + $speedtest_current = 'N/A'; + $speedtest_update = false; + $FTL_current = 'N/A'; $FTL_update = false; @@ -49,6 +53,15 @@ function checkUpdate($currentVersion, $latestVersion) $web_current = explode('-', $versions['WEB_VERSION'])[0]; } + // Get Speedtest Mod branch / version / commit + $speedtest_branch = $versions['SPEEDTEST_BRANCH']; + if ($speedtest_branch !== 'master') { + $speedtest_current = 'vDev'; + $speedtest_commit = $versions['SPEEDTEST_VERSION']; + } else { + $speedtest_current = explode('-', $versions['SPEEDTEST_VERSION'])[0]; + } + // Get Pi-hole FTL (not a git repository) $FTL_branch = $versions['FTL_BRANCH']; if (substr($versions['FTL_VERSION'], 0, 4) === 'vDev') { @@ -68,6 +81,7 @@ function checkUpdate($currentVersion, $latestVersion) // Get data from GitHub $core_latest = $versions['GITHUB_CORE_VERSION']; $web_latest = $versions['GITHUB_WEB_VERSION']; + $speedtest_latest = $versions['GITHUB_SPEEDTEST_VERSION']; $FTL_latest = $versions['GITHUB_FTL_VERSION']; if (isset($versions['GITHUB_DOCKER_VERSION'])) { $docker_latest = $versions['GITHUB_DOCKER_VERSION']; @@ -77,6 +91,7 @@ function checkUpdate($currentVersion, $latestVersion) $core_update = false; $web_update = false; + $speedtest_update = false; $FTL_update = false; // Version comparison @@ -92,6 +107,7 @@ function checkUpdate($currentVersion, $latestVersion) // Components comparison $core_update = checkUpdate($core_current, $core_latest); $web_update = checkUpdate($web_current, $web_latest); + $speedtest_update = checkUpdate($speedtest_current, $speedtest_latest); $FTL_update = checkUpdate($FTL_current, $FTL_latest); // Not a docker container @@ -100,10 +116,11 @@ function checkUpdate($currentVersion, $latestVersion) } // URLs for the links -$coreUrl = 'https://github.com/pi-hole/pi-hole/releases'; +$coreUrl = 'https://github.com/arevindh/pi-hole/releases'; $webUrl = 'https://github.com/arevindh/AdminLTE/releases'; $ftlUrl = 'https://github.com/pi-hole/FTL/releases'; -$dockerUrl = 'https://github.com/arevindh/docker-pi-hole/releases'; +$dockerUrl = 'https://github.com/pi-hole/docker-pi-hole/releases'; +$speedtestUrl = 'https://github.com/arevindh/pihole-speedtest/releases'; // Version strings (encoded to avoid code execution) // If "vDev" show branch/commit, else show link @@ -119,6 +136,12 @@ function checkUpdate($currentVersion, $latestVersion) $webVersionStr = ''.htmlentities($web_current).''; } +if (isset($speedtest_commit)) { + $speedtestVersionStr = htmlentities($speedtest_current.' ('.$speedtest_branch.', '.$speedtest_commit.')'); +} else { + $speedtestVersionStr = ''.htmlentities($speedtest_current).''; +} + if (isset($FTL_commit)) { $ftlVersionStr = htmlentities($FTL_current.' ('.$FTL_branch.', '.$FTL_commit.')'); } else { diff --git a/scripts/pi-hole/speedtest/speedtest.db b/scripts/pi-hole/speedtest/speedtest.db deleted file mode 100644 index b15cec5d6..000000000 Binary files a/scripts/pi-hole/speedtest/speedtest.db and /dev/null differ diff --git a/scripts/pi-hole/speedtest/speedtest.sh b/scripts/pi-hole/speedtest/speedtest.sh deleted file mode 100755 index 65601b7e0..000000000 --- a/scripts/pi-hole/speedtest/speedtest.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash -FILE=/tmp/speedtest.log -readonly setupVars="/etc/pihole/setupVars.conf" -serverid=$(grep 'SPEEDTEST_SERVER' ${setupVars} | cut -d '=' -f2) -start=$(date +"%Y-%m-%d %H:%M:%S") - -speedtest() { - if grep -q official <<< "$(/usr/bin/speedtest --version)"; then - if [[ -z "${serverid}" ]]; then - /usr/bin/speedtest --accept-gdpr --accept-license -f json-pretty - else - /usr/bin/speedtest -s $serverid --accept-gdpr --accept-license -f json-pretty - fi - else - if [[ -z "${serverid}" ]]; then - /usr/bin/speedtest --json --share --secure - else - /usr/bin/speedtest -s $serverid --json --share --secure - fi - fi -} - -internet() { - stop=$(date +"%Y-%m-%d %H:%M:%S") - res="$(<$FILE)" - server_name=$(jq -r '.server.name' <<< "$res") - server_dist=0 - - if grep -q official <<< "$(/usr/bin/speedtest --version)"; then - download=$(jq -r '.download.bandwidth' <<< "$res" | awk '{$1=$1*8/1000/1000; print $1;}' | sed 's/,/./g') - upload=$(jq -r '.upload.bandwidth' <<< "$res" | awk '{$1=$1*8/1000/1000; print $1;}' | sed 's/,/./g') - isp=$(jq -r '.isp' <<< "$res") - server_ip=$(jq -r '.server.ip' <<< "$res") - from_ip=$(jq -r '.interface.externalIp' <<< "$res") - server_ping=$(jq -r '.ping.latency' <<< "$res") - share_url=$(jq -r '.result.url' <<< "$res") - else - download=$(jq -r '.download' <<< "$res" | awk '{$1=$1/1000/1000; print $1;}' | sed 's/,/./g') - upload=$(jq -r '.upload' <<< "$res" | awk '{$1=$1/1000/1000; print $1;}' | sed 's/,/./g') - isp=$(jq -r '.client.isp' <<< "$res") - server_ip=$(jq -r '.server.host' <<< "$res") - from_ip=$(jq -r '.client.ip' <<< "$res") - server_ping=$(jq -r '.ping' <<< "$res") - share_url=$(jq -r '.share' <<< "$res") - fi - - sep="\t" - quote="" - opts= - sep="$quote$sep$quote" - printf "$quote$start$sep$stop$sep$isp$sep$from_ip$sep$server_name$sep$server_dist$sep$server_ping$sep$download$sep$upload$sep$share_url$quote\n" - sqlite3 /etc/pihole/speedtest.db "insert into speedtest values (NULL, '${start}', '${stop}', '${isp}', '${from_ip}', '${server_name}', ${server_dist}, ${server_ping}, ${download}, ${upload}, '${share_url}');" - exit 0 -} - -nointernet(){ - stop=$(date +"%Y-%m-%d %H:%M:%S") - echo "No Internet" - sqlite3 /etc/pihole/speedtest.db "insert into speedtest values (NULL, '${start}', '${stop}', 'No Internet', '-', '-', 0, 0, 0, 0, '#');" - exit 1 -} - -tryagain(){ - start=$(date +"%Y-%m-%d %H:%M:%S") - speedtest > $FILE && internet || nointernet -} - -main() { - if [ $EUID != 0 ]; then - sudo "$0" "$@" - exit $? - fi - echo "Test has been initiated, please wait." - speedtest > "$FILE" && internet || tryagain -} - -main diff --git a/scripts/pi-hole/speedtest/uninstall.sh b/scripts/pi-hole/speedtest/uninstall.sh deleted file mode 100644 index 5e2c62dfd..000000000 --- a/scripts/pi-hole/speedtest/uninstall.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -echo "$(date) - Restoring Pi-hole..." - -cd /opt/ -if [ ! -f /opt/pihole/webpage.sh.org ]; then - rm -rf org_pihole - git clone https://github.com/pi-hole/pi-hole org_pihole - cd org_pihole - git fetch --tags -q - localVer=$(pihole -v | grep "Pi-hole" | cut -d ' ' -f 6) - remoteVer=$(curl -s https://api.github.com/repos/pi-hole/pi-hole/releases/latest | grep "tag_name" | cut -d '"' -f 4) - if [[ "$localVer" < "$remoteVer" && "$localVer" == *.* ]]; then - remoteVer=$localVer - fi - git checkout -q $remoteVer - cp advanced/Scripts/webpage.sh ../pihole/webpage.sh.org - cd - > /dev/null - rm -rf org_pihole -fi - -cd /var/www/html -if [ ! -d /var/www/html/org_admin ]; then - rm -rf org_admin - git clone https://github.com/pi-hole/AdminLTE org_admin - cd org_admin - git fetch --tags -q - localVer=$(pihole -v | grep "AdminLTE" | cut -d ' ' -f 6) - remoteVer=$(curl -s https://api.github.com/repos/pi-hole/AdminLTE/releases/latest | grep "tag_name" | cut -d '"' -f 4) - if [[ "$localVer" < "$remoteVer" && "$localVer" == *.* ]]; then - remoteVer=$localVer - fi - git checkout -q $remoteVer - cd - > /dev/null -fi - -if [ "${1-}" == "db" ] && [ -f /etc/pihole/speedtest.db ]; then - mv /etc/pihole/speedtest.db /etc/pihole/speedtest.db.old - echo "$(date) - Configured Database..." -fi - -echo "$(date) - Uninstalling Current Speedtest Mod..." - -if [ -d /var/www/html/admin ]; then - rm -rf mod_admin - mv admin mod_admin -fi -mv org_admin admin -cd /opt/pihole/ -cp webpage.sh webpage.sh.mod -mv webpage.sh.org webpage.sh -chmod +x webpage.sh - -echo "$(date) - Uninstall Complete" -exit 0 diff --git a/settings.php b/settings.php index 919814034..26698dda1 100644 --- a/settings.php +++ b/settings.php @@ -460,11 +460,11 @@ $DHCPIPv6 = false; $DHCP_rapid_commit = false; } - if (isset($setupVars['PIHOLE_DOMAIN'])) { - $piHoleDomain = $setupVars['PIHOLE_DOMAIN']; - } else { - $piHoleDomain = 'lan'; - } +if (isset($setupVars['PIHOLE_DOMAIN'])) { + $piHoleDomain = $setupVars['PIHOLE_DOMAIN']; +} else { + $piHoleDomain = 'lan'; +} ?>
    @@ -1504,14 +1504,13 @@
    - -
    -
    - - - + + + +
    + +
    - -

    Speedtest settings

    - +

    Basic

    -
    - -
    - - -
    -
    +
    - - + +

    Any real number, down to a minute

    +
    +
    Hours
    + +
    +
    - - + +

    Integers only, max number to show

    +
    +
    Days
    + +
    - -

    You can find the closest servers with speedtest or here. Experts only!

    -
    -
    -
    Speedtest.net Server
    - -
    + Health check +
    + +
    +

    + +

    +
    - + Bar graph
    /> - +
    - +
    +

    + +

    +
    +
    +
    +
    +
    + +
    +
    +
    +

    Advanced

    +
    +
    +
    +
    -
    - +
    + Mod the Mod +

    With the Script

    - +
    - +
    - +
    -

    Attach to a running process with

    -
    sudo tmux attach-session -t pimod
    -

    Or access the latest log whenever with

    -
    cat /var/log/pimod.log
    +

    + +

    +
    + +
    + +

    To be tried first

    +
    +
    id
    + +
    +

    + +

    -
    - +
    +
    -
    +
    + +
    +
    @@ -1639,3 +1678,6 @@ + + + diff --git a/speedtest.php b/speedtest.php index 6901f4ddf..53e4681d5 100644 --- a/speedtest.php +++ b/speedtest.php @@ -8,15 +8,11 @@ require 'scripts/pi-hole/php/header_authenticated.php'; ?> - -
    -

    Recent Speedtests , Export as CSV

    +

    Speedtest Results, Export as CSV

    @@ -25,7 +21,7 @@ ID - Test Time + Start Time End Time Provider Your IP @@ -40,7 +36,7 @@ ID - Test Time + Start Time End Time Provider Your IP