mirror of
https://github.com/alexjustesen/speedtest-tracker.git
synced 2026-06-23 04:30:09 +00:00
116f83a367
Co-authored-by: Alex Justesen <alexjustesen@users.noreply.github.com>
350 lines
18 KiB
PHP
350 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources\Results\Tables;
|
|
|
|
use App\Enums\ResultStatus;
|
|
use App\Filament\Exports\ResultExporter;
|
|
use App\Helpers\Number;
|
|
use App\Jobs\TruncateResults;
|
|
use App\Models\Result;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\ActionGroup;
|
|
use Filament\Actions\DeleteAction;
|
|
use Filament\Actions\DeleteBulkAction;
|
|
use Filament\Actions\ExportAction;
|
|
use Filament\Actions\ViewAction;
|
|
use Filament\Forms\Components\DatePicker;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Support\Enums\Alignment;
|
|
use Filament\Tables\Columns\IconColumn;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Filters\Filter;
|
|
use Filament\Tables\Filters\SelectFilter;
|
|
use Filament\Tables\Filters\TernaryFilter;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class ResultTable
|
|
{
|
|
public static function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->columns([
|
|
TextColumn::make('id')
|
|
->label(__('general.id'))
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(),
|
|
TextColumn::make('data.interface.externalIp')
|
|
->label(__('results.ip_address'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->interface->externalIp', $direction);
|
|
}),
|
|
TextColumn::make('service')
|
|
->label(__('results.service'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(),
|
|
TextColumn::make('data.server.id')
|
|
->label(__('results.server_id'))
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->server->id', $direction);
|
|
}),
|
|
TextColumn::make('data.isp')
|
|
->label(__('results.isp'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->isp', $direction);
|
|
}),
|
|
TextColumn::make('data.server.location')
|
|
->label(__('results.server_location'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->server->location', $direction);
|
|
}),
|
|
TextColumn::make('data.server.name')
|
|
->label(__('results.server_name'))
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->server->name', $direction);
|
|
}),
|
|
TextColumn::make('download')
|
|
->label(__('results.download'))
|
|
->getStateUsing(fn (Result $record): ?string => ! blank($record->download) ? Number::toBitRate(bits: $record->download_bits, precision: 2) : null)
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(),
|
|
TextColumn::make('upload')
|
|
->label(__('results.upload'))
|
|
->getStateUsing(fn (Result $record): ?string => ! blank($record->upload) ? Number::toBitRate(bits: $record->upload_bits, precision: 2) : null)
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(),
|
|
TextColumn::make('ping')
|
|
->label(__('results.ping'))
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable()
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.download.latency.jitter')
|
|
->label(__('results.download_latency_jitter'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->download->latency->jitter', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.download.latency.high')
|
|
->label(__('results.download_latency_high'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->download->latency->high', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.download.latency.low')
|
|
->label(__('results.download_latency_low'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->download->latency->low', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.download.latency.iqm')
|
|
->label(__('results.download_latency_iqm'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->download->latency->iqm', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.upload.latency.jitter')
|
|
->label(__('results.upload_latency_jitter'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->upload->latency->jitter', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.upload.latency.high')
|
|
->label(__('results.upload_latency_high'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->upload->latency->high', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.upload.latency.low')
|
|
->label(__('results.upload_latency_low'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->upload->latency->low', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.upload.latency.iqm')
|
|
->label(__('results.upload_latency_iqm'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(query: function (Builder $query, string $direction): Builder {
|
|
return $query->orderBy('data->upload->latency->iqm', $direction);
|
|
})
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 0, '.', '').' ms';
|
|
}),
|
|
TextColumn::make('data.packetLoss')
|
|
->label(__('results.packet_loss'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable()
|
|
->formatStateUsing(function ($state) {
|
|
return number_format((float) $state, 2, '.', '').' %';
|
|
}),
|
|
TextColumn::make('status')
|
|
->label(__('general.status'))
|
|
->badge()
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable(),
|
|
IconColumn::make('scheduled')
|
|
->label(__('results.scheduled'))
|
|
->boolean()
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->alignment(Alignment::Center),
|
|
IconColumn::make('healthy')
|
|
->label(__('general.healthy'))
|
|
->boolean()
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable()
|
|
->alignment(Alignment::Center),
|
|
TextColumn::make('data.message')
|
|
->label(__('results.error_message'))
|
|
->limit(15)
|
|
->tooltip(fn ($state) => $state)
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable(),
|
|
TextColumn::make('created_at')
|
|
->label(__('general.created_at'))
|
|
->dateTime(config('app.datetime_format'))
|
|
->timezone(config('app.display_timezone'))
|
|
->toggleable(isToggledHiddenByDefault: false)
|
|
->sortable()
|
|
->alignment(Alignment::End),
|
|
TextColumn::make('updated_at')
|
|
->label(__('general.updated_at'))
|
|
->dateTime(config('app.datetime_format'))
|
|
->timezone(config('app.display_timezone'))
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->sortable()
|
|
->alignment(Alignment::End),
|
|
])
|
|
->deferFilters(false)
|
|
->deferColumnManager(false)
|
|
->filters([
|
|
Filter::make('created_at')
|
|
->label(__('general.created_at'))
|
|
->schema([
|
|
DatePicker::make('created_from')
|
|
->label(__('results.created_from'))
|
|
->closeOnDateSelection()
|
|
->native(false),
|
|
DatePicker::make('created_until')
|
|
->label(__('results.created_until'))
|
|
->closeOnDateSelection()
|
|
->native(false),
|
|
])
|
|
->query(function (Builder $query, array $data): Builder {
|
|
return $query
|
|
->when(
|
|
$data['created_from'],
|
|
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date),
|
|
)
|
|
->when(
|
|
$data['created_until'],
|
|
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date),
|
|
);
|
|
}),
|
|
SelectFilter::make('ip_address')
|
|
->label(__('results.ip_address'))
|
|
->multiple()
|
|
->options(function (): array {
|
|
return Result::query()
|
|
->select('data->interface->externalIp AS public_ip_address')
|
|
->whereNotNull('data->interface->externalIp')
|
|
->where('status', '=', ResultStatus::Completed)
|
|
->distinct()
|
|
->orderBy('data->interface->externalIp')
|
|
->get()
|
|
->mapWithKeys(function (Result $item, int $key) {
|
|
return [$item['public_ip_address'] => $item['public_ip_address']];
|
|
})
|
|
->toArray();
|
|
})
|
|
->attribute('data->interface->externalIp'),
|
|
SelectFilter::make('server_name')
|
|
->label(__('results.server_name'))
|
|
->multiple()
|
|
->options(function (): array {
|
|
return Result::query()
|
|
->select('data->server->name AS data_server_name')
|
|
->whereNotNull('data->server->name')
|
|
->where('status', '=', ResultStatus::Completed)
|
|
->distinct()
|
|
->orderBy('data->server->name')
|
|
->get()
|
|
->mapWithKeys(function (Result $item, int $key) {
|
|
return [$item['data_server_name'] => $item['data_server_name']];
|
|
})
|
|
->toArray();
|
|
})
|
|
->attribute('data->server->name'),
|
|
TernaryFilter::make('scheduled')
|
|
->label(__('results.scheduled'))
|
|
->nullable()
|
|
->native(false)
|
|
->trueLabel(__('results.only_scheduled_speedtests'))
|
|
->falseLabel(__('results.only_manual_speedtests'))
|
|
->queries(
|
|
true: fn (Builder $query) => $query->where('scheduled', true),
|
|
false: fn (Builder $query) => $query->where('scheduled', false),
|
|
blank: fn (Builder $query) => $query,
|
|
),
|
|
SelectFilter::make('status')
|
|
->label(__('general.status'))
|
|
->multiple()
|
|
->options(ResultStatus::class),
|
|
TernaryFilter::make('healthy')
|
|
->label(__('general.healthy'))
|
|
->nullable()
|
|
->native(false)
|
|
->trueLabel(__('results.only_healthy_speedtests'))
|
|
->falseLabel(__('results.only_unhealthy_speedtests'))
|
|
->queries(
|
|
true: fn (Builder $query) => $query->where('healthy', true),
|
|
false: fn (Builder $query) => $query->where('healthy', false),
|
|
blank: fn (Builder $query) => $query,
|
|
),
|
|
])
|
|
->recordActions([
|
|
ActionGroup::make([
|
|
ViewAction::make(),
|
|
DeleteAction::make(),
|
|
Action::make('view result')
|
|
->label(__('results.view_on_speedtest_net'))
|
|
->icon('heroicon-o-link')
|
|
->url(fn (Result $record): ?string => $record->result_url)
|
|
->hidden(fn (Result $record): bool => $record->status !== ResultStatus::Completed)
|
|
->openUrlInNewTab(),
|
|
Action::make('updateComments')
|
|
->label(__('results.update_comments'))
|
|
->icon('heroicon-o-chat-bubble-bottom-center-text')
|
|
->hidden(fn (): bool => ! (Auth::user()?->is_admin ?? false) && ! (Auth::user()?->is_user ?? false))
|
|
->mountUsing(fn ($form, Result $record) => $form->fill([
|
|
'comments' => $record->comments,
|
|
]))
|
|
->action(function (Result $record, array $data): void {
|
|
$record->comments = $data['comments'];
|
|
$record->save();
|
|
})
|
|
->schema([
|
|
Textarea::make('comments')
|
|
->label(__('general.comments'))
|
|
->rows(6)
|
|
->maxLength(500),
|
|
]),
|
|
]),
|
|
])
|
|
->toolbarActions([
|
|
DeleteBulkAction::make(),
|
|
])
|
|
->headerActions([
|
|
ExportAction::make()
|
|
->exporter(ResultExporter::class)
|
|
->columnMapping(false)
|
|
->modalHeading(__('results.export_all_results'))
|
|
->modalDescription(__('results.export_all_results_description'))
|
|
->fileName(fn (): string => 'results-'.now()->timestamp),
|
|
ActionGroup::make([
|
|
Action::make('truncate')
|
|
->label(__('results.truncate_results'))
|
|
->action(fn () => TruncateResults::dispatch(Auth::user()))
|
|
->requiresConfirmation()
|
|
->modalHeading(__('results.truncate_results'))
|
|
->modalDescription(__('results.truncate_results_description'))
|
|
->color('danger')
|
|
->icon('heroicon-o-trash')
|
|
->hidden(fn (): bool => ! Auth::user()->is_admin),
|
|
])
|
|
->dropdownPlacement('left-start'),
|
|
])
|
|
->defaultSort('id', 'desc')
|
|
->paginationPageOptions([5, 10, 25, 50, 'all'])
|
|
->deferLoading()
|
|
->poll('60s');
|
|
}
|
|
}
|