From 73f44abb3cf6b3a2918144e7e201b1f34cbe5145 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Thu, 11 Dec 2025 17:26:31 +0100
Subject: [PATCH 01/34] feat: add speedtest button to metric dashboard (#2521)
---
app/Livewire/Topbar/Actions.php | 2 ++
resources/views/layouts/partials/header.blade.php | 2 ++
resources/views/livewire/topbar/actions.blade.php | 5 +++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/app/Livewire/Topbar/Actions.php b/app/Livewire/Topbar/Actions.php
index 95a8abcce..5f687ae86 100644
--- a/app/Livewire/Topbar/Actions.php
+++ b/app/Livewire/Topbar/Actions.php
@@ -21,6 +21,8 @@ class Actions extends Component implements HasActions, HasForms
{
use InteractsWithActions, InteractsWithForms;
+ public bool $showDashboard = true;
+
public function dashboardAction(): Action
{
return Action::make('metrics')
diff --git a/resources/views/layouts/partials/header.blade.php b/resources/views/layouts/partials/header.blade.php
index b8eed7ffc..d26419f78 100644
--- a/resources/views/layouts/partials/header.blade.php
+++ b/resources/views/layouts/partials/header.blade.php
@@ -55,6 +55,8 @@ class="p-2 rounded-md transition-all"
@auth
+
+
{{ $this->speedtestAction }}
- {{ $this->dashboardAction }}
-
+ @if ($showDashboard)
+ {{ $this->dashboardAction }}
+ @endif
From b7e573f56474babfcf3bf568b2e28c0902f3ceef Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Mon, 15 Dec 2025 08:54:42 -0500
Subject: [PATCH 02/34] Release v1.12.4 (#2548)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
config/speedtest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index dfaf9ac76..ec4e255a3 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-08'),
+ 'build_date' => Carbon::parse('2025-12-15'),
- 'build_version' => 'v1.12.3',
+ 'build_version' => 'v1.12.4',
'content_width' => env('CONTENT_WIDTH', '7xl'),
From c4e2e7f627067193daf5590b0f5bac03de1f2d71 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Mon, 15 Dec 2025 09:57:54 -0500
Subject: [PATCH 03/34] fix: adjust speedtest button size based on request
context (#2550)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Livewire/Topbar/Actions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Livewire/Topbar/Actions.php b/app/Livewire/Topbar/Actions.php
index 5f687ae86..9077724c8 100644
--- a/app/Livewire/Topbar/Actions.php
+++ b/app/Livewire/Topbar/Actions.php
@@ -67,7 +67,7 @@ public function speedtestAction(): Action
->modalWidth('lg')
->modalSubmitActionLabel(__('results.start'))
->button()
- ->size(Size::Medium)
+ ->size(request()->is('filament*') ? Size::Medium : Size::Large)
->color('primary')
->label(__('results.speedtest'))
->icon('tabler-rocket')
From 74062c902d9985f95cf25c53ee0069a23cddee51 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Mon, 15 Dec 2025 19:55:37 -0500
Subject: [PATCH 04/34] Update form method to use Schema instead of Form in
ListOoklaServers (#2551)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Filament/Pages/Tools/ListOoklaServers.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/Filament/Pages/Tools/ListOoklaServers.php b/app/Filament/Pages/Tools/ListOoklaServers.php
index d2d5ea3eb..c82ad82b3 100644
--- a/app/Filament/Pages/Tools/ListOoklaServers.php
+++ b/app/Filament/Pages/Tools/ListOoklaServers.php
@@ -7,9 +7,9 @@
use Filament\Forms\Components\Textarea;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
-use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
+use Filament\Schemas\Schema;
class ListOoklaServers extends Page implements HasForms
{
@@ -69,10 +69,10 @@ public function fetchServers(): void
}
}
- public function form(Form $form): Form
+ public function form(Schema $schema): Schema
{
- return $form
- ->schema([
+ return $schema
+ ->components([
Textarea::make('servers')
->label(false)
->rows(20)
From 3381265bdf5082bc66fc37466d3657f8fbabc711 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Tue, 16 Dec 2025 06:48:11 -0500
Subject: [PATCH 05/34] fix: datetime format for next speedtest banner (#2554)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
resources/views/livewire/next-speedtest-banner.blade.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/views/livewire/next-speedtest-banner.blade.php b/resources/views/livewire/next-speedtest-banner.blade.php
index ce65f81d7..e66d3c1ee 100644
--- a/resources/views/livewire/next-speedtest-banner.blade.php
+++ b/resources/views/livewire/next-speedtest-banner.blade.php
@@ -8,7 +8,7 @@
- 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')) }}.
From 963fc051ad6a8645c0ab2ee585854c9b5cd79c65 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Tue, 16 Dec 2025 07:55:52 -0500
Subject: [PATCH 06/34] update external IP fetching logic to handle failures
(#2555)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Actions/GetExternalIpAddress.php | 20 +++++++++++++++-----
app/Jobs/Ookla/SkipSpeedtestJob.php | 18 +++++++++++++++++-
config/speedtest.php | 6 +++++-
3 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/app/Actions/GetExternalIpAddress.php b/app/Actions/GetExternalIpAddress.php
index 6a4d0b114..48417c1f1 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.get_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/Jobs/Ookla/SkipSpeedtestJob.php b/app/Jobs/Ookla/SkipSpeedtestJob.php
index 13c444133..12c276fc1 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) {
diff --git a/config/speedtest.php b/config/speedtest.php
index ec4e255a3..937125766 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -29,7 +29,11 @@
'interface' => env('SPEEDTEST_INTERFACE'),
- 'checkinternet_url' => env('SPEEDTEST_CHECKINTERNET_URL', 'https://icanhazip.com'),
+ 'preflight' => [
+ 'get_external_ip_url' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_GET_EXTERNAL_IP_URL', 'https://icanhazip.com'),
+ ],
+
+ 'checkinternet_url' => env('SPEEDTEST_CHECKINTERNET_URL'), // ! DEPRECATED, use preflight.get_external_ip_url instead
/**
* IP filtering settings.
From 03e02b6eb900b478b297868b48c8cb52c92f8613 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Tue, 16 Dec 2025 09:24:42 -0500
Subject: [PATCH 07/34] check for internet using ping (#2556)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Actions/CheckInternetConnection.php | 33 --------
app/Actions/GetExternalIpAddress.php | 2 +-
app/Actions/PingHostname.php | 27 ++++++
app/Jobs/CheckForInternetConnectionJob.php | 10 ++-
composer.json | 2 +-
composer.lock | 98 +++++++++++++---------
config/speedtest.php | 5 +-
7 files changed, 98 insertions(+), 79 deletions(-)
delete mode 100644 app/Actions/CheckInternetConnection.php
create mode 100644 app/Actions/PingHostname.php
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 48417c1f1..d47cc0a6f 100644
--- a/app/Actions/GetExternalIpAddress.php
+++ b/app/Actions/GetExternalIpAddress.php
@@ -14,7 +14,7 @@ class GetExternalIpAddress
public function handle(?string $url = null): array
{
- $url = $url ?? config('speedtest.preflight.get_external_ip_url');
+ $url = $url ?? config('speedtest.preflight.external_ip_url');
try {
$response = Http::retry(3, 100)
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/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 937125766..204bf21ec 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -30,11 +30,10 @@
'interface' => env('SPEEDTEST_INTERFACE'),
'preflight' => [
- 'get_external_ip_url' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_GET_EXTERNAL_IP_URL', 'https://icanhazip.com'),
+ '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'),
],
- 'checkinternet_url' => env('SPEEDTEST_CHECKINTERNET_URL'), // ! DEPRECATED, use preflight.get_external_ip_url instead
-
/**
* IP filtering settings.
*/
From c7c467ff8a08f6ea2aeb0225186b2a92eb199b2c Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Tue, 16 Dec 2025 09:32:52 -0500
Subject: [PATCH 08/34] move skip IPs configuration to preflight settings
(#2557)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Jobs/Ookla/SkipSpeedtestJob.php | 4 ++--
config/speedtest.php | 3 +--
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/app/Jobs/Ookla/SkipSpeedtestJob.php b/app/Jobs/Ookla/SkipSpeedtestJob.php
index 12c276fc1..948fe792d 100644
--- a/app/Jobs/Ookla/SkipSpeedtestJob.php
+++ b/app/Jobs/Ookla/SkipSpeedtestJob.php
@@ -92,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/config/speedtest.php b/config/speedtest.php
index 204bf21ec..48dc7d083 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -32,6 +32,7 @@
'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'),
],
/**
@@ -39,8 +40,6 @@
*/
'allowed_ips' => env('ALLOWED_IPS'),
- 'skip_ips' => env('SPEEDTEST_SKIP_IPS', ''),
-
/**
* Threshold settings.
*/
From cd0915f601371d822c89f52972dc9887e99085cf Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Tue, 16 Dec 2025 09:47:48 -0500
Subject: [PATCH 09/34] Release v1.13.0 (#2558)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
config/speedtest.php | 4 ++--
resources/views/filament/pages/dashboard.blade.php | 3 +++
resources/views/livewire/latest-result-stats.blade.php | 8 ++++----
resources/views/livewire/platform-stats.blade.php | 6 +++---
4 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index 48dc7d083..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'),
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"
>
{{ __('general.documentation') }}
@@ -35,6 +36,7 @@ class="col-span-1"
class="col-span-1"
icon="tabler-cash-banknote-heart"
icon-size="md"
+ :compact="true"
>
{{ __('general.donations') }}
@@ -59,6 +61,7 @@ class="col-span-1"
class="col-span-1"
icon="tabler-brand-github"
icon-size="md"
+ :compact="true"
>
{{ __('general.speedtest_tracker') }}
diff --git a/resources/views/livewire/latest-result-stats.blade.php b/resources/views/livewire/latest-result-stats.blade.php
index a3cc2b708..c1c69eeec 100644
--- a/resources/views/livewire/latest-result-stats.blade.php
+++ b/resources/views/livewire/latest-result-stats.blade.php
@@ -24,7 +24,7 @@
-
+
{{ __('general.download') }}
@@ -63,7 +63,7 @@
-
+
{{ __('general.upload') }}
@@ -102,7 +102,7 @@
-
+
{{ __('general.ping') }}
@@ -135,7 +135,7 @@
-
+
{{ __('results.packet_loss') }}
diff --git a/resources/views/livewire/platform-stats.blade.php b/resources/views/livewire/platform-stats.blade.php
index 567c1f31a..f8f149e6e 100644
--- a/resources/views/livewire/platform-stats.blade.php
+++ b/resources/views/livewire/platform-stats.blade.php
@@ -23,7 +23,7 @@
--}}
-
+
Total tests
@@ -31,7 +31,7 @@
{{ $this->platformStats['total'] }}
-
+
Total completed tests
@@ -39,7 +39,7 @@
{{ $this->platformStats['completed'] }}
-
+
Total failed tests
From b21f0f18d760b73ed90ff00e6fa3efca8b9a3174 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Wed, 17 Dec 2025 09:30:30 -0500
Subject: [PATCH 10/34] Hotfix v1.13.1 (fix for ping failing) (#2561)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Actions/PingHostname.php | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app/Actions/PingHostname.php b/app/Actions/PingHostname.php
index 7707f1abe..5bbf15ddf 100644
--- a/app/Actions/PingHostname.php
+++ b/app/Actions/PingHostname.php
@@ -2,6 +2,7 @@
namespace App\Actions;
+use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
use Spatie\Ping\Ping;
use Spatie\Ping\PingResult;
@@ -22,6 +23,11 @@ public function handle(?string $hostname = null, int $count = 1): PingResult
count: $count,
))->run();
+ Log::info('Pinged hostname', [
+ 'host' => $hostname,
+ 'data' => $ping->toArray(),
+ ]);
+
return $ping;
}
}
From 43a88f551f37bd6b6997ef23d942a712ef1af32b Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Thu, 18 Dec 2025 09:00:57 -0500
Subject: [PATCH 11/34] add selection details option to speedtest command
(#2572)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
app/Jobs/Ookla/RunSpeedtestJob.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/Jobs/Ookla/RunSpeedtestJob.php b/app/Jobs/Ookla/RunSpeedtestJob.php
index 486faab6f..61b371939 100644
--- a/app/Jobs/Ookla/RunSpeedtestJob.php
+++ b/app/Jobs/Ookla/RunSpeedtestJob.php
@@ -60,6 +60,7 @@ public function handle(): void
'speedtest',
'--accept-license',
'--accept-gdpr',
+ '--selection-details',
'--format=json',
$this->result->server_id ? '--server-id='.$this->result->server_id : null,
config('speedtest.interface') ? '--interface='.config('speedtest.interface') : null,
From 376b2065f63357b0af2e89aa215c15d6331d37ef Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Thu, 18 Dec 2025 09:19:05 -0500
Subject: [PATCH 12/34] remove platform stats from metrics dashboard (#2574)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
resources/views/dashboard.blade.php | 4 ----
1 file changed, 4 deletions(-)
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index e1b26abed..8b54445cb 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -2,10 +2,6 @@
- @auth
-
- @endauth
-
From dc4dd2ff660246da1b450bea620760e0535a8169 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Thu, 18 Dec 2025 09:24:20 -0500
Subject: [PATCH 13/34] Release v1.13.2 (#2575)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
config/speedtest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index b68a6a040..946b93f75 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-16'),
+ 'build_date' => Carbon::parse('2025-12-18'),
- 'build_version' => 'v1.13.0',
+ 'build_version' => 'v1.13.2',
'content_width' => env('CONTENT_WIDTH', '7xl'),
From eb9a3e676ecb242b8854b7bfb9e074b03570b9aa Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Fri, 19 Dec 2025 17:26:43 +0100
Subject: [PATCH 14/34] Chore: Improve Apprise Logic & UI Text (#2579)
---
.../SendAppriseTestNotification.php | 77 ++++++++++++++++---
app/Filament/Pages/Settings/Notification.php | 5 +-
app/Notifications/AppriseChannel.php | 33 ++++----
app/Rules/ContainsString.php | 24 ++++++
lang/en/settings/notifications.php | 13 ++--
5 files changed, 118 insertions(+), 34 deletions(-)
create mode 100644 app/Rules/ContainsString.php
diff --git a/app/Actions/Notifications/SendAppriseTestNotification.php b/app/Actions/Notifications/SendAppriseTestNotification.php
index 062202b06..64bba779b 100644
--- a/app/Actions/Notifications/SendAppriseTestNotification.php
+++ b/app/Actions/Notifications/SendAppriseTestNotification.php
@@ -3,15 +3,17 @@
namespace App\Actions\Notifications;
use App\Notifications\Apprise\TestNotification;
+use App\Settings\NotificationSettings;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Notification as FacadesNotification;
use Lorisleiva\Actions\Concerns\AsAction;
+use Throwable;
class SendAppriseTestNotification
{
use AsAction;
- public function handle(array $channel_urls)
+ public function handle(array $channel_urls): void
{
if (! count($channel_urls)) {
Notification::make()
@@ -22,19 +24,41 @@ public function handle(array $channel_urls)
return;
}
- foreach ($channel_urls as $row) {
- $channelUrl = $row['channel_url'] ?? null;
- if (! $channelUrl) {
- Notification::make()
- ->title('Skipping missing channel URL!')
- ->warning()
- ->send();
+ $settings = app(NotificationSettings::class);
+ $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/');
- continue;
+ if (empty($appriseUrl)) {
+ Notification::make()
+ ->title('Apprise Server URL is not configured')
+ ->body('Please configure the Apprise Server URL in the settings above.')
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ try {
+ foreach ($channel_urls as $row) {
+ $channelUrl = $row['channel_url'] ?? null;
+ if (! $channelUrl) {
+ continue;
+ }
+
+ // Use notifyNow() to send synchronously even though notification implements ShouldQueue
+ // This allows us to catch exceptions and show them in the UI immediately
+ FacadesNotification::route('apprise_urls', $channelUrl)
+ ->notifyNow(new TestNotification);
}
+ } catch (Throwable $e) {
+ $errorMessage = $this->cleanErrorMessage($e);
+
+ Notification::make()
+ ->title('Failed to send Apprise test notification')
+ ->body($errorMessage)
+ ->danger()
+ ->send();
- FacadesNotification::route('apprise_urls', $channelUrl)
- ->notify(new TestNotification);
+ return;
}
Notification::make()
@@ -42,4 +66,35 @@ public function handle(array $channel_urls)
->success()
->send();
}
+
+ /**
+ * Clean up error message for display in UI.
+ */
+ protected function cleanErrorMessage(Throwable $e): string
+ {
+ $message = $e->getMessage();
+
+ // Get the full Apprise server URL for error messages
+ $settings = app(NotificationSettings::class);
+ $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/');
+
+ // Handle connection errors - extract just the important part
+ if (str_contains($message, 'cURL error')) {
+ if (str_contains($message, 'Could not resolve host')) {
+ return "Could not connect to Apprise server at {$appriseUrl}";
+ }
+
+ if (str_contains($message, 'Connection refused')) {
+ return "Connection refused by Apprise server at {$appriseUrl}";
+ }
+
+ if (str_contains($message, 'Operation timed out')) {
+ return "Connection to Apprise server at {$appriseUrl} timed out";
+ }
+
+ return "Failed to connect to Apprise server at {$appriseUrl}";
+ }
+
+ return $message;
+ }
}
diff --git a/app/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php
index 9a5decb00..7e1c1a58a 100755
--- a/app/Filament/Pages/Settings/Notification.php
+++ b/app/Filament/Pages/Settings/Notification.php
@@ -14,6 +14,7 @@
use App\Actions\Notifications\SendTelegramTestNotification;
use App\Actions\Notifications\SendWebhookTestNotification;
use App\Rules\AppriseScheme;
+use App\Rules\ContainsString;
use App\Settings\NotificationSettings;
use CodeWithDennis\SimpleAlert\Components\SimpleAlert;
use Filament\Actions\Action;
@@ -233,10 +234,12 @@ public function form(Schema $schema): Schema
->schema([
TextInput::make('apprise_server_url')
->label(__('settings/notifications.apprise_server_url'))
- ->placeholder('http://localhost:8000')
+ ->placeholder('http://localhost:8000/notify')
+ ->helperText(__('settings/notifications.apprise_server_url_helper'))
->maxLength(2000)
->required()
->url()
+ ->rule(new ContainsString('/notify'))
->columnSpanFull(),
Checkbox::make('apprise_verify_ssl')
->label(__('settings/notifications.apprise_verify_ssl'))
diff --git a/app/Notifications/AppriseChannel.php b/app/Notifications/AppriseChannel.php
index 3cd2592a1..0e9ed1087 100644
--- a/app/Notifications/AppriseChannel.php
+++ b/app/Notifications/AppriseChannel.php
@@ -3,9 +3,11 @@
namespace App\Notifications;
use App\Settings\NotificationSettings;
+use Exception;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
+use Throwable;
class AppriseChannel
{
@@ -22,7 +24,7 @@ public function send(object $notifiable, Notification $notification): void
}
$settings = app(NotificationSettings::class);
- $appriseUrl = rtrim($settings->apprise_server_url ?? '', '/');
+ $appriseUrl = $settings->apprise_server_url ?? '';
if (empty($appriseUrl)) {
Log::warning('Apprise notification skipped: No Server URL configured');
@@ -41,7 +43,7 @@ public function send(object $notifiable, Notification $notification): void
$request = $request->withoutVerifying();
}
- $response = $request->post("{$appriseUrl}/notify", [
+ $response = $request->post($appriseUrl, [
'urls' => $message->urls,
'title' => $message->title,
'body' => $message->body,
@@ -50,26 +52,25 @@ public function send(object $notifiable, Notification $notification): void
'tag' => $message->tag ?? null,
]);
- if ($response->failed()) {
- Log::error('Apprise notification failed', [
- 'channel' => $message->urls,
- 'instance' => $appriseUrl,
- 'status' => $response->status(),
- 'body' => $response->body(),
- ]);
- } else {
- Log::info('Apprise notification sent', [
- 'channel' => $message->urls,
- 'instance' => $appriseUrl,
- ]);
+ // Only accept 200 OK responses as successful
+ if ($response->status() !== 200) {
+ throw new Exception('Apprise returned an error, please check Apprise logs for details');
}
- } catch (\Throwable $e) {
- Log::error('Apprise notification exception', [
+
+ Log::info('Apprise notification sent', [
+ 'channel' => $message->urls,
+ 'instance' => $appriseUrl,
+ ]);
+ } catch (Throwable $e) {
+ Log::error('Apprise notification failed', [
'channel' => $message->urls ?? 'unknown',
'instance' => $appriseUrl,
'message' => $e->getMessage(),
'exception' => get_class($e),
]);
+
+ // Re-throw the exception so it can be handled by the queue
+ throw $e;
}
}
}
diff --git a/app/Rules/ContainsString.php b/app/Rules/ContainsString.php
new file mode 100644
index 000000000..370a499c6
--- /dev/null
+++ b/app/Rules/ContainsString.php
@@ -0,0 +1,24 @@
+caseSensitive ? $value : strtolower($value);
+ $needle = $this->caseSensitive ? $this->needle : strtolower($this->needle);
+
+ if (! str_contains($haystack, $needle)) {
+ $fail("The :attribute must contain '{$this->needle}'.");
+ }
+ }
+}
diff --git a/lang/en/settings/notifications.php b/lang/en/settings/notifications.php
index 203590b23..788f31d91 100644
--- a/lang/en/settings/notifications.php
+++ b/lang/en/settings/notifications.php
@@ -19,19 +19,20 @@
'enable_apprise_notifications' => 'Enable Apprise notifications',
'apprise_server' => 'Apprise Server',
'apprise_server_url' => 'Apprise Server URL',
+ 'apprise_server_url_helper' => 'The URL of your Apprise Server. The URL must end on /notify',
'apprise_verify_ssl' => 'Verify SSL',
- 'apprise_channels' => 'Apprise Channels',
- 'apprise_channel_url' => 'Channel URL',
- 'apprise_hint_description' => 'For more information on setting up Apprise, view the documentation.',
- 'apprise_channel_url_helper' => 'Provide the service endpoint URL for notifications.',
+ 'apprise_channels' => 'Notification Channels',
+ 'apprise_channel_url' => 'Service URL',
+ 'apprise_hint_description' => 'Apprise allows you to send notifications to 90+ services. You need to run an Apprise server and configure service URLs below.',
+ 'apprise_channel_url_helper' => 'Use Apprise URL format. Examples: discord://WebhookID/Token, slack://TokenA/TokenB/TokenC',
'test_apprise_channel' => 'Test Apprise',
- 'apprise_channel_url_validation_error' => 'The Apprise channel URL must not start with "http" or "https". Please provide a valid Apprise URL scheme.',
+ 'apprise_channel_url_validation_error' => 'Invalid Apprise URL. Must use Apprise format (e.g., discord://, slack://), not http:// or https://. See the Apprise documentation for more information',
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
'test_webhook_channel' => 'Test webhook channel',
- 'webhook_hint_description' => 'These are generic webhooks. For payload examples and implementation details, view the documentation.',
+ 'webhook_hint_description' => 'These are generic webhooks. For payload examples and implementation details, view the documentation. For services like Discord, Ntfy etc please use Apprise.',
// Common notification messages
'notify_on_every_speedtest_run' => 'Notify on every scheduled speedtest run',
From 045a5191d20cc8fa1f220e90c6da9db5884b353f Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Fri, 19 Dec 2025 17:38:22 +0100
Subject: [PATCH 15/34] chore: deprecate notification channel alarm on
dashboard and notifications (#2577)
---
.../DeprecatedNotificationChannelsBanner.php | 66 +++++++++++++++++++
resources/views/dashboard.blade.php | 2 +
.../discord/speedtest-completed.blade.php | 2 +
.../discord/speedtest-threshold.blade.php | 2 +
.../views/filament/pages/dashboard.blade.php | 2 +
.../gotify/speedtest-completed.blade.php | 2 +
.../gotify/speedtest-threshold.blade.php | 2 +
...ted-notification-channels-banner.blade.php | 25 +++++++
.../views/ntfy/speedtest-completed.blade.php | 2 +
.../views/ntfy/speedtest-threshold.blade.php | 2 +
.../pushover/speedtest-completed.blade.php | 2 +
.../pushover/speedtest-threshold.blade.php | 2 +
.../views/slack/speedtest-completed.blade.php | 2 +
.../views/slack/speedtest-threshold.blade.php | 2 +
.../telegram/speedtest-completed.blade.php | 2 +
.../telegram/speedtest-threshold.blade.php | 2 +
16 files changed, 119 insertions(+)
create mode 100644 app/Livewire/DeprecatedNotificationChannelsBanner.php
create mode 100644 resources/views/livewire/deprecated-notification-channels-banner.blade.php
diff --git a/app/Livewire/DeprecatedNotificationChannelsBanner.php b/app/Livewire/DeprecatedNotificationChannelsBanner.php
new file mode 100644
index 000000000..84533de19
--- /dev/null
+++ b/app/Livewire/DeprecatedNotificationChannelsBanner.php
@@ -0,0 +1,66 @@
+discord_enabled
+ || $settings->gotify_enabled
+ || $settings->healthcheck_enabled
+ || $settings->ntfy_enabled
+ || $settings->pushover_enabled
+ || $settings->slack_enabled
+ || $settings->telegram_enabled;
+ }
+
+ #[Computed]
+ public function deprecatedChannelsList(): array
+ {
+ $settings = app(NotificationSettings::class);
+ $channels = [];
+
+ if ($settings->discord_enabled) {
+ $channels[] = 'Discord';
+ }
+
+ if ($settings->gotify_enabled) {
+ $channels[] = 'Gotify';
+ }
+
+ if ($settings->healthcheck_enabled) {
+ $channels[] = 'Healthchecks';
+ }
+
+ if ($settings->ntfy_enabled) {
+ $channels[] = 'Ntfy';
+ }
+
+ if ($settings->pushover_enabled) {
+ $channels[] = 'Pushover';
+ }
+
+ if ($settings->slack_enabled) {
+ $channels[] = 'Slack';
+ }
+
+ if ($settings->telegram_enabled) {
+ $channels[] = 'Telegram';
+ }
+
+ return $channels;
+ }
+
+ public function render()
+ {
+ return view('livewire.deprecated-notification-channels-banner');
+ }
+}
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index 8b54445cb..967996d47 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -1,5 +1,7 @@
+
+
diff --git a/resources/views/discord/speedtest-completed.blade.php b/resources/views/discord/speedtest-completed.blade.php
index 6a8cdf9a5..851a8200a 100644
--- a/resources/views/discord/speedtest-completed.blade.php
+++ b/resources/views/discord/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Discord notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Discord and many other services.**
+
**Speedtest Completed - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}**.
diff --git a/resources/views/discord/speedtest-threshold.blade.php b/resources/views/discord/speedtest-threshold.blade.php
index 95dc4bf01..b0716b50b 100644
--- a/resources/views/discord/speedtest-threshold.blade.php
+++ b/resources/views/discord/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Discord notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Discord and many other services.**
+
**Speedtest Threshold Breached - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
diff --git a/resources/views/filament/pages/dashboard.blade.php b/resources/views/filament/pages/dashboard.blade.php
index b92cde2a9..2d9d68211 100644
--- a/resources/views/filament/pages/dashboard.blade.php
+++ b/resources/views/filament/pages/dashboard.blade.php
@@ -1,5 +1,7 @@
+
+
diff --git a/resources/views/gotify/speedtest-completed.blade.php b/resources/views/gotify/speedtest-completed.blade.php
index 910b9347c..c17d46daf 100644
--- a/resources/views/gotify/speedtest-completed.blade.php
+++ b/resources/views/gotify/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Gotify notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Gotify and many other services.**
+
**Speedtest Completed - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}**.
diff --git a/resources/views/gotify/speedtest-threshold.blade.php b/resources/views/gotify/speedtest-threshold.blade.php
index dd86bcf8a..e731a0104 100644
--- a/resources/views/gotify/speedtest-threshold.blade.php
+++ b/resources/views/gotify/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Gotify notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Gotify and many other services.**
+
**Speedtest Threshold Breached - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
diff --git a/resources/views/livewire/deprecated-notification-channels-banner.blade.php b/resources/views/livewire/deprecated-notification-channels-banner.blade.php
new file mode 100644
index 000000000..a96ed0441
--- /dev/null
+++ b/resources/views/livewire/deprecated-notification-channels-banner.blade.php
@@ -0,0 +1,25 @@
+
+ @if ($this->hasDeprecatedChannels)
+
+
+
+
+
+
+
+
+ Deprecated Notification Channels
+
+
+
+ You are currently using the following deprecated notification channels: {{ implode(', ', $this->deprecatedChannelsList) }}.
+
+
+ These channels will be removed at the end of January 2026. As of that moment you will no longer receive notifications. Please migrate to Apprise which supports all these services and more.
+
+
+
+
+
+ @endif
+
diff --git a/resources/views/ntfy/speedtest-completed.blade.php b/resources/views/ntfy/speedtest-completed.blade.php
index 24c67a908..64501e794 100644
--- a/resources/views/ntfy/speedtest-completed.blade.php
+++ b/resources/views/ntfy/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+Deprecation Notice: The Ntfy notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Ntfy and many other services.
+
Speedtest Completed - #{{ $id }}
A new speedtest on {{ config('app.name') }} was completed using {{ $service }}.
diff --git a/resources/views/ntfy/speedtest-threshold.blade.php b/resources/views/ntfy/speedtest-threshold.blade.php
index 8ad956c03..7e9ef3c93 100644
--- a/resources/views/ntfy/speedtest-threshold.blade.php
+++ b/resources/views/ntfy/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+Deprecation Notice: The Ntfy notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Ntfy and many other services.
+
Speedtest Threshold Breached - #{{ $id }}
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
diff --git a/resources/views/pushover/speedtest-completed.blade.php b/resources/views/pushover/speedtest-completed.blade.php
index 8760eb5de..dfae02299 100644
--- a/resources/views/pushover/speedtest-completed.blade.php
+++ b/resources/views/pushover/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+Deprecation Notice: The Pushover notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Pushover and many other services.
+
Speedtest Completed - #{{ $id }}
A new speedtest on {{ config('app.name') }} was completed using {{ $service }}.
diff --git a/resources/views/pushover/speedtest-threshold.blade.php b/resources/views/pushover/speedtest-threshold.blade.php
index 8ad956c03..6f222fa19 100644
--- a/resources/views/pushover/speedtest-threshold.blade.php
+++ b/resources/views/pushover/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+Deprecation Notice: The Pushover notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Pushover and many other services.
+
Speedtest Threshold Breached - #{{ $id }}
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
diff --git a/resources/views/slack/speedtest-completed.blade.php b/resources/views/slack/speedtest-completed.blade.php
index f93d8daf4..c1abcefc5 100644
--- a/resources/views/slack/speedtest-completed.blade.php
+++ b/resources/views/slack/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+*Deprecation Notice: The Slack notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Slack and many other services.*
+
*Speedtest Completed - #{{ $id }}*
A new speedtest on *{{ config('app.name') }}* was completed using *{{ $service }}*.
diff --git a/resources/views/slack/speedtest-threshold.blade.php b/resources/views/slack/speedtest-threshold.blade.php
index 612475a28..2d4a31fc5 100644
--- a/resources/views/slack/speedtest-threshold.blade.php
+++ b/resources/views/slack/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+*Deprecation Notice: The Slack notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Slack and many other services.*
+
**Speedtest Threshold Breached - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
diff --git a/resources/views/telegram/speedtest-completed.blade.php b/resources/views/telegram/speedtest-completed.blade.php
index 497c3bf12..ba168cb10 100644
--- a/resources/views/telegram/speedtest-completed.blade.php
+++ b/resources/views/telegram/speedtest-completed.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Telegram notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Telegram and many other services.**
+
*Speedtest Completed - #{{ $id }}*
A new speedtest on *{{ config('app.name') }}* was completed using *{{ $service }}*.
diff --git a/resources/views/telegram/speedtest-threshold.blade.php b/resources/views/telegram/speedtest-threshold.blade.php
index 95dc4bf01..81ef8bb74 100644
--- a/resources/views/telegram/speedtest-threshold.blade.php
+++ b/resources/views/telegram/speedtest-threshold.blade.php
@@ -1,3 +1,5 @@
+**Deprecation Notice: The Telegram notification channel will stop working at the end of January 2026. Please migrate to Apprise which supports Telegram and many other services.**
+
**Speedtest Threshold Breached - #{{ $id }}**
A new speedtest on **{{ config('app.name') }}** was completed using **{{ $service }}** on **{{ $isp }}** but a threshold was breached.
From 15258b7ceb7783ac07b603b4cee6f281a33d14a5 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Fri, 19 Dec 2025 11:42:50 -0500
Subject: [PATCH 16/34] Release v1.13.3 (#2580)
---
config/speedtest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index 946b93f75..94ebcaa84 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-18'),
+ 'build_date' => Carbon::parse('2025-12-19'),
- 'build_version' => 'v1.13.2',
+ 'build_version' => 'v1.13.3',
'content_width' => env('CONTENT_WIDTH', '7xl'),
From b068b3a45b09617d53952fd33b1ba9855278f69c Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Sun, 21 Dec 2025 13:28:46 -0500
Subject: [PATCH 17/34] New Crowdin updates (#2496)
---
lang/de_DE/auth.php | 1 +
lang/de_DE/general.php | 13 +++++++++++++
lang/de_DE/results.php | 4 ----
lang/de_DE/settings/data_integration.php | 2 +-
lang/de_DE/settings/notifications.php | 13 +++++++++++++
lang/es_ES/auth.php | 1 +
lang/es_ES/general.php | 15 +++++++++++++++
lang/es_ES/results.php | 7 +++----
lang/es_ES/settings/data_integration.php | 9 ++++++++-
lang/es_ES/settings/notifications.php | 24 ++++++++++++++++--------
lang/fr_FR/auth.php | 1 +
lang/fr_FR/general.php | 13 +++++++++++++
lang/fr_FR/results.php | 4 ----
lang/fr_FR/settings/data_integration.php | 2 +-
lang/fr_FR/settings/notifications.php | 13 +++++++++++++
lang/nl_NL/auth.php | 1 +
lang/nl_NL/general.php | 13 +++++++++++++
lang/nl_NL/results.php | 4 ----
lang/nl_NL/settings/data_integration.php | 2 +-
lang/nl_NL/settings/notifications.php | 13 +++++++++++++
lang/pt_BR/auth.php | 1 +
lang/pt_BR/general.php | 13 +++++++++++++
lang/pt_BR/results.php | 4 ----
lang/pt_BR/settings/data_integration.php | 2 +-
lang/pt_BR/settings/notifications.php | 13 +++++++++++++
25 files changed, 155 insertions(+), 33 deletions(-)
diff --git a/lang/de_DE/auth.php b/lang/de_DE/auth.php
index 6d83d009e..ed5c9f604 100644
--- a/lang/de_DE/auth.php
+++ b/lang/de_DE/auth.php
@@ -13,6 +13,7 @@
|
*/
+ 'sign_in' => 'Anmelden',
'failed' => 'Diese Zugangsdaten stimmen nicht mit unseren Datensätzen überein.',
'password' => 'Das angegebene Passwort ist falsch.',
'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.',
diff --git a/lang/de_DE/general.php b/lang/de_DE/general.php
index 86af86298..4cbb97ede 100644
--- a/lang/de_DE/general.php
+++ b/lang/de_DE/general.php
@@ -1,6 +1,11 @@
'Aktuelle Version',
+ 'latest_version' => 'Neueste Version',
+ 'github' => 'GitHub',
+ 'repository' => 'Repository',
+
// Common actions
'save' => 'Speichern',
'cancel' => 'Abbrechen',
@@ -32,6 +37,10 @@
'created_at' => 'Erstellt am',
'updated_at' => 'Aktualisiert am',
'url' => 'URL',
+ 'server' => 'Server',
+ 'servers' => 'Server',
+ 'stats' => 'Statistiken',
+ 'statistics' => 'Statistiken',
// Navigation
'dashboard' => 'Dashboard',
@@ -42,6 +51,7 @@
'view_documentation' => 'Dokumentation anzeigen',
'links' => 'Links',
'donate' => 'Spenden',
+ 'donations' => 'Spenden',
// Roles
'admin' => 'Admin',
@@ -54,12 +64,15 @@
'last_month' => 'Letzten Monat',
// Metrics
+ 'metrics' => 'Metriken',
'average' => 'Durchschnitt',
'high' => 'Hoch',
'low' => 'Niedrig',
'faster' => 'schneller',
'slower' => 'langsamer',
'healthy' => 'Gesund',
+ 'not_measured' => 'Nicht gemessen',
+ 'unhealthy' => 'fehlerhaft',
// Units
'ms' => 'M',
diff --git a/lang/de_DE/results.php b/lang/de_DE/results.php
index 127d85b81..a506bce62 100644
--- a/lang/de_DE/results.php
+++ b/lang/de_DE/results.php
@@ -55,9 +55,6 @@
// Actions
'update_comments' => 'Kommentare aktualisieren',
- 'truncate_results' => 'Ergebnisse kürzen',
- 'truncate_results_description' => 'Sind Sie sicher, dass Sie alle Ergebnisse kürzen möchten? Diese Aktion kann nicht rückgängig gemacht werden.',
- 'truncate_results_success' => 'Ergebnistabelle abgeschnitten!',
'view_on_speedtest_net' => 'Auf Speedtest.net anzeigen',
// Notifications
@@ -72,7 +69,6 @@
// Run Speedtest Action
'speedtest' => 'Schnelligkeit',
- 'public_dashboard' => 'Öffentliches Dashboard',
'select_server' => 'Server auswählen',
'select_server_helper' => 'Leer lassen, um den Speedtest auszuführen, ohne einen Server anzugeben. Blockierte Server werden übersprungen.',
'manual_servers' => 'Manuelle Server',
diff --git a/lang/de_DE/settings/data_integration.php b/lang/de_DE/settings/data_integration.php
index 83464fa4d..71d1936dc 100644
--- a/lang/de_DE/settings/data_integration.php
+++ b/lang/de_DE/settings/data_integration.php
@@ -28,7 +28,7 @@
'influxdb_test_success_body' => 'Testdaten wurden an InfluxDB gesendet. Überprüfen Sie, ob die Daten empfangen wurden.',
// Bulk write notifications
- 'influxdb_bulk_write_failed' => 'Fehler beim Erstellen des Schreibens auf Influxdb.',
+ 'influxdb_bulk_write_failed' => 'Fehler beim Schreiben von Massendaten in InfluxDB.',
'influxdb_bulk_write_failed_body' => 'Überprüfen Sie die Protokolle für weitere Details.',
'influxdb_bulk_write_success' => 'Massendatenlade für Influxdb abgeschlossen.',
'influxdb_bulk_write_success_body' => 'Daten wurden an InfluxDB gesendet. Überprüfen Sie, ob die Daten empfangen wurden.',
diff --git a/lang/de_DE/settings/notifications.php b/lang/de_DE/settings/notifications.php
index 6d6623a47..8ec3ee6dc 100644
--- a/lang/de_DE/settings/notifications.php
+++ b/lang/de_DE/settings/notifications.php
@@ -14,6 +14,19 @@
'recipients' => 'Empfänger',
'test_mail_channel' => 'Mail-Kanal testen',
+ // Apprise notifications
+ 'apprise' => 'Apprise',
+ 'enable_apprise_notifications' => 'Apprise Benachrichtigungen aktivieren',
+ 'apprise_server' => 'Apprise Server',
+ 'apprise_server_url' => 'Apprise Server URL',
+ 'apprise_verify_ssl' => 'SSL verifizieren',
+ 'apprise_channels' => 'Apprise Kanäle',
+ 'apprise_channel_url' => 'Kanal URL',
+ 'apprise_hint_description' => 'Lesen Sie für weitere Informationen zum Einrichten von Apprise die Dokumentation.',
+ 'apprise_channel_url_helper' => 'Geben Sie die Service Endpoint URL für Benachrichtigung an.',
+ 'test_apprise_channel' => 'Apprise testen',
+ 'apprise_channel_url_validation_error' => 'Die Apprise Channel URL muss nicht mit "HTTP" oder "HTTPS" starten. Geben Sie ein valides Apprise URL Schema an.',
+
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
diff --git a/lang/es_ES/auth.php b/lang/es_ES/auth.php
index f4b03c1d5..b150afe6b 100644
--- a/lang/es_ES/auth.php
+++ b/lang/es_ES/auth.php
@@ -13,6 +13,7 @@
|
*/
+ 'sign_in' => 'Iniciar sesión',
'failed' => 'Estas credenciales no coinciden con nuestros registros.',
'password' => 'La contraseña proporcionada es incorrecta.',
'throttle' => 'Demasiados intentos de inicio de sesión. Por favor, inténtalo de nuevo en :seconds segundos.',
diff --git a/lang/es_ES/general.php b/lang/es_ES/general.php
index bf2e3a571..fddf7a982 100644
--- a/lang/es_ES/general.php
+++ b/lang/es_ES/general.php
@@ -1,6 +1,11 @@
'Versión actual',
+ 'latest_version' => 'Última versión',
+ 'github' => 'GitHub',
+ 'repository' => 'Repositorio',
+
// Common actions
'save' => 'Guardar',
'cancel' => 'Cancelar',
@@ -16,6 +21,7 @@
'no' => 'Nu',
'options' => 'Opciones',
'details' => 'Detalles',
+ 'view' => 'Ver',
// Common labels
'name' => 'Nombre',
@@ -31,6 +37,10 @@
'created_at' => 'Creado el',
'updated_at' => 'Actualizado el',
'url' => 'URL',
+ 'server' => 'Servidor',
+ 'servers' => 'Servidores',
+ 'stats' => 'Estadísticas',
+ 'statistics' => 'Estadísticas',
// Navigation
'dashboard' => 'Tablero',
@@ -38,8 +48,10 @@
'settings' => 'Ajustes',
'users' => 'Usuarios',
'documentation' => 'Documentación',
+ 'view_documentation' => 'Ver documentación',
'links' => 'Enlaces',
'donate' => 'Donar',
+ 'donations' => 'Donaciones',
// Roles
'admin' => 'Admin',
@@ -52,12 +64,15 @@
'last_month' => 'Último mes',
// Metrics
+ 'metrics' => 'Métricas',
'average' => 'Promedio',
'high' => 'Alta',
'low' => 'Baja',
'faster' => 'más rápido',
'slower' => 'más lento',
'healthy' => 'Saludable',
+ 'not_measured' => 'No medido',
+ 'unhealthy' => 'Poco saludable',
// Units
'ms' => 'm',
diff --git a/lang/es_ES/results.php b/lang/es_ES/results.php
index f41af5df8..89df7e984 100644
--- a/lang/es_ES/results.php
+++ b/lang/es_ES/results.php
@@ -55,21 +55,20 @@
// Actions
'update_comments' => 'Actualizar comentarios',
- 'truncate_results' => 'Truncar resultados',
- 'truncate_results_description' => '¿Está seguro que desea truncar todos los resultados? Esta acción es irreversible.',
- 'truncate_results_success' => '¡Tabla de resultados truncada!',
'view_on_speedtest_net' => 'Ver en Speedtest.net',
// Notifications
+ 'speedtest_benchmark_passed' => 'La prueba de rendimiento de velocidad ha pasado',
+ 'speedtest_benchmark_failed' => 'Prueba de rendimiento de velocidad fallida',
'speedtest_started' => 'Velocidad iniciada',
'speedtest_completed' => 'Velocidad completada',
+ 'speedtest_failed' => 'Error en la prueba de velocidad',
'download_threshold_breached' => '¡Umbral de descarga incumplido!',
'upload_threshold_breached' => '¡Umbral de subida infringido!',
'ping_threshold_breached' => '¡Umbral de ping infringido!',
// Run Speedtest Action
'speedtest' => 'Velocidad',
- 'public_dashboard' => 'Panel público',
'select_server' => 'Seleccionar Servidor',
'select_server_helper' => 'Dejar en blanco para ejecutar el test de velocidad sin especificar un servidor. Se omitirán los servidores bloqueados.',
'manual_servers' => 'Servidores manuales',
diff --git a/lang/es_ES/settings/data_integration.php b/lang/es_ES/settings/data_integration.php
index 1c3633559..a1b024848 100644
--- a/lang/es_ES/settings/data_integration.php
+++ b/lang/es_ES/settings/data_integration.php
@@ -28,11 +28,18 @@
'influxdb_test_success_body' => 'Los datos de prueba han sido enviados a InfluxDB, compruebe si los datos han sido recibidos.',
// Bulk write notifications
- 'influxdb_bulk_write_failed' => 'Error al construir escritura en Influxdb.',
+ 'influxdb_bulk_write_failed' => 'Error al escribir en masa a Influxdb.',
'influxdb_bulk_write_failed_body' => 'Revisa los registros para más detalles.',
'influxdb_bulk_write_success' => 'Carga de datos en masa a Influxdb.',
'influxdb_bulk_write_success_body' => 'Los datos han sido enviados a InfluxDB, compruebe si los datos han sido recibidos.',
+ // Prometheus
+ 'prometheus' => 'Prometeo',
+ 'prometheus_enabled' => 'Activar',
+ 'prometheus_enabled_helper_text' => 'Cuando está activado, las métricas para cada prueba de velocidad nueva estarán disponibles en el punto final /prometheus.',
+ 'prometheus_allowed_ips' => 'Direcciones IP permitidas',
+ 'prometheus_allowed_ips_helper' => 'Lista de direcciones IP o rangos CIDR (por ejemplo, 192.168.1.0/24) permitieron acceder al extremo de las métricas. Dejar en blanco para permitir todas las IPs.',
+
// Common labels
'org' => 'Org',
'bucket' => 'Cubo',
diff --git a/lang/es_ES/settings/notifications.php b/lang/es_ES/settings/notifications.php
index ddbb10dca..83d8bbf20 100644
--- a/lang/es_ES/settings/notifications.php
+++ b/lang/es_ES/settings/notifications.php
@@ -7,27 +7,35 @@
// Database notifications
'database' => 'Base de datos',
'database_description' => 'Las notificaciones enviadas a este canal se mostrarán bajo el icono :belell: en el encabezado.',
- 'database_on_speedtest_run' => 'Notificar en cada prueba de velocidad',
- 'database_on_threshold_failure' => 'Notificar en los umbrales de fallos',
'test_database_channel' => 'Probar canal de base de datos',
// Mail notifications
'mail' => 'Correo',
'recipients' => 'Destinatarios',
- 'mail_on_speedtest_run' => 'Notificar en cada prueba de velocidad',
- 'mail_on_threshold_failure' => 'Notificar en los umbrales de fallos',
'test_mail_channel' => 'Canal de prueba de correo',
+ // Apprise notifications
+ 'apprise' => 'Apprise',
+ 'enable_apprise_notifications' => 'Habilitar notificaciones Apprise',
+ 'apprise_server' => 'Servidor Apprise',
+ 'apprise_server_url' => 'URL del servidor',
+ 'apprise_verify_ssl' => 'Verificar SSL',
+ 'apprise_channels' => 'Canales de expedición',
+ 'apprise_channel_url' => 'URL del canal',
+ 'apprise_hint_description' => 'Para más información sobre cómo configurar Apprise, vea la documentación.',
+ 'apprise_channel_url_helper' => 'Proporcionar la URL de los puntos finales del servicio para las notificaciones.',
+ 'test_apprise_channel' => 'Prueba de aviso',
+ 'apprise_channel_url_validation_error' => 'La URL del canal Apprise no debe comenzar con "http" o "https". Por favor, proporcione un esquema de URL de Apprise válido.',
+
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
- 'webhook_on_speedtest_run' => 'Notificar en cada prueba de velocidad',
- 'webhook_on_threshold_failure' => 'Notificar en los umbrales de fallos',
'test_webhook_channel' => 'Probar canal webhook',
+ 'webhook_hint_description' => 'Estos son webhooks genéricos. Para ejemplos de payload y detalles de la implementación, vea la documentación.',
// Common notification messages
- 'notify_on_every_speedtest_run' => 'Notificar en cada prueba de velocidad',
- 'notify_on_threshold_failures' => 'Notificar en los umbrales de fallos',
+ 'notify_on_every_speedtest_run' => 'Notificar en cada prueba de velocidad programada',
+ 'notify_on_threshold_failures' => 'Notificar fallos de umbral para pruebas de velocidad programadas',
// Test notification messages
'test_notifications' => [
diff --git a/lang/fr_FR/auth.php b/lang/fr_FR/auth.php
index c1a529c3a..fc8d1390b 100644
--- a/lang/fr_FR/auth.php
+++ b/lang/fr_FR/auth.php
@@ -13,6 +13,7 @@
|
*/
+ 'sign_in' => 'Se connecter',
'failed' => 'Ces identifiants ne correspondent pas à nos enregistrements.',
'password' => 'Le mot de passe fourni est incorrect.',
'throttle' => 'Trop de tentatives de connexion. Veuillez réessayer dans :seconds secondes.',
diff --git a/lang/fr_FR/general.php b/lang/fr_FR/general.php
index adeba836d..4b9422d7f 100644
--- a/lang/fr_FR/general.php
+++ b/lang/fr_FR/general.php
@@ -1,6 +1,11 @@
'Version actuelle',
+ 'latest_version' => 'Dernière version',
+ 'github' => 'GitHub',
+ 'repository' => 'Dépôt',
+
// Common actions
'save' => 'Enregistrer',
'cancel' => 'Abandonner',
@@ -32,6 +37,10 @@
'created_at' => 'Créé le',
'updated_at' => 'Mis à jour le',
'url' => 'URL',
+ 'server' => 'Serveur',
+ 'servers' => 'Serveurs',
+ 'stats' => 'Stats',
+ 'statistics' => 'Statistiques',
// Navigation
'dashboard' => 'Tableau de bord',
@@ -42,6 +51,7 @@
'view_documentation' => 'Afficher la documentation',
'links' => 'Liens',
'donate' => 'Faire un don',
+ 'donations' => 'Dons',
// Roles
'admin' => 'Administrateur',
@@ -54,12 +64,15 @@
'last_month' => 'Le mois dernier',
// Metrics
+ 'metrics' => 'Métriques',
'average' => 'Moyenne',
'high' => 'Élevé',
'low' => 'Bas',
'faster' => 'rapide',
'slower' => 'lent',
'healthy' => 'Sain',
+ 'not_measured' => 'Non mesuré',
+ 'unhealthy' => 'Malsain',
// Units
'ms' => 'ms',
diff --git a/lang/fr_FR/results.php b/lang/fr_FR/results.php
index 7284dcbbc..ab2715930 100644
--- a/lang/fr_FR/results.php
+++ b/lang/fr_FR/results.php
@@ -55,9 +55,6 @@
// Actions
'update_comments' => 'Actualiser les commentaires',
- 'truncate_results' => 'Tronquer les résultats',
- 'truncate_results_description' => 'Êtes-vous sûr de vouloir tronquer tous les résultats ? Cette action est irréversible.',
- 'truncate_results_success' => 'Tableau des résultats tronqué !',
'view_on_speedtest_net' => 'Voir sur Speedtest.net',
// Notifications
@@ -72,7 +69,6 @@
// Run Speedtest Action
'speedtest' => 'Test de vitesse',
- 'public_dashboard' => 'Tableau de bord public',
'select_server' => 'Sélectionner un serveur',
'select_server_helper' => 'Laisser vide pour exécuter le test de vitesse sans spécifier de serveur. Les serveurs bloqués seront ignorés.',
'manual_servers' => 'Serveurs manuels',
diff --git a/lang/fr_FR/settings/data_integration.php b/lang/fr_FR/settings/data_integration.php
index 007362964..ce0af0775 100644
--- a/lang/fr_FR/settings/data_integration.php
+++ b/lang/fr_FR/settings/data_integration.php
@@ -28,7 +28,7 @@
'influxdb_test_success_body' => 'Les données de test ont été envoyées à InfluxDB, vérifiez si les données ont été reçues.',
// Bulk write notifications
- 'influxdb_bulk_write_failed' => 'Échec de la construction de l\'écriture sur Influxdb.',
+ 'influxdb_bulk_write_failed' => 'Impossible d\'écrire en masse sur Influxdb.',
'influxdb_bulk_write_failed_body' => 'Consultez les journaux pour plus de détails.',
'influxdb_bulk_write_success' => 'Charge de données en masse terminée sur Influxdb.',
'influxdb_bulk_write_success_body' => 'Les données ont été envoyées à InfluxDB, vérifiez si les données ont été reçues.',
diff --git a/lang/fr_FR/settings/notifications.php b/lang/fr_FR/settings/notifications.php
index 778334423..359a1424a 100644
--- a/lang/fr_FR/settings/notifications.php
+++ b/lang/fr_FR/settings/notifications.php
@@ -14,6 +14,19 @@
'recipients' => 'Destinataires',
'test_mail_channel' => 'Tester le canal de messagerie',
+ // Apprise notifications
+ 'apprise' => 'Apprise',
+ 'enable_apprise_notifications' => 'Activer les notifications de base de données',
+ 'apprise_server' => 'Serveur Apprise',
+ 'apprise_server_url' => 'Serveur Apprise',
+ 'apprise_verify_ssl' => 'Vérifier SSL',
+ 'apprise_channels' => 'Canaux Apprise',
+ 'apprise_channel_url' => 'URL du canal de mise à jour',
+ 'apprise_hint_description' => 'Pour plus d\'informations sur la configuration d\'Apprise, consultez la documentation.',
+ 'apprise_channel_url_helper' => 'Fournir l\'URL de terminaison du service pour les notifications.',
+ 'test_apprise_channel' => 'Apprise de test',
+ 'apprise_channel_url_validation_error' => 'L\'URL du canal Apprise ne doit pas commencer par "http" ou "https". Veuillez fournir un schéma d\'URL Apprise valide.',
+
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
diff --git a/lang/nl_NL/auth.php b/lang/nl_NL/auth.php
index 7667d5e81..2c9909f81 100644
--- a/lang/nl_NL/auth.php
+++ b/lang/nl_NL/auth.php
@@ -13,6 +13,7 @@
|
*/
+ 'sign_in' => 'Aanmelden',
'failed' => 'Deze gegevens komen niet overeen met onze administratie.',
'password' => 'Het opgegeven wachtwoord is onjuist.',
'throttle' => 'Te veel inlogpogingen. Probeer het over :seconds seconden opnieuw.',
diff --git a/lang/nl_NL/general.php b/lang/nl_NL/general.php
index 28e36b0f9..e17828565 100644
--- a/lang/nl_NL/general.php
+++ b/lang/nl_NL/general.php
@@ -1,6 +1,11 @@
'Huidige versie',
+ 'latest_version' => 'Laatste versie',
+ 'github' => 'GitHub',
+ 'repository' => 'Repository',
+
// Common actions
'save' => 'Opslaan',
'cancel' => 'Annuleren',
@@ -32,6 +37,10 @@
'created_at' => 'Aangemaakt op',
'updated_at' => 'Bijgewerkt op',
'url' => 'URL',
+ 'server' => 'Server',
+ 'servers' => 'Servers',
+ 'stats' => 'Statistieken',
+ 'statistics' => 'Statistieken',
// Navigation
'dashboard' => 'Dashboard',
@@ -42,6 +51,7 @@
'view_documentation' => 'Bekijk documentatie',
'links' => 'Koppelingen',
'donate' => 'Doneren',
+ 'donations' => 'Donaties',
// Roles
'admin' => 'Beheerder',
@@ -54,12 +64,15 @@
'last_month' => 'Vorige maand',
// Metrics
+ 'metrics' => 'Statistieken',
'average' => 'Gemiddeld',
'high' => 'Hoog',
'low' => 'Laag',
'faster' => 'sneller',
'slower' => 'langzamer',
'healthy' => 'Gezond',
+ 'not_measured' => 'Niet gemeten',
+ 'unhealthy' => 'Ongezond',
// Units
'ms' => 'ms',
diff --git a/lang/nl_NL/results.php b/lang/nl_NL/results.php
index 69befafa6..4a17ed6c8 100644
--- a/lang/nl_NL/results.php
+++ b/lang/nl_NL/results.php
@@ -55,9 +55,6 @@
// Actions
'update_comments' => 'Reacties bijwerken',
- 'truncate_results' => 'Afkappen resultaten',
- 'truncate_results_description' => 'Weet je zeker dat je alle resultaten wilt afbreken? Deze actie is onomkeerbaar.',
- 'truncate_results_success' => 'Resultatentabel afgekapt!',
'view_on_speedtest_net' => 'Bekijk op Speedtest.net',
// Notifications
@@ -72,7 +69,6 @@
// Run Speedtest Action
'speedtest' => 'Snelheidstest',
- 'public_dashboard' => 'Openbaar Dashboard',
'select_server' => 'Selecteer Server',
'select_server_helper' => 'Laat leeg om de snelheidstest uit te voeren zonder een server op te geven. Geblokkeerde servers zullen worden overgeslagen.',
'manual_servers' => 'Handmatige servers',
diff --git a/lang/nl_NL/settings/data_integration.php b/lang/nl_NL/settings/data_integration.php
index 2508fdf6b..c95902fac 100644
--- a/lang/nl_NL/settings/data_integration.php
+++ b/lang/nl_NL/settings/data_integration.php
@@ -28,7 +28,7 @@
'influxdb_test_success_body' => 'Test gegevens zijn verzonden naar de InfluxDB, controleer of de gegevens zijn ontvangen.',
// Bulk write notifications
- 'influxdb_bulk_write_failed' => 'Kan schrijven naar Influxdb niet maken.',
+ 'influxdb_bulk_write_failed' => 'Bulk schrijven naar Influxdb mislukt.',
'influxdb_bulk_write_failed_body' => 'Bekijk de logs voor meer details.',
'influxdb_bulk_write_success' => 'Alle resultaten naar InfluxDB sturen afgerond.',
'influxdb_bulk_write_success_body' => 'Gegevens zijn verzonden naar InfluxDB, controleer of de gegevens zijn ontvangen.',
diff --git a/lang/nl_NL/settings/notifications.php b/lang/nl_NL/settings/notifications.php
index 9e1942078..e7eff7a97 100644
--- a/lang/nl_NL/settings/notifications.php
+++ b/lang/nl_NL/settings/notifications.php
@@ -14,6 +14,19 @@
'recipients' => 'Ontvangers',
'test_mail_channel' => 'Test e-mailkanaal',
+ // Apprise notifications
+ 'apprise' => 'Apprise',
+ 'enable_apprise_notifications' => 'Inschakelen Apprise meldingen',
+ 'apprise_server' => 'Apprise Server',
+ 'apprise_server_url' => 'Appprise Server-URL',
+ 'apprise_verify_ssl' => 'Controleer SSL',
+ 'apprise_channels' => 'Apprise Kanalen',
+ 'apprise_channel_url' => 'Kanaal URL',
+ 'apprise_hint_description' => 'Voor meer informatie over het instellen van Apprise, bekijk de documentatie.',
+ 'apprise_channel_url_helper' => 'Geef de service eindpunt URL voor meldingen.',
+ 'test_apprise_channel' => 'Test Apprise',
+ 'apprise_channel_url_validation_error' => 'De URL van het Apprise kanaal mag niet beginnen met "http" of "https". Geef een geldig URL-schema op.',
+
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
diff --git a/lang/pt_BR/auth.php b/lang/pt_BR/auth.php
index 16ae2cdb5..13625436a 100644
--- a/lang/pt_BR/auth.php
+++ b/lang/pt_BR/auth.php
@@ -13,6 +13,7 @@
|
*/
+ 'sign_in' => 'Entrar',
'failed' => 'Credenciais não correspondem aos nossos registros.',
'password' => 'A senha fornecida está incorreta.',
'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.',
diff --git a/lang/pt_BR/general.php b/lang/pt_BR/general.php
index 57ee664d6..7a8b67af2 100644
--- a/lang/pt_BR/general.php
+++ b/lang/pt_BR/general.php
@@ -1,6 +1,11 @@
'Versão atual',
+ 'latest_version' => 'Versão mais recente',
+ 'github' => 'GitHub',
+ 'repository' => 'Repositório',
+
// Common actions
'save' => 'Salvar',
'cancel' => 'Cancelar',
@@ -32,6 +37,10 @@
'created_at' => 'Criado em',
'updated_at' => 'Atualizado em',
'url' => 'URL',
+ 'server' => 'Servidor',
+ 'servers' => 'Servidores',
+ 'stats' => 'Estatísticas',
+ 'statistics' => 'Estatísticas',
// Navigation
'dashboard' => 'Painel',
@@ -42,6 +51,7 @@
'view_documentation' => 'Ver documentação',
'links' => 'Links',
'donate' => 'Doar',
+ 'donations' => 'Doações',
// Roles
'admin' => 'Admin',
@@ -54,12 +64,15 @@
'last_month' => 'Mês anterior',
// Metrics
+ 'metrics' => 'Métricas',
'average' => 'Média',
'high' => 'Alta',
'low' => 'Baixa',
'faster' => 'mais rápido',
'slower' => 'lento',
'healthy' => 'Saudável',
+ 'not_measured' => 'Não medido',
+ 'unhealthy' => 'Não saudável',
// Units
'ms' => 'ms',
diff --git a/lang/pt_BR/results.php b/lang/pt_BR/results.php
index bc725b402..5d3c62055 100644
--- a/lang/pt_BR/results.php
+++ b/lang/pt_BR/results.php
@@ -55,9 +55,6 @@
// Actions
'update_comments' => 'Atualizar comentários',
- 'truncate_results' => 'Truncar resultados',
- 'truncate_results_description' => 'Tem certeza que deseja truncar todos os resultados? Esta ação é irreversível.',
- 'truncate_results_success' => 'Tabela de resultados truncada!',
'view_on_speedtest_net' => 'Ver em Speedtest.net',
// Notifications
@@ -72,7 +69,6 @@
// Run Speedtest Action
'speedtest' => 'Teste de velocidade',
- 'public_dashboard' => 'Painel público',
'select_server' => 'Selecionar servidor',
'select_server_helper' => 'Deixe em branco para executar o acelerador sem especificar um servidor. Os servidores bloqueados serão ignorados.',
'manual_servers' => 'Servidores manuais',
diff --git a/lang/pt_BR/settings/data_integration.php b/lang/pt_BR/settings/data_integration.php
index 2b5dd5ae2..02cc2a6fd 100644
--- a/lang/pt_BR/settings/data_integration.php
+++ b/lang/pt_BR/settings/data_integration.php
@@ -28,7 +28,7 @@
'influxdb_test_success_body' => 'Dados de teste enviados para InfluxDB, verifique se os dados foram recebidos.',
// Bulk write notifications
- 'influxdb_bulk_write_failed' => 'Falha ao escrever no Influxdb.',
+ 'influxdb_bulk_write_failed' => 'Falha ao escrever em massa no Influxdb.',
'influxdb_bulk_write_failed_body' => 'Confira os logs para mais detalhes.',
'influxdb_bulk_write_success' => 'Carga massiva de dados concluída para o Influxdb.',
'influxdb_bulk_write_success_body' => 'Os dados foram enviados para InfluxDB, verifique se os dados foram recebidos.',
diff --git a/lang/pt_BR/settings/notifications.php b/lang/pt_BR/settings/notifications.php
index 6d90ed8f4..827ce15f8 100644
--- a/lang/pt_BR/settings/notifications.php
+++ b/lang/pt_BR/settings/notifications.php
@@ -14,6 +14,19 @@
'recipients' => 'Destinatários',
'test_mail_channel' => 'Testar canal de e-mail',
+ // Apprise notifications
+ 'apprise' => 'Informar',
+ 'enable_apprise_notifications' => 'Habilitar notificações Apprise',
+ 'apprise_server' => 'Servidor Apprise',
+ 'apprise_server_url' => 'URL do Servidor Apprise',
+ 'apprise_verify_ssl' => 'Verificar SSL',
+ 'apprise_channels' => 'Canais Apprise',
+ 'apprise_channel_url' => 'URL do Canal',
+ 'apprise_hint_description' => 'Para obter mais informações sobre como configurar o Apprise, veja a documentação.',
+ 'apprise_channel_url_helper' => 'Forneça o URL do serviço endpoint para notificações.',
+ 'test_apprise_channel' => 'Testar Apprise',
+ 'apprise_channel_url_validation_error' => 'O URL do canal Apprise não deve começar com "http" ou "https". Por favor, forneça um esquema válido de URL Apprise.',
+
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
From 62f13fb009f6589fe1e976910a587f77bb57de39 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Fri, 26 Dec 2025 15:11:39 -0500
Subject: [PATCH 18/34] Mark speedtest as scheduled when triggered using the
API (#2597)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
.../Api/V1/SpeedtestController.php | 1 +
app/Listeners/ProcessCompletedSpeedtest.php | 51 ++-------
app/Listeners/ProcessFailedSpeedtest.php | 35 +-----
app/Listeners/ProcessUnhealthySpeedtest.php | 48 +-------
app/Listeners/UserNotificationSubscriber.php | 104 ++++++++++++++++++
app/Models/Result.php | 11 ++
6 files changed, 132 insertions(+), 118 deletions(-)
create mode 100644 app/Listeners/UserNotificationSubscriber.php
diff --git a/app/Http/Controllers/Api/V1/SpeedtestController.php b/app/Http/Controllers/Api/V1/SpeedtestController.php
index 19519f61e..d605d945c 100644
--- a/app/Http/Controllers/Api/V1/SpeedtestController.php
+++ b/app/Http/Controllers/Api/V1/SpeedtestController.php
@@ -37,6 +37,7 @@ public function __invoke(Request $request)
}
$result = RunSpeedtestAction::run(
+ scheduled: true,
serverId: $request->input('server_id'),
dispatchedBy: $request->user()->id,
);
diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php
index 1b72f3053..ae5d544e1 100644
--- a/app/Listeners/ProcessCompletedSpeedtest.php
+++ b/app/Listeners/ProcessCompletedSpeedtest.php
@@ -31,11 +31,17 @@ public function handle(SpeedtestCompleted $event): void
{
$result = $event->result;
- $result->loadMissing(['dispatchedBy']);
+ if ($result->healthy === false) {
+ return;
+ }
+
+ // Don't send notifications for unscheduled speedtests.
+ if ($result->unscheduled) {
+ return;
+ }
$this->notifyAppriseChannels($result);
$this->notifyDatabaseChannels($result);
- $this->notifyDispatchingUser($result);
$this->notifyMailChannels($result);
$this->notifyWebhookChannels($result);
}
@@ -45,11 +51,6 @@ public function handle(SpeedtestCompleted $event): void
*/
private function notifyAppriseChannels(Result $result): void
{
- // Don't send Apprise notification if dispatched by a user or test is unhealthy.
- if (filled($result->dispatched_by) || $result->healthy === false) {
- return;
- }
-
// Check if Apprise notifications are enabled.
if (! $this->notificationSettings->apprise_enabled || ! $this->notificationSettings->apprise_on_speedtest_run) {
return;
@@ -97,11 +98,6 @@ private function notifyAppriseChannels(Result $result): void
*/
private function notifyDatabaseChannels(Result $result): void
{
- // Don't send database notification if dispatched by a user or test is unhealthy.
- if (filled($result->dispatched_by) || $result->healthy === false) {
- return;
- }
-
// Check if database notifications are enabled.
if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_speedtest_run) {
return;
@@ -120,37 +116,11 @@ private function notifyDatabaseChannels(Result $result): void
}
}
- /**
- * Notify the user who dispatched the speedtest.
- */
- private function notifyDispatchingUser(Result $result): void
- {
- if (empty($result->dispatched_by) || ! $result->healthy) {
- return;
- }
-
- $result->dispatchedBy->notify(
- FilamentNotification::make()
- ->title(__('results.speedtest_completed'))
- ->actions([
- Action::make('view')
- ->label(__('general.view'))
- ->url(route('filament.admin.resources.results.index')),
- ])
- ->success()
- ->toDatabase(),
- );
- }
-
/**
* Notify mail channels.
*/
private function notifyMailChannels(Result $result): void
{
- if (filled($result->dispatched_by) || $result->healthy === false) {
- return;
- }
-
if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_speedtest_run) {
return;
}
@@ -172,11 +142,6 @@ private function notifyMailChannels(Result $result): void
*/
private function notifyWebhookChannels(Result $result): void
{
- // Don't send webhook if dispatched by a user or test is unhealthy.
- if (filled($result->dispatched_by) || $result->healthy === false) {
- return;
- }
-
// Check if webhook notifications are enabled.
if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_speedtest_run) {
return;
diff --git a/app/Listeners/ProcessFailedSpeedtest.php b/app/Listeners/ProcessFailedSpeedtest.php
index 8ba154387..3cc4d9cd8 100644
--- a/app/Listeners/ProcessFailedSpeedtest.php
+++ b/app/Listeners/ProcessFailedSpeedtest.php
@@ -4,8 +4,6 @@
use App\Events\SpeedtestFailed;
use App\Models\Result;
-use Filament\Actions\Action;
-use Filament\Notifications\Notification;
class ProcessFailedSpeedtest
{
@@ -16,10 +14,12 @@ public function handle(SpeedtestFailed $event): void
{
$result = $event->result;
- $result->loadMissing(['dispatchedBy']);
+ // Don't send notifications for unscheduled speedtests.
+ if ($result->unscheduled) {
+ return;
+ }
// $this->notifyAppriseChannels($result);
- $this->notifyDispatchingUser($result);
}
/**
@@ -27,33 +27,6 @@ public function handle(SpeedtestFailed $event): void
*/
private function notifyAppriseChannels(Result $result): void
{
- // Don't send Apprise notification if dispatched by a user or test is unhealthy.
- if (filled($result->dispatched_by) || ! $result->healthy) {
- return;
- }
-
//
}
-
- /**
- * Notify the user who dispatched the speedtest.
- */
- private function notifyDispatchingUser(Result $result): void
- {
- if (empty($result->dispatched_by)) {
- return;
- }
-
- $result->dispatchedBy->notify(
- Notification::make()
- ->title(__('results.speedtest_failed'))
- ->actions([
- Action::make('view')
- ->label(__('general.view'))
- ->url(route('filament.admin.resources.results.index')),
- ])
- ->warning()
- ->toDatabase(),
- );
- }
}
diff --git a/app/Listeners/ProcessUnhealthySpeedtest.php b/app/Listeners/ProcessUnhealthySpeedtest.php
index 4f51e4267..f2e0d419c 100644
--- a/app/Listeners/ProcessUnhealthySpeedtest.php
+++ b/app/Listeners/ProcessUnhealthySpeedtest.php
@@ -33,11 +33,13 @@ public function handle(SpeedtestBenchmarkFailed $event): void
{
$result = $event->result;
- $result->loadMissing(['dispatchedBy']);
+ // Don't send notifications for unscheduled speedtests.
+ if ($result->unscheduled) {
+ return;
+ }
$this->notifyAppriseChannels($result);
$this->notifyDatabaseChannels($result);
- $this->notifyDispatchingUser($result);
$this->notifyMailChannels($result);
$this->notifyWebhookChannels($result);
}
@@ -47,11 +49,6 @@ public function handle(SpeedtestBenchmarkFailed $event): void
*/
private function notifyAppriseChannels(Result $result): void
{
- // Don't send Apprise notification if dispatched by a user.
- if (filled($result->dispatched_by)) {
- return;
- }
-
if (! $this->notificationSettings->apprise_enabled || ! $this->notificationSettings->apprise_on_threshold_failure) {
return;
}
@@ -132,11 +129,6 @@ private function formatMetricValue(string $metric, Result $result): string
*/
private function notifyDatabaseChannels(Result $result): void
{
- // Don't send database notification if dispatched by a user.
- if (filled($result->dispatched_by)) {
- return;
- }
-
// Check if database notifications are enabled.
if (! $this->notificationSettings->database_enabled || ! $this->notificationSettings->database_on_threshold_failure) {
return;
@@ -155,38 +147,11 @@ private function notifyDatabaseChannels(Result $result): void
}
}
- /**
- * Notify the user who dispatched the speedtest.
- */
- private function notifyDispatchingUser(Result $result): void
- {
- if (empty($result->dispatched_by)) {
- return;
- }
-
- $result->dispatchedBy->notify(
- FilamentNotification::make()
- ->title(__('results.speedtest_benchmark_failed'))
- ->actions([
- Action::make('view')
- ->label(__('general.view'))
- ->url(route('filament.admin.resources.results.index')),
- ])
- ->warning()
- ->toDatabase(),
- );
- }
-
/**
* Notify mail channels.
*/
private function notifyMailChannels(Result $result): void
{
- // Don't send mail if dispatched by a user.
- if (filled($result->dispatched_by)) {
- return;
- }
-
// Check if mail notifications are enabled.
if (! $this->notificationSettings->mail_enabled || ! $this->notificationSettings->mail_on_threshold_failure) {
return;
@@ -210,11 +175,6 @@ private function notifyMailChannels(Result $result): void
*/
private function notifyWebhookChannels(Result $result): void
{
- // Don't send webhook if dispatched by a user.
- if (filled($result->dispatched_by)) {
- return;
- }
-
// Check if webhook notifications are enabled.
if (! $this->notificationSettings->webhook_enabled || ! $this->notificationSettings->webhook_on_threshold_failure) {
return;
diff --git a/app/Listeners/UserNotificationSubscriber.php b/app/Listeners/UserNotificationSubscriber.php
new file mode 100644
index 000000000..4ff7dc7c8
--- /dev/null
+++ b/app/Listeners/UserNotificationSubscriber.php
@@ -0,0 +1,104 @@
+result;
+
+ if (empty($result->dispatched_by)) {
+ return;
+ }
+
+ $result->loadMissing('dispatchedBy');
+
+ Notification::make()
+ ->title(__('results.speedtest_completed'))
+ ->actions([
+ Action::make('view')
+ ->label(__('general.view'))
+ ->url(route('filament.admin.resources.results.index')),
+ ])
+ ->success()
+ ->sendToDatabase($result->dispatchedBy);
+ }
+
+ /**
+ * Handle the event.
+ */
+ public function handleBenchmarkFailed(SpeedtestBenchmarkFailed $event): void
+ {
+ $result = $event->result;
+
+ if (empty($result->dispatched_by)) {
+ return;
+ }
+
+ // Don't send notifications for unscheduled speedtests.
+ if ($result->unscheduled) {
+ return;
+ }
+
+ $result->loadMissing('dispatchedBy');
+
+ Notification::make()
+ ->title(__('results.speedtest_benchmark_failed'))
+ ->actions([
+ Action::make('view')
+ ->label(__('general.view'))
+ ->url(route('filament.admin.resources.results.index')),
+ ])
+ ->warning()
+ ->sendToDatabase($result->dispatchedBy);
+ }
+
+ /**
+ * Handle the event.
+ */
+ public function handleFailed(SpeedtestFailed $event): void
+ {
+ $result = $event->result;
+
+ if (empty($result->dispatched_by)) {
+ return;
+ }
+
+ $result->loadMissing('dispatchedBy');
+
+ Notification::make()
+ ->title(__('results.speedtest_failed'))
+ ->actions([
+ Action::make('view')
+ ->label(__('general.view'))
+ ->url(route('filament.admin.resources.results.index')),
+ ])
+ ->warning()
+ ->sendToDatabase($result->dispatchedBy);
+ }
+
+ /**
+ * Register the listeners for the subscriber.
+ *
+ * @return array
+ */
+ public function subscribe(Dispatcher $events): array
+ {
+ return [
+ SpeedtestCompleted::class => 'handleCompleted',
+ SpeedtestBenchmarkFailed::class => 'handleBenchmarkFailed',
+ SpeedtestFailed::class => 'handleFailed',
+ ];
+ }
+}
diff --git a/app/Models/Result.php b/app/Models/Result.php
index bba1a37d9..084c04097 100644
--- a/app/Models/Result.php
+++ b/app/Models/Result.php
@@ -6,6 +6,7 @@
use App\Enums\ResultStatus;
use App\Models\Traits\ResultDataAttributes;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
@@ -54,4 +55,14 @@ public function dispatchedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'dispatched_by');
}
+
+ /**
+ * Determine if the result was unscheduled.
+ */
+ protected function unscheduled(): Attribute
+ {
+ return Attribute::make(
+ get: fn (): bool => ! $this->scheduled,
+ );
+ }
}
From ae3eb321c16167bf9c25bf7c59d7f01e9050de78 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Fri, 26 Dec 2025 15:14:30 -0500
Subject: [PATCH 19/34] New Crowdin updates (#2598)
---
lang/nl_NL/settings/notifications.php | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/lang/nl_NL/settings/notifications.php b/lang/nl_NL/settings/notifications.php
index e7eff7a97..7a37c850a 100644
--- a/lang/nl_NL/settings/notifications.php
+++ b/lang/nl_NL/settings/notifications.php
@@ -19,19 +19,20 @@
'enable_apprise_notifications' => 'Inschakelen Apprise meldingen',
'apprise_server' => 'Apprise Server',
'apprise_server_url' => 'Appprise Server-URL',
+ 'apprise_server_url_helper' => 'De URL van uw Apprise Server. De URL moet eindigen op /notify',
'apprise_verify_ssl' => 'Controleer SSL',
- 'apprise_channels' => 'Apprise Kanalen',
- 'apprise_channel_url' => 'Kanaal URL',
- 'apprise_hint_description' => 'Voor meer informatie over het instellen van Apprise, bekijk de documentatie.',
- 'apprise_channel_url_helper' => 'Geef de service eindpunt URL voor meldingen.',
+ 'apprise_channels' => 'Notificatie kanalen',
+ 'apprise_channel_url' => 'Service URL',
+ 'apprise_hint_description' => 'Met Apprise kan je meldingen verzenden naar meer dan 90 diensten. Je moet een Apprise server hebben draaien en onderstaande service URL\'s configureren.',
+ 'apprise_channel_url_helper' => 'Gebruik Apprise URL formaat. Bijvoorbeeld discord://WebhookID/Token, slack://TokenA/TokenB/TokenC',
'test_apprise_channel' => 'Test Apprise',
- 'apprise_channel_url_validation_error' => 'De URL van het Apprise kanaal mag niet beginnen met "http" of "https". Geef een geldig URL-schema op.',
+ 'apprise_channel_url_validation_error' => 'Ongeldige Apprise URL. De URL moet gebruik maken van Apprise formaat (bijv. discord://, slack://), niet http:// of https://. Zie de Apprise documentatie voor meer informatie',
// Webhook
'webhook' => 'Webhook',
'webhooks' => 'Webhooks',
'test_webhook_channel' => 'Test webhook kanaal',
- 'webhook_hint_description' => 'Dit zijn generieke webhooks. Raadpleeg de documentatie voor voorbeelden van payloads en implementatiedetails.',
+ 'webhook_hint_description' => 'Dit zijn algemene webhooks. Voor payload voorbeelden en implementatiegegevens, bekijk de documentatie. Voor diensten zoals Discord, Ntfy etc. gebruik Apprise.',
// Common notification messages
'notify_on_every_speedtest_run' => 'Notificatie bij elke geplande snelheidstest',
From 94159115693c897e3c2e56bd57022ab436c2d7a3 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Fri, 26 Dec 2025 15:16:31 -0500
Subject: [PATCH 20/34] Release v1.13.4 (#2605)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
config/speedtest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index 94ebcaa84..79c2e84b4 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-19'),
+ 'build_date' => Carbon::parse('2025-12-26'),
- 'build_version' => 'v1.13.3',
+ 'build_version' => 'v1.13.4',
'content_width' => env('CONTENT_WIDTH', '7xl'),
From fd7a034235cca2150cea114d373a3c4ef7b93c7d Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Fri, 26 Dec 2025 20:16:11 -0500
Subject: [PATCH 21/34] Rename benchmark healthy/unhealthy event classes and
update references (#2607)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
...tBenchmarkFailed.php => SpeedtestBenchmarkHealthy.php} | 2 +-
...enchmarkPassed.php => SpeedtestBenchmarkUnhealthy.php} | 2 +-
app/Jobs/Ookla/BenchmarkSpeedtestJob.php | 8 ++++----
app/Listeners/ProcessUnhealthySpeedtest.php | 4 ++--
app/Listeners/UserNotificationSubscriber.php | 6 +++---
5 files changed, 11 insertions(+), 11 deletions(-)
rename app/Events/{SpeedtestBenchmarkFailed.php => SpeedtestBenchmarkHealthy.php} (90%)
rename app/Events/{SpeedtestBenchmarkPassed.php => SpeedtestBenchmarkUnhealthy.php} (90%)
diff --git a/app/Events/SpeedtestBenchmarkFailed.php b/app/Events/SpeedtestBenchmarkHealthy.php
similarity index 90%
rename from app/Events/SpeedtestBenchmarkFailed.php
rename to app/Events/SpeedtestBenchmarkHealthy.php
index b00175b13..2838d442b 100644
--- a/app/Events/SpeedtestBenchmarkFailed.php
+++ b/app/Events/SpeedtestBenchmarkHealthy.php
@@ -6,7 +6,7 @@
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
-class SpeedtestBenchmarkFailed
+class SpeedtestBenchmarkHealthy
{
use Dispatchable, SerializesModels;
diff --git a/app/Events/SpeedtestBenchmarkPassed.php b/app/Events/SpeedtestBenchmarkUnhealthy.php
similarity index 90%
rename from app/Events/SpeedtestBenchmarkPassed.php
rename to app/Events/SpeedtestBenchmarkUnhealthy.php
index ab8e9ae82..92b706e35 100644
--- a/app/Events/SpeedtestBenchmarkPassed.php
+++ b/app/Events/SpeedtestBenchmarkUnhealthy.php
@@ -6,7 +6,7 @@
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
-class SpeedtestBenchmarkPassed
+class SpeedtestBenchmarkUnhealthy
{
use Dispatchable, SerializesModels;
diff --git a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
index c1298a147..6f6f0751a 100644
--- a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
+++ b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
@@ -3,9 +3,9 @@
namespace App\Jobs\Ookla;
use App\Enums\ResultStatus;
-use App\Events\SpeedtestBenchmarkFailed;
+use App\Events\SpeedtestBenchmarkHealthy;
use App\Events\SpeedtestBenchmarking;
-use App\Events\SpeedtestBenchmarkPassed;
+use App\Events\SpeedtestBenchmarkUnhealthy;
use App\Helpers\Benchmark;
use App\Models\Result;
use App\Settings\ThresholdSettings;
@@ -70,8 +70,8 @@ public function handle(): void
]);
$this->healthy
- ? SpeedtestBenchmarkPassed::dispatch($this->result)
- : SpeedtestBenchmarkFailed::dispatch($this->result);
+ ? SpeedtestBenchmarkHealthy::dispatch($this->result)
+ : SpeedtestBenchmarkUnhealthy::dispatch($this->result);
}
private function benchmark(Result $result, ThresholdSettings $settings): array
diff --git a/app/Listeners/ProcessUnhealthySpeedtest.php b/app/Listeners/ProcessUnhealthySpeedtest.php
index f2e0d419c..288f60739 100644
--- a/app/Listeners/ProcessUnhealthySpeedtest.php
+++ b/app/Listeners/ProcessUnhealthySpeedtest.php
@@ -2,7 +2,7 @@
namespace App\Listeners;
-use App\Events\SpeedtestBenchmarkFailed;
+use App\Events\SpeedtestBenchmarkUnhealthy;
use App\Helpers\Number;
use App\Mail\UnhealthySpeedtestMail;
use App\Models\Result;
@@ -29,7 +29,7 @@ public function __construct(
/**
* Handle the event.
*/
- public function handle(SpeedtestBenchmarkFailed $event): void
+ public function handle(SpeedtestBenchmarkUnhealthy $event): void
{
$result = $event->result;
diff --git a/app/Listeners/UserNotificationSubscriber.php b/app/Listeners/UserNotificationSubscriber.php
index 4ff7dc7c8..aefa0eaae 100644
--- a/app/Listeners/UserNotificationSubscriber.php
+++ b/app/Listeners/UserNotificationSubscriber.php
@@ -2,7 +2,7 @@
namespace App\Listeners;
-use App\Events\SpeedtestBenchmarkFailed;
+use App\Events\SpeedtestBenchmarkUnhealthy;
use App\Events\SpeedtestCompleted;
use App\Events\SpeedtestFailed;
use Filament\Actions\Action;
@@ -38,7 +38,7 @@ public function handleCompleted(SpeedtestCompleted $event): void
/**
* Handle the event.
*/
- public function handleBenchmarkFailed(SpeedtestBenchmarkFailed $event): void
+ public function handleBenchmarkFailed(SpeedtestBenchmarkUnhealthy $event): void
{
$result = $event->result;
@@ -97,7 +97,7 @@ public function subscribe(Dispatcher $events): array
{
return [
SpeedtestCompleted::class => 'handleCompleted',
- SpeedtestBenchmarkFailed::class => 'handleBenchmarkFailed',
+ SpeedtestBenchmarkUnhealthy::class => 'handleBenchmarkFailed',
SpeedtestFailed::class => 'handleFailed',
];
}
From 190b7ef486885ee6efba09e725bbbd6da3e12077 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 2 Jan 2026 16:02:43 -0500
Subject: [PATCH 22/34] gh actions: bump actions/create-github-app-token from 1
to 2 (#2621)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/cd.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 73817d100..b383b2c34 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Generate GitHub App token
id: generate_token
- uses: actions/create-github-app-token@v1
+ uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
From 1ea93d2b3b3ce3fe0faa1f1f74e24022319fd1b0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 2 Jan 2026 16:03:51 -0500
Subject: [PATCH 23/34] gh actions: bump actions/cache from 4 to 5 (#2622)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Justesen
---
.github/workflows/ci.yml | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index abe74743b..1e6db763d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,7 +22,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -57,7 +57,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -65,7 +65,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -115,7 +115,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -123,7 +123,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -173,7 +173,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -181,7 +181,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -231,7 +231,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -239,7 +239,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -289,7 +289,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -297,7 +297,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -347,7 +347,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -355,7 +355,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -405,7 +405,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -413,7 +413,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
@@ -454,7 +454,7 @@ jobs:
php-version: '8.4'
- name: Cache Composer dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
@@ -462,7 +462,7 @@ jobs:
composer-${{ runner.os }}-
- name: Cache NPM dependencies
- uses: actions/cache@v4
+ uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
From f9be7c6cc2afab778bb5813d9de728f13d363d23 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 2 Jan 2026 16:05:14 -0500
Subject: [PATCH 24/34] gh actions: bump peter-evans/repository-dispatch from 3
to 4 (#2623)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Justesen
---
.github/workflows/cd.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index b383b2c34..f6e40687f 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -19,7 +19,7 @@ jobs:
repositories: docker-speedtest-tracker
- name: Trigger docker-speedtest-tracker build
- uses: peter-evans/repository-dispatch@v3
+ uses: peter-evans/repository-dispatch@v4
with:
token: ${{ steps.generate_token.outputs.token }}
repository: alexjustesen/docker-speedtest-tracker
From 0e405e3f63d62b3fc936c847fa820d51c332c49b Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Fri, 2 Jan 2026 22:07:57 +0100
Subject: [PATCH 25/34] chore: update bug report template with support
guidelines (#2591)
Co-authored-by: Alex Justesen
---
.github/ISSUE_TEMPLATE/bug_report.yml | 79 ++++++++++++++++------
.github/ISSUE_TEMPLATE/feature_request.yml | 37 +++++++---
2 files changed, 84 insertions(+), 32 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index cf8b5abcb..ab4407dcc 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -2,67 +2,100 @@ name: Bug Report
description: Use this template to report a bug or issue.
title: "[Question] "
labels: ["question", "needs review"]
-body:
- - type: markdown
- attributes:
- value: |
- Thanks for taking the time to report this issue! We appreciate your help in improving the project. If this report is confirmed as a bug, we’ll update its type accordingly.
- Please note:
- - For **feature requests or changes**, use the [feature request form](https://github.com/alexjustesen/speedtest-tracker/issues/new?template=feature_request.yml).
- - For **general questions**, **setup or configuration help**, or if you’re not sure this is a bug, please use **[GitHub Discussions](https://github.com/alexjustesen/speedtest-tracker/discussions)** instead.
- - Any isseus with translations should be reported/solved within the [crowdin project](https://crowdin.com/project/speedtest-tracker).
+body:
- type: checkboxes
+ id: terms
attributes:
- label: Pre-work
+ label: Welcome!
description: |
- Before opening an issue make sure you've checked the resources below first, any issues that could have been solved by reading the docs or existing issues will be closed.
+ The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please use the **[GitHub Discussions](https://github.com/alexjustesen/speedtest-tracker/discussions)** instead
+
+ Please note:
+ - For translation-related issues or requests, please use the [Crowdin project](https://crowdin.com/project/speedtest-tracker).
+ - Any issues that can be resolved by consulting the documentation or by reviewing existing open or closed issues will be closed.
+ - We only support installations that follow the methods described in the documentation. Installations using third-party or undocumented methods are not supported by the project.
+
options:
- - label: I have read the [docs](https://docs.speedtest-tracker.dev).
+ - label: I have read the [documentation](https://docs.speedtest-tracker.dev) and my problem was not listed in the help section.
+ required: true
+ - label: I have searched open and closed issues and my problem was not mentioned before.
required: true
- - label: I have searched open and closed issues.
+ - label: I have verified I am using the latest version available. You can check the latest release [here](https://github.com/alexjustesen/speedtest-tracker/releases).
required: true
- label: I agree to follow this project's [Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md).
required: true
+
- type: textarea
id: description
attributes:
- label: Description
- description: Explain the issue you experienced, please be clear and concise.
- placeholder: I went to the coffee pot and it was empty.
+ label: What did you do?
+ description: |
+ How to write a good bug report?
+
+ - Respect the issue template as much as possible.
+ - The title should be short and descriptive.
+ - Explain the conditions which led you to report this issue: the context.
+ - The context should lead to something, a problem that you’re facing.
+ - Remain clear and concise.
+ - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
validations:
required: true
+
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
- description: In a perfect world, what should have happened?
+ description: |
+ In a perfect world, what should have happened?
+ **Important:** Be specific. Vague descriptions like "it should work" are not helpful.
placeholder: When I got to the coffee pot, it should have been full.
validations:
required: true
+
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
- description: Describe how to reproduce the issue in repeatable steps.
+ description: |
+ Provide detailed, numbered steps that someone else can follow to reproduce the issue.
+ **Important:** Vague descriptions like "it doesn't work" or "it's broken" will result in the issue being closed.
+ Include specific actions, URLs, button clicks, and any relevant data or configuration.
placeholder: |
1. Go to the coffee pot.
2. Make more coffee.
3. Pour it into a cup.
+ 4. Observe that the cup is empty instead of full.
validations:
required: true
+
- type: dropdown
id: deployment-environment
attributes:
label: Deployment Environment
- description: How did you deploy the application?
+ description: How did you deploy the application? Only supported deployment methods are listed.
options:
- Docker Compose
- Docker Run
- - Other
default: 0
validations:
required: true
+
+ - type: textarea
+ id: environment-configuration
+ attributes:
+ label: What is your environment & configuration?
+ description: Please add your docker compose file or docker run command used to deploy the application.
+ placeholder: Add information here.
+ value: |
+ ```yaml
+ # (paste your configuration here)
+ ```
+
+ Add more configuration information here.
+ validations:
+ required: true
+
- type: textarea
id: application-information
attributes:
@@ -71,6 +104,7 @@ body:
render: json
validations:
required: true
+
- type: input
id: browsers
attributes:
@@ -78,9 +112,12 @@ body:
placeholder: Chrome, Firefox, Safari, etc.
validations:
required: true
+
- type: textarea
id: logs
attributes:
label: Logs
- description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. If you are unsure which logs to include, include all logs. You can get the logs by running `docker logs `.
render: shell
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index aa13ebc08..bdd144689 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -2,22 +2,28 @@ name: Feature Request
description: Use this template for requesting a new feature or change.
title: "[Feature] "
labels: ["feature", "needs review"]
+
body:
- - type: markdown
- attributes:
- value: |
- You should only use this form to request a change or new feature, to report a bug or issue use the [bug report form](https://github.com/alexjustesen/speedtest-tracker).
- Any reqeusts for new translations should be reqeusted within the [crowdin project](https://crowdin.com/project/speedtest-tracker).
- type: checkboxes
attributes:
- label: Pre-work
+ label: Welcome!
+ description: |
+ The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please use the **[GitHub Discussions](https://github.com/alexjustesen/speedtest-tracker/discussions)** instead
+
+ Please note:
+ - For **Bug reports**, use the [Bug Form](https://github.com/alexjustesen/speedtest-tracker/issues/new?template=bug_report.yml).
+ - Any requests for new translations should be requested within the [crowdin project](https://crowdin.com/project/speedtest-tracker).
+
options:
- - label: I have searched open and closed feature request to make sure this or similar feature request does not already exist.
+ - label: I have searched open and closed feature requests to make sure this or similar feature request does not already exist.
+ required: true
+ - label: I have reviewed the [Milestones](https://github.com/alexjustesen/speedtest-tracker/milestones) to ensure that this feature request, or a similar one, has not already been proposed.
required: true
- - label: I have reviewed the [milestones](https://github.com/alexjustesen/speedtest-tracker/milestones) to ensure that this feature request, or a similar one, has not already been proposed.
+ - label: This is a feature request, not a bug report or support question.
required: true
- - label: I agree to follow this project's [Code of Conduct]().
+ - label: I agree to follow this project's [Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md).
required: true
+
- type: dropdown
id: idea-section
attributes:
@@ -28,14 +34,23 @@ body:
- Notifications
- Speedtest
- Web UI/UX
+ - Other
default: 0
validations:
required: true
+
- type: textarea
id: description
attributes:
label: Description
- description: Describe the solution or feature you'd like, you should also mention if this solves a problem.
- placeholder: Be sure to keep it clear and concise.
+ description: |
+ Describe the solution or feature you'd like. Explain what problem this solves or what value it adds.
+ **Important:** Be specific and detailed. Vague requests like "make it better" will be closed.
+ placeholder: |
+ Example:
+ - What is the feature?
+ - What problem does it solve?
+ - How should it work?
+ - Why would this be valuable?
validations:
required: true
From 07aaf84a4bcd0a43cc43818bf73175ae96ce6476 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Sat, 3 Jan 2026 14:09:51 +0100
Subject: [PATCH 26/34] Fix: Apprise test button shown without saving (#2627)
---
app/Filament/Pages/Settings/Notification.php | 7 ++++++-
lang/en/settings/notifications.php | 1 +
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/app/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php
index 7e1c1a58a..27d26bc39 100755
--- a/app/Filament/Pages/Settings/Notification.php
+++ b/app/Filament/Pages/Settings/Notification.php
@@ -257,6 +257,7 @@ public function form(Schema $schema): Schema
]),
Repeater::make('apprise_channel_urls')
->label(__('settings/notifications.apprise_channels'))
+ ->helperText(__('settings/notifications.apprise_save_to_test'))
->schema([
TextInput::make('channel_url')
->label(__('settings/notifications.apprise_channel_url'))
@@ -274,7 +275,11 @@ public function form(Schema $schema): Schema
->action(fn (Get $get) => SendAppriseTestNotification::run(
channel_urls: $get('apprise_channel_urls'),
))
- ->hidden(fn (Get $get) => ! count($get('apprise_channel_urls'))),
+ ->hidden(function () {
+ $settings = app(NotificationSettings::class);
+
+ return empty($settings->apprise_server_url) || ! count($settings->apprise_channel_urls ?? []);
+ }),
]),
]),
]),
diff --git a/lang/en/settings/notifications.php b/lang/en/settings/notifications.php
index 788f31d91..87c82e7da 100644
--- a/lang/en/settings/notifications.php
+++ b/lang/en/settings/notifications.php
@@ -25,6 +25,7 @@
'apprise_channel_url' => 'Service URL',
'apprise_hint_description' => 'Apprise allows you to send notifications to 90+ services. You need to run an Apprise server and configure service URLs below.',
'apprise_channel_url_helper' => 'Use Apprise URL format. Examples: discord://WebhookID/Token, slack://TokenA/TokenB/TokenC',
+ 'apprise_save_to_test' => 'Save your settings to test the notification.',
'test_apprise_channel' => 'Test Apprise',
'apprise_channel_url_validation_error' => 'Invalid Apprise URL. Must use Apprise format (e.g., discord://, slack://), not http:// or https://. See the Apprise documentation for more information',
From d0377e15a711ec68b9e8a2a985959280de987b9f Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Sat, 3 Jan 2026 14:14:31 +0100
Subject: [PATCH 27/34] Fix: Skip SkipSpeedtestJob when env var is empty
(#2626)
Co-authored-by: Alex Justesen
---
app/Jobs/Ookla/SkipSpeedtestJob.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/Jobs/Ookla/SkipSpeedtestJob.php b/app/Jobs/Ookla/SkipSpeedtestJob.php
index 948fe792d..773d4a793 100644
--- a/app/Jobs/Ookla/SkipSpeedtestJob.php
+++ b/app/Jobs/Ookla/SkipSpeedtestJob.php
@@ -40,9 +40,9 @@ public function middleware(): array
public function handle(): void
{
/**
- * Only skip IPs for scheduled tests.
+ * Skip if test is not scheduled or no IPs are configured to skip.
*/
- if ($this->result->scheduled === false) {
+ if ($this->result->scheduled === false || empty(config('speedtest.preflight.skip_ips'))) {
return;
}
From 5bb28c23a9ed7c6deb2faefb4ae39501bfbb77f2 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Sun, 4 Jan 2026 15:31:00 +0100
Subject: [PATCH 28/34] Fix: Increase timeout for Apprise notifications (#2628)
Co-authored-by: Alex Justesen
---
app/Notifications/AppriseChannel.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Notifications/AppriseChannel.php b/app/Notifications/AppriseChannel.php
index 0e9ed1087..c6fe6a1c3 100644
--- a/app/Notifications/AppriseChannel.php
+++ b/app/Notifications/AppriseChannel.php
@@ -33,7 +33,7 @@ public function send(object $notifiable, Notification $notification): void
}
try {
- $request = Http::timeout(5)
+ $request = Http::timeout(30)
->withHeaders([
'Content-Type' => 'application/json',
]);
From 82fdd6fa79cfb773050b17470b0bc586d03de307 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Sun, 4 Jan 2026 15:32:11 +0100
Subject: [PATCH 29/34] Chore: clean up ping logs (#2629)
Co-authored-by: Alex Justesen
---
app/Actions/PingHostname.php | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/app/Actions/PingHostname.php b/app/Actions/PingHostname.php
index 5bbf15ddf..04884fe31 100644
--- a/app/Actions/PingHostname.php
+++ b/app/Actions/PingHostname.php
@@ -23,9 +23,12 @@ public function handle(?string $hostname = null, int $count = 1): PingResult
count: $count,
))->run();
- Log::info('Pinged hostname', [
+ $data = $ping->toArray();
+ unset($data['raw_output'], $data['lines']);
+
+ Log::debug('Pinged hostname', [
'host' => $hostname,
- 'data' => $ping->toArray(),
+ 'data' => $data,
]);
return $ping;
From f1480e4c0529ecd5bd4098b59e795ade1d366234 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Thu, 8 Jan 2026 23:20:46 +0100
Subject: [PATCH 30/34] chore: add missing translation strings (#2639)
---
lang/en/general.php | 4 ++++
resources/views/dashboard.blade.php | 2 +-
resources/views/livewire/latest-result-stats.blade.php | 2 +-
resources/views/livewire/next-speedtest-banner.blade.php | 2 +-
resources/views/livewire/platform-stats.blade.php | 6 +++---
5 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/lang/en/general.php b/lang/en/general.php
index 9b8aa7d95..2d39844c2 100644
--- a/lang/en/general.php
+++ b/lang/en/general.php
@@ -73,6 +73,10 @@
'healthy' => 'Healthy',
'not_measured' => 'Not measured',
'unhealthy' => 'Unhealthy',
+ 'last_results' => 'Last results',
+ 'total_failed' => 'Total failed tests',
+ 'total_complted' => 'Total completed tests',
+ 'total' => 'Total',
// Units
'ms' => 'ms',
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index 967996d47..c66c27c1c 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -9,7 +9,7 @@
- Metrics
+ {{ __('general.metrics') }}
@livewire(\App\Filament\Widgets\RecentDownloadChartWidget::class)
diff --git a/resources/views/livewire/latest-result-stats.blade.php b/resources/views/livewire/latest-result-stats.blade.php
index c1c69eeec..f6997016a 100644
--- a/resources/views/livewire/latest-result-stats.blade.php
+++ b/resources/views/livewire/latest-result-stats.blade.php
@@ -6,7 +6,7 @@
- Latest result
+ {{ __('general.last_results') }}
{{ $this->latestResult->created_at->timezone(config('app.display_timezone'))->format(config('app.datetime_format')) }}
diff --git a/resources/views/livewire/next-speedtest-banner.blade.php b/resources/views/livewire/next-speedtest-banner.blade.php
index e66d3c1ee..f7f9211c0 100644
--- a/resources/views/livewire/next-speedtest-banner.blade.php
+++ b/resources/views/livewire/next-speedtest-banner.blade.php
@@ -8,7 +8,7 @@
- Next scheduled test at {{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format(config('app.datetime_format')) }}.
+ {{ __('dashboard.next_speedtest_at') }} {{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format(config('app.datetime_format')) }}.
diff --git a/resources/views/livewire/platform-stats.blade.php b/resources/views/livewire/platform-stats.blade.php
index f8f149e6e..00caf7a17 100644
--- a/resources/views/livewire/platform-stats.blade.php
+++ b/resources/views/livewire/platform-stats.blade.php
@@ -25,7 +25,7 @@
- Total tests
+ {{ __('general.total') }}
{{ $this->platformStats['total'] }}
@@ -33,7 +33,7 @@
- Total completed tests
+ {{ __('general.total_complted') }}
{{ $this->platformStats['completed'] }}
@@ -41,7 +41,7 @@
- Total failed tests
+ {{ __('general.total_failed') }}
{{ $this->platformStats['failed'] }}
From 0e104db343df968a303556e1e60002ed97bcc870 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Thu, 8 Jan 2026 23:23:11 +0100
Subject: [PATCH 31/34] Chore: Add helpertext for notification triggers (#2640)
Co-authored-by: Alex Justesen
---
app/Filament/Pages/Settings/Notification.php | 23 ++++++++++++--------
lang/en/settings/notifications.php | 4 +++-
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/app/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php
index 27d26bc39..c01ee4e1f 100755
--- a/app/Filament/Pages/Settings/Notification.php
+++ b/app/Filament/Pages/Settings/Notification.php
@@ -86,10 +86,11 @@ public function form(Schema $schema): Schema
->columns(1)
->schema([
Checkbox::make('database_on_speedtest_run')
- ->label(__('settings/notifications.notify_on_every_speedtest_run')),
-
+ ->label(__('settings/notifications.notify_on_every_speedtest_run'))
+ ->helpertext(__('settings/notifications.notify_on_every_speedtest_run_helper')),
Checkbox::make('database_on_threshold_failure')
- ->label(__('settings/notifications.notify_on_threshold_failures')),
+ ->label(__('settings/notifications.notify_on_threshold_failures'))
+ ->helpertext(__('settings/notifications.notify_on_threshold_failures_helper')),
]),
Actions::make([
@@ -118,10 +119,11 @@ public function form(Schema $schema): Schema
->columns(1)
->schema([
Checkbox::make('mail_on_speedtest_run')
- ->label(__('settings/notifications.notify_on_every_speedtest_run')),
-
+ ->label(__('settings/notifications.notify_on_every_speedtest_run'))
+ ->helpertext(__('settings/notifications.notify_on_every_speedtest_run_helper')),
Checkbox::make('mail_on_threshold_failure')
- ->label(__('settings/notifications.notify_on_threshold_failures')),
+ ->label(__('settings/notifications.notify_on_threshold_failures'))
+ ->helpertext(__('settings/notifications.notify_on_threshold_failures_helper')),
]),
Repeater::make('mail_recipients')
@@ -176,10 +178,11 @@ public function form(Schema $schema): Schema
->columns(1)
->schema([
Checkbox::make('webhook_on_speedtest_run')
- ->label(__('settings/notifications.notify_on_every_speedtest_run')),
-
+ ->label(__('settings/notifications.notify_on_every_speedtest_run'))
+ ->helpertext(__('settings/notifications.notify_on_every_speedtest_run_helper')),
Checkbox::make('webhook_on_threshold_failure')
- ->label(__('settings/notifications.notify_on_threshold_failures')),
+ ->label(__('settings/notifications.notify_on_threshold_failures'))
+ ->helpertext(__('settings/notifications.notify_on_threshold_failures_helper')),
]),
Repeater::make('webhook_urls')
@@ -250,9 +253,11 @@ public function form(Schema $schema): Schema
->schema([
Checkbox::make('apprise_on_speedtest_run')
->label(__('settings/notifications.notify_on_every_speedtest_run'))
+ ->helpertext(__('settings/notifications.notify_on_every_speedtest_run_helper'))
->columnSpanFull(),
Checkbox::make('apprise_on_threshold_failure')
->label(__('settings/notifications.notify_on_threshold_failures'))
+ ->helpertext(__('settings/notifications.notify_on_threshold_failures_helper'))
->columnSpanFull(),
]),
Repeater::make('apprise_channel_urls')
diff --git a/lang/en/settings/notifications.php b/lang/en/settings/notifications.php
index 87c82e7da..8c3145532 100644
--- a/lang/en/settings/notifications.php
+++ b/lang/en/settings/notifications.php
@@ -36,8 +36,10 @@
'webhook_hint_description' => 'These are generic webhooks. For payload examples and implementation details, view the documentation. For services like Discord, Ntfy etc please use Apprise.',
// Common notification messages
- 'notify_on_every_speedtest_run' => 'Notify on every scheduled speedtest run',
+ 'notify_on_every_speedtest_run' => 'Notify on every completed scheduled speedtest run',
+ 'notify_on_every_speedtest_run_helper' => 'This will send a notification for every completed scheduled speedtest run, only for healthy or unbenchmarked tests',
'notify_on_threshold_failures' => 'Notify on threshold failures for scheduled speedtests',
+ 'notify_on_threshold_failures_helper' => 'This will send a notification when a scheduled speedtest fails any configured thresholds',
// Test notification messages
'test_notifications' => [
From cc0f0d36e3ce64eadce286c909f5a76353b77298 Mon Sep 17 00:00:00 2001
From: Alex Justesen
Date: Thu, 8 Jan 2026 17:34:20 -0500
Subject: [PATCH 32/34] Release v1.13.5 (#2641)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
---
config/speedtest.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/speedtest.php b/config/speedtest.php
index 79c2e84b4..4226f5a0a 100644
--- a/config/speedtest.php
+++ b/config/speedtest.php
@@ -6,9 +6,9 @@
/**
* General settings.
*/
- 'build_date' => Carbon::parse('2025-12-26'),
+ 'build_date' => Carbon::parse('2026-01-08'),
- 'build_version' => 'v1.13.4',
+ 'build_version' => 'v1.13.5',
'content_width' => env('CONTENT_WIDTH', '7xl'),
From 7d74172de9f91549af0bca02fcce9ffc59683f9e Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Mon, 12 Jan 2026 18:33:03 +0100
Subject: [PATCH 33/34] chore: improve dev container (#2648)
---
.devcontainer/devcontainer.json | 6 +++---
.env.example | 9 +++++++++
compose.yaml | 2 +-
3 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 7ec51536b..04653336d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,8 +1,8 @@
// https://aka.ms/devcontainer.json
{
- "name": "Existing Docker Compose (Extend)",
+ "name": "Speedtest Tracker Dev Environment",
"dockerComposeFile": [
- "../docker-compose.yml"
+ "../compose.yaml"
],
"service": "laravel.test",
"workspaceFolder": "/var/www/html",
@@ -20,7 +20,7 @@
}
},
"remoteUser": "sail",
- "postCreateCommand": "chown -R 1000:1000 /var/www/html 2>/dev/null || true"
+ "postCreateCommand": "composer install && npm install && npm run build && touch database/database.sqlite && php artisan migrate:fresh --force"
// "forwardPorts": [],
// "runServices": [],
// "shutdownAction": "none",
diff --git a/.env.example b/.env.example
index 1a762a1ce..b09ac0014 100644
--- a/.env.example
+++ b/.env.example
@@ -18,6 +18,11 @@ LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
+#DB_HOST=
+#DB_PORT=
+#DB_DATABASE=
+#DB_USERNAME=
+#DB_PASSWORD=
SESSION_DRIVER=cookie
SESSION_LIFETIME=10080
@@ -42,3 +47,7 @@ MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="Speedtest Tracker"
VITE_APP_NAME="${APP_NAME}"
+
+# For the Dev Container
+# WWWUSER=1000
+# WWWGROUP=1000
diff --git a/compose.yaml b/compose.yaml
index acd813d9b..a4ccb9e57 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -25,7 +25,7 @@ services:
- mailpit
- apprise
pgsql:
- image: 'postgres:17-alpine'
+ image: 'postgres:18-alpine'
ports:
- '${FORWARD_DB_PORT:-5432}:5432'
environment:
From b7846b0b1d08d7c24073c91ed96bd60267eba140 Mon Sep 17 00:00:00 2001
From: Sven van Ginkel
Date: Wed, 21 Jan 2026 15:05:38 +0100
Subject: [PATCH 34/34] chore: update webhook payload (#2636)
---
.../Notifications/SendWebhookTestNotification.php | 2 +-
app/Jobs/Ookla/BenchmarkSpeedtestJob.php | 10 +++++++---
app/Listeners/ProcessCompletedSpeedtest.php | 7 ++++---
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/app/Actions/Notifications/SendWebhookTestNotification.php b/app/Actions/Notifications/SendWebhookTestNotification.php
index 9ae6d922d..45aa2abc5 100644
--- a/app/Actions/Notifications/SendWebhookTestNotification.php
+++ b/app/Actions/Notifications/SendWebhookTestNotification.php
@@ -37,7 +37,7 @@ public function handle(array $webhooks)
'ping' => $fakeResult->ping,
'download' => $fakeResult->download,
'upload' => $fakeResult->upload,
- 'packetLoss' => $fakeResult->data['packetLoss'],
+ 'packet_loss' => $fakeResult->data['packetLoss'],
'speedtest_url' => $fakeResult->data['result']['url'],
'url' => url('/admin/results'),
])
diff --git a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
index 6f6f0751a..683fe395b 100644
--- a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
+++ b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
@@ -7,6 +7,7 @@
use App\Events\SpeedtestBenchmarking;
use App\Events\SpeedtestBenchmarkUnhealthy;
use App\Helpers\Benchmark;
+use App\Helpers\Number;
use App\Models\Result;
use App\Settings\ThresholdSettings;
use Illuminate\Bus\Batchable;
@@ -83,7 +84,8 @@ private function benchmark(Result $result, ThresholdSettings $settings): array
'bar' => 'min',
'passed' => Benchmark::bitrate($result->download, ['value' => $settings->absolute_download, 'unit' => 'mbps']),
'type' => 'absolute',
- 'value' => $settings->absolute_download,
+ 'test_value' => Number::bitsToMagnitude(bits: $result->download_bits, precision: 0, magnitude: 'mbit'),
+ 'benchmark_value' => $settings->absolute_download,
'unit' => 'mbps',
]);
@@ -97,7 +99,8 @@ private function benchmark(Result $result, ThresholdSettings $settings): array
'bar' => 'min',
'passed' => filter_var(Benchmark::bitrate($result->upload, ['value' => $settings->absolute_upload, 'unit' => 'mbps']), FILTER_VALIDATE_BOOLEAN),
'type' => 'absolute',
- 'value' => $settings->absolute_upload,
+ 'test_value' => Number::bitsToMagnitude(bits: $result->upload_bits, precision: 0, magnitude: 'mbit'),
+ 'benchmark_value' => $settings->absolute_upload,
'unit' => 'mbps',
]);
@@ -111,7 +114,8 @@ private function benchmark(Result $result, ThresholdSettings $settings): array
'bar' => 'max',
'passed' => Benchmark::ping($result->ping, ['value' => $settings->absolute_ping]),
'type' => 'absolute',
- 'value' => $settings->absolute_ping,
+ 'test_value' => round($result->ping),
+ 'benchmark_value' => $settings->absolute_ping,
'unit' => 'ms',
]);
diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php
index ae5d544e1..87b7b7794 100644
--- a/app/Listeners/ProcessCompletedSpeedtest.php
+++ b/app/Listeners/ProcessCompletedSpeedtest.php
@@ -162,10 +162,11 @@ private function notifyWebhookChannels(Result $result): void
'site_name' => config('app.name'),
'server_name' => Arr::get($result->data, 'server.name'),
'server_id' => Arr::get($result->data, 'server.id'),
+ 'status' => $result->status,
'isp' => Arr::get($result->data, 'isp'),
- 'ping' => $result->ping,
- 'download' => $result->downloadBits,
- 'upload' => $result->uploadBits,
+ 'ping' => round($result->ping),
+ 'download' => Number::bitsToMagnitude(bits: $result->download_bits, precision: 0, magnitude: 'mbit'),
+ 'upload' => Number::bitsToMagnitude(bits: $result->upload_bits, precision: 0, magnitude: 'mbit'),
'packet_loss' => Arr::get($result->data, 'packetLoss'),
'speedtest_url' => Arr::get($result->data, 'result.url'),
'url' => url('/admin/results'),