feat(scheduler): add pagination to skipped jobs and filter manager start events
- Implement pagination for skipped jobs display with 20 items per page - Add pagination controls (previous/next buttons) to the scheduled jobs view - Exclude ScheduledJobManager "started" events from run logs, keeping only "completed" events - Add ShouldBeEncrypted interface to ScheduledTaskJob for secure queue handling - Update log filtering to fetch 500 recent skips and slice for pagination - Use Log facade instead of fully qualified class name
This commit is contained in:
parent
a0c177f6f2
commit
63be5928ab
7 changed files with 101 additions and 5 deletions
|
|
@ -478,7 +478,7 @@ private function backup_standalone_mongodb(string $databaseWithCollections): voi
|
||||||
throw new \Exception('MongoDB credentials not found. Ensure MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD environment variables are available in the container.');
|
throw new \Exception('MongoDB credentials not found. Ensure MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD environment variables are available in the container.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\Log::info('MongoDB backup URL configured', ['has_url' => filled($url), 'using_env_vars' => blank($this->database->internal_db_url)]);
|
Log::info('MongoDB backup URL configured', ['has_url' => filled($url), 'using_env_vars' => blank($this->database->internal_db_url)]);
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ public function handle(): void
|
||||||
|
|
||||||
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
$this->server->team?->notify(new DockerCleanupSuccess($this->server, $message));
|
||||||
event(new DockerCleanupDone($this->execution_log));
|
event(new DockerCleanupDone($this->execution_log));
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,14 @@
|
||||||
use App\Notifications\ScheduledTask\TaskSuccess;
|
use App\Notifications\ScheduledTask\TaskSuccess;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class ScheduledTaskJob implements ShouldQueue
|
class ScheduledTaskJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,18 @@ class ScheduledJobs extends Component
|
||||||
|
|
||||||
public string $filterDate = 'last_24h';
|
public string $filterDate = 'last_24h';
|
||||||
|
|
||||||
|
public int $skipPage = 0;
|
||||||
|
|
||||||
|
public int $skipDefaultTake = 20;
|
||||||
|
|
||||||
|
public bool $showSkipNext = false;
|
||||||
|
|
||||||
|
public bool $showSkipPrev = false;
|
||||||
|
|
||||||
|
public int $skipCurrentPage = 1;
|
||||||
|
|
||||||
|
public int $skipTotalCount = 0;
|
||||||
|
|
||||||
protected Collection $executions;
|
protected Collection $executions;
|
||||||
|
|
||||||
protected Collection $skipLogs;
|
protected Collection $skipLogs;
|
||||||
|
|
@ -42,11 +54,30 @@ public function mount(): void
|
||||||
|
|
||||||
public function updatedFilterType(): void
|
public function updatedFilterType(): void
|
||||||
{
|
{
|
||||||
|
$this->skipPage = 0;
|
||||||
$this->loadData();
|
$this->loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedFilterDate(): void
|
public function updatedFilterDate(): void
|
||||||
{
|
{
|
||||||
|
$this->skipPage = 0;
|
||||||
|
$this->loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function skipNextPage(): void
|
||||||
|
{
|
||||||
|
$this->skipPage += $this->skipDefaultTake;
|
||||||
|
$this->showSkipPrev = true;
|
||||||
|
$this->loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function skipPreviousPage(): void
|
||||||
|
{
|
||||||
|
$this->skipPage -= $this->skipDefaultTake;
|
||||||
|
if ($this->skipPage < 0) {
|
||||||
|
$this->skipPage = 0;
|
||||||
|
}
|
||||||
|
$this->showSkipPrev = $this->skipPage > 0;
|
||||||
$this->loadData();
|
$this->loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,7 +100,12 @@ private function loadData(?int $teamId = null): void
|
||||||
$this->executions = $this->getExecutions($teamId);
|
$this->executions = $this->getExecutions($teamId);
|
||||||
|
|
||||||
$parser = new SchedulerLogParser;
|
$parser = new SchedulerLogParser;
|
||||||
$this->skipLogs = $parser->getRecentSkips(50, $teamId);
|
$allSkips = $parser->getRecentSkips(500, $teamId);
|
||||||
|
$this->skipTotalCount = $allSkips->count();
|
||||||
|
$this->skipLogs = $allSkips->slice($this->skipPage, $this->skipDefaultTake)->values();
|
||||||
|
$this->showSkipPrev = $this->skipPage > 0;
|
||||||
|
$this->showSkipNext = ($this->skipPage + $this->skipDefaultTake) < $this->skipTotalCount;
|
||||||
|
$this->skipCurrentPage = intval($this->skipPage / $this->skipDefaultTake) + 1;
|
||||||
$this->managerRuns = $parser->getRecentRuns(30, $teamId);
|
$this->managerRuns = $parser->getRecentRuns(30, $teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ public function getRecentRuns(int $limit = 60, ?int $teamId = null): Collection
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! str_contains($entry['message'], 'ScheduledJobManager')) {
|
if (! str_contains($entry['message'], 'ScheduledJobManager') || str_contains($entry['message'], 'started')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class="flex flex-col gap-8">
|
||||||
])
|
])
|
||||||
:class="activeTab === 'skipped-jobs' && 'dark:bg-coollabs bg-coollabs text-white'"
|
:class="activeTab === 'skipped-jobs' && 'dark:bg-coollabs bg-coollabs text-white'"
|
||||||
@click="activeTab = 'skipped-jobs'; window.location.hash = 'skipped-jobs'">
|
@click="activeTab = 'skipped-jobs'; window.location.hash = 'skipped-jobs'">
|
||||||
Skipped Jobs ({{ $skipLogs->count() }})
|
Skipped Jobs ({{ $skipTotalCount }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -186,6 +186,27 @@ class="border-b border-gray-200 dark:border-coolgray-400">
|
||||||
{{-- Skipped Jobs Tab --}}
|
{{-- Skipped Jobs Tab --}}
|
||||||
<div x-show="activeTab === 'skipped-jobs'" x-cloak>
|
<div x-show="activeTab === 'skipped-jobs'" x-cloak>
|
||||||
<div class="pb-4 text-sm text-gray-500">Jobs that were not dispatched because conditions were not met.</div>
|
<div class="pb-4 text-sm text-gray-500">Jobs that were not dispatched because conditions were not met.</div>
|
||||||
|
@if($skipTotalCount > $skipDefaultTake)
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-forms.button disabled="{{ !$showSkipPrev }}" wire:click="skipPreviousPage">
|
||||||
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</x-forms.button>
|
||||||
|
<span class="text-sm">
|
||||||
|
Page {{ $skipCurrentPage }} of {{ ceil($skipTotalCount / $skipDefaultTake) }}
|
||||||
|
</span>
|
||||||
|
<x-forms.button disabled="{{ !$showSkipNext }}" wire:click="skipNextPage">
|
||||||
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-sm text-left">
|
<table class="w-full text-sm text-left">
|
||||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-coolgray-200">
|
<thead class="text-xs uppercase bg-gray-50 dark:bg-coolgray-200">
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,42 @@
|
||||||
@unlink($logPath);
|
@unlink($logPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('scheduler log parser excludes started events from runs', function () {
|
||||||
|
$logPath = storage_path('logs/scheduled-test-started-filter.log');
|
||||||
|
$logDir = dirname($logPath);
|
||||||
|
if (! is_dir($logDir)) {
|
||||||
|
mkdir($logDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily rename existing logs so they don't interfere
|
||||||
|
$existingLogs = glob(storage_path('logs/scheduled-*.log'));
|
||||||
|
$renamed = [];
|
||||||
|
foreach ($existingLogs as $log) {
|
||||||
|
$tmp = $log.'.bak';
|
||||||
|
rename($log, $tmp);
|
||||||
|
$renamed[$tmp] = $log;
|
||||||
|
}
|
||||||
|
|
||||||
|
$logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
|
||||||
|
$lines = [
|
||||||
|
'['.now()->format('Y-m-d H:i:s').'] production.INFO: ScheduledJobManager started {}',
|
||||||
|
'['.now()->format('Y-m-d H:i:s').'] production.INFO: ScheduledJobManager completed {"duration_ms":74,"dispatched":1,"skipped":13}',
|
||||||
|
];
|
||||||
|
file_put_contents($logPath, implode("\n", $lines)."\n");
|
||||||
|
|
||||||
|
$parser = new SchedulerLogParser;
|
||||||
|
$runs = $parser->getRecentRuns();
|
||||||
|
|
||||||
|
expect($runs)->toHaveCount(1);
|
||||||
|
expect($runs->first()['message'])->toContain('completed');
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
@unlink($logPath);
|
||||||
|
foreach ($renamed as $tmp => $original) {
|
||||||
|
rename($tmp, $original);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('scheduler log parser filters by team id', function () {
|
test('scheduler log parser filters by team id', function () {
|
||||||
$logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
|
$logPath = storage_path('logs/scheduled-'.now()->format('Y-m-d').'.log');
|
||||||
$logDir = dirname($logPath);
|
$logDir = dirname($logPath);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue