mirror of
https://github.com/alexjustesen/speedtest-tracker.git
synced 2026-06-23 04:10:25 +00:00
Show banner for next scheduled test (#2507)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Services\ScheduledSpeedtestService;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
class NextSpeedtestBanner extends Component
|
||||
{
|
||||
#[Computed]
|
||||
public function nextSpeedtest(): ?Carbon
|
||||
{
|
||||
return ScheduledSpeedtestService::getNextScheduledTest();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.next-speedtest-banner');
|
||||
}
|
||||
}
|
||||
@@ -4,26 +4,12 @@ namespace App\Livewire;
|
||||
|
||||
use App\Enums\ResultStatus;
|
||||
use App\Models\Result;
|
||||
use Carbon\Carbon;
|
||||
use Cron\CronExpression;
|
||||
use Illuminate\Support\Number;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
class PlatformStats extends Component
|
||||
{
|
||||
#[Computed]
|
||||
public function nextSpeedtest(): ?Carbon
|
||||
{
|
||||
if ($schedule = config('speedtest.schedule')) {
|
||||
$cronExpression = new CronExpression($schedule);
|
||||
|
||||
return Carbon::parse(time: $cronExpression->getNextRunDate(timeZone: config('app.display_timezone')));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function platformStats(): array
|
||||
{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Cron\CronExpression;
|
||||
|
||||
class ScheduledSpeedtestService
|
||||
{
|
||||
/**
|
||||
* Assess if there are scheduled speedtests and return the next scheduled time.
|
||||
*
|
||||
* @return Carbon|null Returns null if no tests are scheduled, or Carbon instance with next scheduled test
|
||||
*/
|
||||
public static function getNextScheduledTest(): ?Carbon
|
||||
{
|
||||
$schedule = config('speedtest.schedule');
|
||||
|
||||
if (blank($schedule) || $schedule === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cronExpression = new CronExpression($schedule);
|
||||
|
||||
return Carbon::parse(
|
||||
time: $cronExpression->getNextRunDate(timeZone: config('app.display_timezone'))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<x-app-layout title="Dashboard">
|
||||
<div class="space-y-6 md:space-y-12 dashboard-page">
|
||||
<livewire:next-speedtest-banner />
|
||||
|
||||
@auth
|
||||
<livewire:platform-stats />
|
||||
@endauth
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<x-filament-panels::page class="dashboard-page">
|
||||
<div class="space-y-6 md:space-y-12">
|
||||
<livewire:next-speedtest-banner />
|
||||
|
||||
<livewire:platform-stats />
|
||||
|
||||
<livewire:latest-result-stats />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<div wire:poll.60s>
|
||||
@if ($this->nextSpeedtest)
|
||||
<div class="rounded-md bg-blue-50 dark:bg-blue-500/10 p-4 outline outline-blue-500/20">
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<x-tabler-info-circle class="size-5 text-blue-400" />
|
||||
</div>
|
||||
|
||||
<div class="ml-3 flex-1">
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300">
|
||||
Next scheduled test at <span class="font-medium">{{ $this->nextSpeedtest->timezone(config('app.display_timezone'))->format('F jS, Y, g:i a') }}</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<div wire:poll.60s>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<h2 class="flex items-center gap-x-2 text-base md:text-lg font-semibold text-zinc-900 dark:text-zinc-100 col-span-full">
|
||||
<x-tabler-chart-bar class="size-5" />
|
||||
{{ __('general.statistics') }}
|
||||
@@ -23,25 +23,7 @@
|
||||
</div>
|
||||
</x-filament::section> --}}
|
||||
|
||||
@filled($this->nextSpeedtest)
|
||||
<x-filament::section class="col-span-1">
|
||||
<x-slot name="heading">
|
||||
Next Speedtest in
|
||||
</x-slot>
|
||||
|
||||
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100" title="{{ $this->nextSpeedtest->format('F jS, Y g:i A') }}">{{ $this->nextSpeedtest->diffForHumans() }}</p>
|
||||
</x-filament::section>
|
||||
@else
|
||||
<x-filament::section class="col-span-1 bg-zinc-100 shadow-none">
|
||||
<x-slot name="heading">
|
||||
Next Speedtest in
|
||||
</x-slot>
|
||||
|
||||
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">No scheduled speedtests</p>
|
||||
</x-filament::section>
|
||||
@endfilled
|
||||
|
||||
<x-filament::section class="col-span-1">
|
||||
<x-filament::section class="col-span-1" icon="tabler-hash">
|
||||
<x-slot name="heading">
|
||||
Total tests
|
||||
</x-slot>
|
||||
@@ -49,7 +31,7 @@
|
||||
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">{{ $this->platformStats['total'] }}</p>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section class="col-span-1">
|
||||
<x-filament::section class="col-span-1" icon="tabler-circle-check">
|
||||
<x-slot name="heading">
|
||||
Total completed tests
|
||||
</x-slot>
|
||||
@@ -57,7 +39,7 @@
|
||||
<p class="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">{{ $this->platformStats['completed'] }}</p>
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section class="col-span-1">
|
||||
<x-filament::section class="col-span-1" icon="tabler-alert-circle">
|
||||
<x-slot name="heading">
|
||||
Total failed tests
|
||||
</x-slot>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
use App\Services\ScheduledSpeedtestService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
test('returns null when schedule config is null', function () {
|
||||
config()->set('speedtest.schedule', null);
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeNull();
|
||||
});
|
||||
|
||||
test('returns null when schedule config is false', function () {
|
||||
config()->set('speedtest.schedule', false);
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeNull();
|
||||
});
|
||||
|
||||
test('returns null when schedule config is blank string', function () {
|
||||
config()->set('speedtest.schedule', '');
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeNull();
|
||||
});
|
||||
|
||||
test('returns Carbon instance when schedule is configured', function () {
|
||||
config()->set('speedtest.schedule', '*/5 * * * *'); // Every 5 minutes
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeInstanceOf(Carbon::class);
|
||||
});
|
||||
|
||||
test('returns correct next scheduled time for hourly cron', function () {
|
||||
config()->set('speedtest.schedule', '0 * * * *'); // Every hour at minute 0
|
||||
config()->set('app.display_timezone', 'UTC');
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeInstanceOf(Carbon::class);
|
||||
expect($result->minute)->toBe(0);
|
||||
});
|
||||
|
||||
test('returns correct next scheduled time for daily cron', function () {
|
||||
config()->set('speedtest.schedule', '0 0 * * *'); // Every day at midnight
|
||||
config()->set('app.display_timezone', 'UTC');
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeInstanceOf(Carbon::class);
|
||||
expect($result->hour)->toBe(0);
|
||||
expect($result->minute)->toBe(0);
|
||||
});
|
||||
|
||||
test('returns future date for next scheduled test', function () {
|
||||
config()->set('speedtest.schedule', '*/5 * * * *'); // Every 5 minutes
|
||||
config()->set('app.display_timezone', 'UTC');
|
||||
|
||||
$result = ScheduledSpeedtestService::getNextScheduledTest();
|
||||
|
||||
expect($result)->toBeInstanceOf(Carbon::class);
|
||||
expect($result->isFuture())->toBeTrue();
|
||||
});
|
||||
Reference in New Issue
Block a user