Skip to content
2 changes: 2 additions & 0 deletions app/Filament/Resources/PingResults/PingResultResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class PingResultResource extends Resource

protected static string|\BackedEnum|null $navigationIcon = 'tabler-chart-line';

protected static string|\UnitEnum|null $navigationGroup = 'Monitor de Ping';

public static function getNavigationLabel(): string
{
return __('ping.ping_results');
Expand Down
3 changes: 3 additions & 0 deletions app/Filament/Resources/PingResults/Tables/PingResultTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public static function table(Table $table): Table
}),
])
->defaultSort('created_at', 'desc')
->persistFilters()
->persistSort()
->persistColumnVisibility()
->poll('60s');
}
}
7 changes: 7 additions & 0 deletions app/Filament/Resources/PingTargets/PingTargetResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class PingTargetResource extends Resource

protected static string|\BackedEnum|null $navigationIcon = 'tabler-broadcast';

protected static string|\UnitEnum|null $navigationGroup = 'Monitor de Ping';

public static function getNavigationLabel(): string
{
return __('ping.ping_targets');
Expand Down Expand Up @@ -51,4 +53,9 @@ public static function getPages(): array
'edit' => EditPingTarget::route('/{record}/edit'),
];
}

public static function getGloballySearchableAttributes(): array
{
return ['name', 'host'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public static function schema(): array
->label(__('ping.host'))
->placeholder(__('ping.host_placeholder'))
->required()
->maxLength(255),
->maxLength(255)
->rules(['regex:/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}|(\d{1,3}\.){3}\d{1,3}$/i'])
->validationMessages([
'regex' => 'The host must be a valid domain or IP address.',
]),

Select::make('interval_seconds')
->label(__('ping.interval_seconds'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public static function table(Table $table): Table
DeleteAction::make(),
]),
])
->defaultSort('id', 'desc');
->defaultSort('id', 'desc')
->persistFilters()
->persistSort()
->persistColumnVisibility();
}
}
7 changes: 7 additions & 0 deletions app/Filament/Resources/Results/ResultResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ResultResource extends Resource

protected static string|\BackedEnum|null $navigationIcon = 'tabler-table';

protected static \UnitEnum|string|null $navigationGroup = null;

public static function getNavigationLabel(): string
{
return __('results.title');
Expand Down Expand Up @@ -47,4 +49,9 @@ public static function getPages(): array
'index' => ListResults::route('/'),
];
}

public static function getGloballySearchableAttributes(): array
{
return ['id', 'data->interface->externalIp', 'data->server->name', 'data->server->id'];
}
}
30 changes: 15 additions & 15 deletions app/Filament/Resources/Results/Schemas/ResultForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
use App\Models\Result;
use Carbon\Carbon;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Illuminate\Support\HtmlString;
Expand Down Expand Up @@ -100,27 +100,27 @@ public static function schema(): array

// Right column: Server & Metadata
Section::make(__('results.server_&_metadata'))->schema([
TextEntry::make('service')
Placeholder::make('service')
->label(__('results.service'))
->state(fn (Result $result): string => $result->service->getLabel()),
TextEntry::make('server_name')
->content(fn (Result $result): string => $result->service->getLabel()),
Placeholder::make('server_name')
->label(__('results.server_name'))
->state(fn (Result $result): ?string => $result->server_name),
TextEntry::make('server_id')
->content(fn (Result $result): ?string => $result->server_name),
Placeholder::make('server_id')
->label(__('results.server_id'))
->state(fn (Result $result): ?string => $result->server_id),
TextEntry::make('isp')
->content(fn (Result $result): ?string => $result->server_id),
Placeholder::make('isp')
->label(__('results.isp'))
->state(fn (Result $result): ?string => $result->isp),
TextEntry::make('server_location')
->content(fn (Result $result): ?string => $result->isp),
Placeholder::make('server_location')
->label(__('results.server_location'))
->state(fn (Result $result): ?string => $result->server_location),
TextEntry::make('server_host')
->content(fn (Result $result): ?string => $result->server_location),
Placeholder::make('server_host')
->label(__('results.server_host'))
->state(fn (Result $result): ?string => $result->server_host),
TextEntry::make('comment')
->content(fn (Result $result): ?string => $result->server_host),
Placeholder::make('comment')
->label(__('general.comment'))
->state(fn (Result $result): ?string => $result->comments),
->content(fn (Result $result): ?string => $result->comments),
Checkbox::make('scheduled')
->label(__('results.scheduled')),
Checkbox::make('healthy')
Expand Down
3 changes: 3 additions & 0 deletions app/Filament/Resources/Results/Tables/ResultTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ public static function table(Table $table): Table
->fileName(fn (): string => 'results-'.now()->timestamp),
])
->defaultSort('id', 'desc')
->persistFilters()
->persistSort()
->persistColumnVisibility()
->paginationPageOptions([10, 25, 50])
->poll('60s');
}
Expand Down
7 changes: 7 additions & 0 deletions app/Filament/Resources/Users/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class UserResource extends Resource

protected static ?int $navigationSort = 4;

protected static \UnitEnum|string|null $navigationGroup = null;

public static function getLabel(): ?string
{
return __('general.user');
Expand Down Expand Up @@ -51,4 +53,9 @@ public static function getPages(): array
'index' => ListUsers::route('/'),
];
}

public static function getGloballySearchableAttributes(): array
{
return ['name', 'email'];
}
}
22 changes: 19 additions & 3 deletions app/Jobs/RunPingTargetJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
use App\Actions\PingHostname;
use App\Models\PingResult;
use App\Models\PingTarget;
use App\Models\User;
use App\Notifications\PingTargetOfflineNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Notification;

class RunPingTargetJob implements ShouldQueue
{
Expand All @@ -24,15 +27,21 @@ public function __construct(
*/
public function handle(PingHostname $pingHostname): void
{
$lastResult = $this->pingTarget->pingResults()->latest('created_at')->first();

$result = $pingHostname->run($this->pingTarget->host, $this->pingTarget->packet_count ?? 1);

if ($result === null) {
if ($result === null || ! $result->isSuccess()) {
$this->pingTarget->pingResults()->create([
'latency' => null,
'packet_loss' => null,
'packet_loss' => $result ? (float) $result->packetLossPercentage() : null,
'is_reachable' => false,
]);

if ($lastResult === null || $lastResult->is_reachable) {
$this->notifyAdmins();
}

return;
}

Expand All @@ -41,9 +50,16 @@ public function handle(PingHostname $pingHostname): void
$isReachable = $result->isSuccess();

$this->pingTarget->pingResults()->create([
'latency' => $isReachable ? round($latency, 3) : null,
'latency' => round($latency, 3),
'packet_loss' => (float) $packetLoss,
'is_reachable' => $isReachable,
]);
}

protected function notifyAdmins(): void
{
$admins = User::where('role', \App\Enums\UserRole::Admin)->get();

Notification::send($admins, new PingTargetOfflineNotification($this->pingTarget));
}
}
82 changes: 82 additions & 0 deletions app/Notifications/PingTargetOfflineNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace App\Notifications;

use App\Models\PingTarget;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Telegram\TelegramMessage;

class PingTargetOfflineNotification extends Notification implements ShouldQueue
{
use Queueable;

/**
* Create a new notification instance.
*/
public function __construct(
public PingTarget $pingTarget,
) {}

/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
$channels = [];

if (config('services.telegram-bot-api.token')) {
$channels[] = 'telegram';
}

if (config('mail.from.address')) {
$channels[] = 'mail';
}

$channels[] = 'database';

return $channels;
}

/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->error()
->subject('Ping Target Offline: '.$this->pingTarget->name)
->line('The ping target "'.$this->pingTarget->name.'" ('.$this->pingTarget->host.') is unreachable.')
->action('View Results', url('/admin/ping-results'))
->line('Thank you for using Speedtest Tracker!');
}

/**
* Get the Telegram message representation of the notification.
*/
public function toTelegram($notifiable): TelegramMessage
{
return TelegramMessage::create()
->to($notifiable->routes['telegram_chat_id'] ?? null)
->content(sprintf('⚠️ *Ping Target Offline* %sThe host "%s" (%s) is unreachable.', PHP_EOL, $this->pingTarget->name, $this->pingTarget->host));
}

/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
'ping_target_id' => $this->pingTarget->id,
'name' => $this->pingTarget->name,
'host' => $this->pingTarget->host,
'message' => 'Ping target is unreachable.',
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('results', function (Blueprint $table) {
$table->index('service');
$table->index('status');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('results', function (Blueprint $table) {
$table->dropIndex(['service']);
$table->dropIndex(['status']);
});
}
};
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.