diff --git a/app/Actions/CheckInternetConnection.php b/app/Actions/CheckInternetConnection.php
deleted file mode 100644
index 22e26f78d..000000000
--- a/app/Actions/CheckInternetConnection.php
+++ /dev/null
@@ -1,33 +0,0 @@
-timeout(5)
- ->get(config('speedtest.checkinternet_url'));
-
- if (! $response->ok()) {
- return false;
- }
-
- return Str::trim($response->body());
- } catch (Throwable $e) {
- Log::error('Failed to connect to the internet.', [$e->getMessage()]);
-
- return false;
- }
- }
-}
diff --git a/app/Actions/GetExternalIpAddress.php b/app/Actions/GetExternalIpAddress.php
index 6a4d0b114..d47cc0a6f 100644
--- a/app/Actions/GetExternalIpAddress.php
+++ b/app/Actions/GetExternalIpAddress.php
@@ -12,18 +12,28 @@ class GetExternalIpAddress
{
use AsAction;
- public function handle(): bool|string
+ public function handle(?string $url = null): array
{
+ $url = $url ?? config('speedtest.preflight.external_ip_url');
+
try {
$response = Http::retry(3, 100)
->timeout(5)
- ->get(url: 'https://icanhazip.com/');
+ ->get(url: $url);
} catch (Throwable $e) {
- Log::error('Failed to fetch external IP address.', [$e->getMessage()]);
+ $message = sprintf('Failed to fetch external IP address from "%s". See the logs for more details.', $url);
+
+ Log::error($message, [$e->getMessage()]);
- return false;
+ return [
+ 'ok' => false,
+ 'body' => $message,
+ ];
}
- return Str::trim($response->body());
+ return [
+ 'ok' => $response->ok(),
+ 'body' => Str::of($response->body())->trim()->toString(),
+ ];
}
}
diff --git a/app/Actions/PingHostname.php b/app/Actions/PingHostname.php
new file mode 100644
index 000000000..7707f1abe
--- /dev/null
+++ b/app/Actions/PingHostname.php
@@ -0,0 +1,27 @@
+run();
+
+ return $ping;
+ }
+}
diff --git a/app/Jobs/CheckForInternetConnectionJob.php b/app/Jobs/CheckForInternetConnectionJob.php
index 37c5e6f04..c0f39a61f 100644
--- a/app/Jobs/CheckForInternetConnectionJob.php
+++ b/app/Jobs/CheckForInternetConnectionJob.php
@@ -2,7 +2,7 @@
namespace App\Jobs;
-use App\Actions\CheckInternetConnection;
+use App\Actions\PingHostname;
use App\Enums\ResultStatus;
use App\Events\SpeedtestChecking;
use App\Events\SpeedtestFailed;
@@ -44,14 +44,18 @@ public function handle(): void
SpeedtestChecking::dispatch($this->result);
- if (CheckInternetConnection::run() !== false) {
+ $ping = PingHostname::run();
+
+ if ($ping->isSuccess()) {
return;
}
+ $message = sprintf('Failed to connected to hostname "%s". Error received "%s".', $ping->getHost(), $ping->error()?->value);
+
$this->result->update([
'data->type' => 'log',
'data->level' => 'error',
- 'data->message' => 'Failed to connect to the internet.',
+ 'data->message' => $message,
'status' => ResultStatus::Failed,
]);
diff --git a/app/Jobs/Ookla/SkipSpeedtestJob.php b/app/Jobs/Ookla/SkipSpeedtestJob.php
index 13c444133..948fe792d 100644
--- a/app/Jobs/Ookla/SkipSpeedtestJob.php
+++ b/app/Jobs/Ookla/SkipSpeedtestJob.php
@@ -4,6 +4,7 @@
use App\Actions\GetExternalIpAddress;
use App\Enums\ResultStatus;
+use App\Events\SpeedtestFailed;
use App\Events\SpeedtestSkipped;
use App\Helpers\Network;
use App\Models\Result;
@@ -47,8 +48,23 @@ public function handle(): void
$externalIp = GetExternalIpAddress::run();
+ if ($externalIp['ok'] === false) {
+ $this->result->update([
+ 'data->type' => 'log',
+ 'data->level' => 'error',
+ 'data->message' => $externalIp['body'],
+ 'status' => ResultStatus::Failed,
+ ]);
+
+ SpeedtestFailed::dispatch($this->result);
+
+ $this->batch()->cancel();
+
+ return;
+ }
+
$shouldSkip = $this->shouldSkip(
- externalIp: $externalIp,
+ externalIp: $externalIp['body'],
);
if ($shouldSkip === false) {
@@ -76,11 +92,11 @@ private function shouldSkip(string $externalIp): bool|string
$skipIPs = array_filter(
array_map(
'trim',
- explode(',', config('speedtest.skip_ips')),
+ explode(',', config('speedtest.preflight.skip_ips')),
),
);
- if (count($skipIPs) < 1) {
+ if (empty($skipIPs)) {
return false;
}
diff --git a/composer.json b/composer.json
index 77cf9452a..4fe2349e4 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,6 @@
"dragonmantank/cron-expression": "^3.6.0",
"filament/filament": "4.1.0",
"filament/spatie-laravel-settings-plugin": "^4.1",
- "geerlingguy/ping": "^1.2.1",
"influxdata/influxdb-client-php": "^3.8",
"laravel-notification-channels/telegram": "^6.0",
"laravel/framework": "^12.41.1",
@@ -36,6 +35,7 @@
"spatie/laravel-query-builder": "^6.3.6",
"spatie/laravel-settings": "^3.6.0",
"spatie/laravel-webhook-server": "^3.8.3",
+ "spatie/ping": "^1.1.1",
"zircote/swagger-php": "^5.7.6"
},
"require-dev": {
diff --git a/composer.lock b/composer.lock
index 23ff73cb5..6c492d7d9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "405d221f03e4de1894ce759a9e751448",
+ "content-hash": "374762e19dbfc99374c14f3f12a4ae3e",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@@ -1779,43 +1779,6 @@
],
"time": "2025-12-03T09:33:47+00:00"
},
- {
- "name": "geerlingguy/ping",
- "version": "1.2.1",
- "source": {
- "type": "git",
- "url": "https://github.com/geerlingguy/Ping.git",
- "reference": "e0206326e23c99e3e8820e24705f8ca517adff93"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/geerlingguy/Ping/zipball/e0206326e23c99e3e8820e24705f8ca517adff93",
- "reference": "e0206326e23c99e3e8820e24705f8ca517adff93",
- "shasum": ""
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "JJG/Ping.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jeff Geerling",
- "email": "jeff@jeffgeerling.com"
- }
- ],
- "description": "A PHP class to ping hosts.",
- "support": {
- "issues": "https://github.com/geerlingguy/Ping/issues",
- "source": "https://github.com/geerlingguy/Ping/tree/1.2.1"
- },
- "time": "2019-07-29T21:54:12+00:00"
- },
{
"name": "graham-campbell/result-type",
"version": "v1.1.3",
@@ -6919,6 +6882,65 @@
],
"time": "2025-02-14T12:55:41+00:00"
},
+ {
+ "name": "spatie/ping",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/ping.git",
+ "reference": "6123a6209148e8919f58121d256f43c75856ab35"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/ping/zipball/6123a6209148e8919f58121d256f43c75856ab35",
+ "reference": "6123a6209148e8919f58121d256f43c75856ab35",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.4",
+ "symfony/process": "^7.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.0",
+ "pestphp/pest": "^3.0",
+ "spatie/pest-expectations": "^1.13",
+ "spatie/ray": "^1.28"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Ping\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Run an ICMP ping and get structured results",
+ "homepage": "https://github.com/spatie/ping",
+ "keywords": [
+ "ping",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/ping/issues",
+ "source": "https://github.com/spatie/ping/tree/1.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-12T20:58:12+00:00"
+ },
{
"name": "spatie/shiki-php",
"version": "2.3.2",
diff --git a/config/speedtest.php b/config/speedtest.php
index ec4e255a3..b68a6a040 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-15'),
+ 'build_date' => Carbon::parse('2025-12-16'),
- 'build_version' => 'v1.12.4',
+ 'build_version' => 'v1.13.0',
'content_width' => env('CONTENT_WIDTH', '7xl'),
@@ -29,15 +29,17 @@
'interface' => env('SPEEDTEST_INTERFACE'),
- 'checkinternet_url' => env('SPEEDTEST_CHECKINTERNET_URL', 'https://icanhazip.com'),
+ 'preflight' => [
+ 'external_ip_url' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_EXTERNAL_IP_URL', 'https://icanhazip.com'),
+ 'internet_check_hostname' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_INTERNET_CHECK_HOSTNAME', 'icanhazip.com'),
+ 'skip_ips' => env('SPEEDTEST_SKIP_IPS'),
+ ],
/**
* IP filtering settings.
*/
'allowed_ips' => env('ALLOWED_IPS'),
- 'skip_ips' => env('SPEEDTEST_SKIP_IPS', ''),
-
/**
* Threshold settings.
*/
diff --git a/resources/views/filament/pages/dashboard.blade.php b/resources/views/filament/pages/dashboard.blade.php
index 42a849976..b92cde2a9 100644
--- a/resources/views/filament/pages/dashboard.blade.php
+++ b/resources/views/filament/pages/dashboard.blade.php
@@ -11,6 +11,7 @@
class="col-span-1"
icon="tabler-book"
icon-size="md"
+ :compact="true"
>
- Next scheduled test at {{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format('F jS, Y, g:i a') }}. + Next scheduled test at {{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format(config('app.datetime_format')) }}.
{{ $this->platformStats['total'] }}
{{ $this->platformStats['completed'] }}