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/.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
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 73817d100..f6e40687f 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 }}
@@ -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
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') }}
diff --git a/app/Actions/CheckInternetConnection.php b/app/Actions/CheckInternetConnection.php
deleted file mode 100644
index 22e26f78d..000000000
--- a/app/Actions/CheckInternetConnection.php
+++ /dev/null
@@ -1,33 +0,0 @@
-timeout(5)
- ->get(config('speedtest.checkinternet_url'));
-
- if (! $response->ok()) {
- return false;
- }
-
- return Str::trim($response->body());
- } catch (Throwable $e) {
- Log::error('Failed to connect to the internet.', [$e->getMessage()]);
-
- return false;
- }
- }
-}
diff --git a/app/Actions/GetExternalIpAddress.php b/app/Actions/GetExternalIpAddress.php
index 6a4d0b114..d47cc0a6f 100644
--- a/app/Actions/GetExternalIpAddress.php
+++ b/app/Actions/GetExternalIpAddress.php
@@ -12,18 +12,28 @@ class GetExternalIpAddress
{
use AsAction;
- public function handle(): bool|string
+ public function handle(?string $url = null): array
{
+ $url = $url ?? config('speedtest.preflight.external_ip_url');
+
try {
$response = Http::retry(3, 100)
->timeout(5)
- ->get(url: 'https://icanhazip.com/');
+ ->get(url: $url);
} catch (Throwable $e) {
- Log::error('Failed to fetch external IP address.', [$e->getMessage()]);
+ $message = sprintf('Failed to fetch external IP address from "%s". See the logs for more details.', $url);
+
+ Log::error($message, [$e->getMessage()]);
- return false;
+ return [
+ 'ok' => false,
+ 'body' => $message,
+ ];
}
- return Str::trim($response->body());
+ return [
+ 'ok' => $response->ok(),
+ 'body' => Str::of($response->body())->trim()->toString(),
+ ];
}
}
diff --git a/app/Actions/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/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/Actions/PingHostname.php b/app/Actions/PingHostname.php
new file mode 100644
index 000000000..04884fe31
--- /dev/null
+++ b/app/Actions/PingHostname.php
@@ -0,0 +1,36 @@
+run();
+
+ $data = $ping->toArray();
+ unset($data['raw_output'], $data['lines']);
+
+ Log::debug('Pinged hostname', [
+ 'host' => $hostname,
+ 'data' => $data,
+ ]);
+
+ return $ping;
+ }
+}
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/Filament/Pages/Settings/Notification.php b/app/Filament/Pages/Settings/Notification.php
index 9a5decb00..c01ee4e1f 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;
@@ -85,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([
@@ -117,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')
@@ -175,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')
@@ -233,10 +237,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'))
@@ -247,13 +253,16 @@ 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')
->label(__('settings/notifications.apprise_channels'))
+ ->helperText(__('settings/notifications.apprise_save_to_test'))
->schema([
TextInput::make('channel_url')
->label(__('settings/notifications.apprise_channel_url'))
@@ -271,7 +280,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/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)
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/Jobs/CheckForInternetConnectionJob.php b/app/Jobs/CheckForInternetConnectionJob.php
index 37c5e6f04..c0f39a61f 100644
--- a/app/Jobs/CheckForInternetConnectionJob.php
+++ b/app/Jobs/CheckForInternetConnectionJob.php
@@ -2,7 +2,7 @@
namespace App\Jobs;
-use App\Actions\CheckInternetConnection;
+use App\Actions\PingHostname;
use App\Enums\ResultStatus;
use App\Events\SpeedtestChecking;
use App\Events\SpeedtestFailed;
@@ -44,14 +44,18 @@ public function handle(): void
SpeedtestChecking::dispatch($this->result);
- if (CheckInternetConnection::run() !== false) {
+ $ping = PingHostname::run();
+
+ if ($ping->isSuccess()) {
return;
}
+ $message = sprintf('Failed to connected to hostname "%s". Error received "%s".', $ping->getHost(), $ping->error()?->value);
+
$this->result->update([
'data->type' => 'log',
'data->level' => 'error',
- 'data->message' => 'Failed to connect to the internet.',
+ 'data->message' => $message,
'status' => ResultStatus::Failed,
]);
diff --git a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
index c1298a147..683fe395b 100644
--- a/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
+++ b/app/Jobs/Ookla/BenchmarkSpeedtestJob.php
@@ -3,10 +3,11 @@
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\Helpers\Number;
use App\Models\Result;
use App\Settings\ThresholdSettings;
use Illuminate\Bus\Batchable;
@@ -70,8 +71,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
@@ -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/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,
diff --git a/app/Jobs/Ookla/SkipSpeedtestJob.php b/app/Jobs/Ookla/SkipSpeedtestJob.php
index 13c444133..773d4a793 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;
@@ -39,16 +40,31 @@ 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;
}
$externalIp = GetExternalIpAddress::run();
+ if ($externalIp['ok'] === false) {
+ $this->result->update([
+ 'data->type' => 'log',
+ 'data->level' => 'error',
+ 'data->message' => $externalIp['body'],
+ 'status' => ResultStatus::Failed,
+ ]);
+
+ SpeedtestFailed::dispatch($this->result);
+
+ $this->batch()->cancel();
+
+ return;
+ }
+
$shouldSkip = $this->shouldSkip(
- externalIp: $externalIp,
+ externalIp: $externalIp['body'],
);
if ($shouldSkip === false) {
@@ -76,11 +92,11 @@ private function shouldSkip(string $externalIp): bool|string
$skipIPs = array_filter(
array_map(
'trim',
- explode(',', config('speedtest.skip_ips')),
+ explode(',', config('speedtest.preflight.skip_ips')),
),
);
- if (count($skipIPs) < 1) {
+ if (empty($skipIPs)) {
return false;
}
diff --git a/app/Listeners/ProcessCompletedSpeedtest.php b/app/Listeners/ProcessCompletedSpeedtest.php
index 1b72f3053..87b7b7794 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;
@@ -197,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'),
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..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,15 +29,17 @@ public function __construct(
/**
* Handle the event.
*/
- public function handle(SpeedtestBenchmarkFailed $event): void
+ public function handle(SpeedtestBenchmarkUnhealthy $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..aefa0eaae
--- /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(SpeedtestBenchmarkUnhealthy $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',
+ SpeedtestBenchmarkUnhealthy::class => 'handleBenchmarkFailed',
+ SpeedtestFailed::class => 'handleFailed',
+ ];
+ }
+}
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/app/Livewire/Topbar/Actions.php b/app/Livewire/Topbar/Actions.php
index 95a8abcce..9077724c8 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')
@@ -65,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')
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,
+ );
+ }
}
diff --git a/app/Notifications/AppriseChannel.php b/app/Notifications/AppriseChannel.php
index 3cd2592a1..c6fe6a1c3 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');
@@ -31,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',
]);
@@ -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/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:
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 dfaf9ac76..4226f5a0a 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('2026-01-08'),
- 'build_version' => 'v1.12.3',
+ 'build_version' => 'v1.13.5',
'content_width' => env('CONTENT_WIDTH', '7xl'),
@@ -29,15 +29,17 @@
'interface' => env('SPEEDTEST_INTERFACE'),
- 'checkinternet_url' => env('SPEEDTEST_CHECKINTERNET_URL', 'https://icanhazip.com'),
+ 'preflight' => [
+ 'external_ip_url' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_EXTERNAL_IP_URL', 'https://icanhazip.com'),
+ 'internet_check_hostname' => env('SPEEDTEST_CHECKINTERNET_URL') ?? env('SPEEDTEST_INTERNET_CHECK_HOSTNAME', 'icanhazip.com'),
+ 'skip_ips' => env('SPEEDTEST_SKIP_IPS'),
+ ],
/**
* IP filtering settings.
*/
'allowed_ips' => env('ALLOWED_IPS'),
- 'skip_ips' => env('SPEEDTEST_SKIP_IPS', ''),
-
/**
* Threshold settings.
*/
diff --git a/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/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/lang/en/settings/notifications.php b/lang/en/settings/notifications.php
index 203590b23..8c3145532 100644
--- a/lang/en/settings/notifications.php
+++ b/lang/en/settings/notifications.php
@@ -19,23 +19,27 @@
'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',
+ 'apprise_save_to_test' => 'Save your settings to test the notification.',
'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',
+ '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' => [
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..7a37c850a 100644
--- a/lang/nl_NL/settings/notifications.php
+++ b/lang/nl_NL/settings/notifications.php
@@ -14,11 +14,25 @@
'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_server_url_helper' => 'De URL van uw Apprise Server. De URL moet eindigen op /notify',
+ 'apprise_verify_ssl' => 'Controleer SSL',
+ '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' => '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',
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',
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index e1b26abed..c66c27c1c 100644
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -1,17 +1,15 @@
-
+
- @auth
-
- @endauth
+
- Metrics
+ {{ __('general.metrics') }}
@livewire(\App\Filament\Widgets\RecentDownloadChartWidget::class)
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 42a849976..2d9d68211 100644
--- a/resources/views/filament/pages/dashboard.blade.php
+++ b/resources/views/filament/pages/dashboard.blade.php
@@ -1,5 +1,7 @@
+
+
@@ -11,6 +13,7 @@
class="col-span-1"
icon="tabler-book"
icon-size="md"
+ :compact="true"
>
{{ __('general.documentation') }}
@@ -35,6 +38,7 @@ class="col-span-1"
class="col-span-1"
icon="tabler-cash-banknote-heart"
icon-size="md"
+ :compact="true"
>
{{ __('general.donations') }}
@@ -59,6 +63,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/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/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
+
+
+ @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/livewire/latest-result-stats.blade.php b/resources/views/livewire/latest-result-stats.blade.php
index a3cc2b708..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')) }}
@@ -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/next-speedtest-banner.blade.php b/resources/views/livewire/next-speedtest-banner.blade.php
index ce65f81d7..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('F jS, Y, g:i a') }}.
+ {{ __('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 567c1f31a..00caf7a17 100644
--- a/resources/views/livewire/platform-stats.blade.php
+++ b/resources/views/livewire/platform-stats.blade.php
@@ -23,25 +23,25 @@
--}}
-
+
- Total tests
+ {{ __('general.total') }}
{{ $this->platformStats['total'] }}
-
+
- Total completed tests
+ {{ __('general.total_complted') }}
{{ $this->platformStats['completed'] }}
-
+
- Total failed tests
+ {{ __('general.total_failed') }}
{{ $this->platformStats['failed'] }}
diff --git a/resources/views/livewire/topbar/actions.blade.php b/resources/views/livewire/topbar/actions.blade.php
index b27fb1767..20db6f5ea 100644
--- a/resources/views/livewire/topbar/actions.blade.php
+++ b/resources/views/livewire/topbar/actions.blade.php
@@ -2,8 +2,9 @@
{{ $this->speedtestAction }}
- {{ $this->dashboardAction }}
-
+ @if ($showDashboard)
+ {{ $this->dashboardAction }}
+ @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.