coolify/resources/views/livewire/settings/scheduled-jobs.blade.php

313 lines
20 KiB
PHP

<div>
<x-slot:title>
Scheduled Jobs | MapleDeploy
</x-slot>
<x-settings.layout>
<div class="application-settings-form mx-auto w-full max-w-none min-w-0" x-data="{
activeTab: ['executions', 'scheduler-runs', 'skipped-jobs'].includes(location.hash.slice(1))
? location.hash.slice(1)
: 'executions',
filterOpen: false,
sortOpen: false,
select(tab) {
this.activeTab = tab;
history.replaceState(null, '', `#${tab}`);
}
}">
<x-application.settings-section title="Scheduler activity" flush>
<x-slot:actions>
<div
class="flex items-center gap-0.5 rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
<button type="button" class="app-tab"
:class="activeTab === 'executions' &&
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25'"
@click="select('executions')">
Failures <span class="ml-1 opacity-60">{{ $executions->count() }}</span>
</button>
<button type="button" class="app-tab"
:class="activeTab === 'scheduler-runs' &&
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25'"
@click="select('scheduler-runs')">
Scheduler runs <span class="ml-1 opacity-60">{{ $managerRuns->count() }}</span>
</button>
<button type="button" class="app-tab"
:class="activeTab === 'skipped-jobs' &&
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25'"
@click="select('skipped-jobs')">
Skipped <span class="ml-1 opacity-60">{{ $skipTotalCount }}</span>
</button>
</div>
<x-forms.button type="button" wire:click="refresh">
<x-reicon name="refresh" class="size-3.5" />
Refresh
</x-forms.button>
</x-slot:actions>
<div
class="flex flex-col gap-3 border-b border-neutral-200 p-3 dark:border-white/[0.08]">
<div x-show="activeTab === 'executions'"
class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="relative w-full sm:max-w-sm">
<x-reicon name="search"
class="pointer-events-none absolute top-1/2 left-2.5 z-10 size-3.5 -translate-y-1/2 text-neutral-400 dark:text-fg-faint" />
<input wire:model.live.debounce.250ms="search" type="search"
placeholder="Search scheduled jobs"
class="h-8! w-full rounded-lg! border-neutral-200! bg-white! py-0! pr-3! pl-8! text-[12px]! shadow-none! placeholder:text-neutral-400 focus:border-accent! focus:ring-0! dark:border-white/[0.08]! dark:bg-white/[0.035]! dark:text-fg! dark:placeholder:text-fg-faint">
</div>
<div class="flex items-center gap-2">
<x-table.dropdown panel-class="w-52!">
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<x-reicon name="filter" class="size-3.5" />
Filter
</button></x-slot:trigger>
<div
class="px-2 py-1 text-[10px] font-semibold tracking-wide text-neutral-400 uppercase dark:text-fg-faint">
Type
</div>
@foreach (['all' => 'All types', 'backup' => 'Backups', 'task' => 'Tasks', 'cleanup' => 'Docker cleanup'] as $value => $label)
<button type="button" class="listbox-option"
wire:click="$set('filterType', '{{ $value }}')"
@click="close()">
<span>{{ $label }}</span>
@if ($filterType === $value)
<span class="text-accent"></span>
@endif
</button>
@endforeach
<div
class="mt-1 border-t border-neutral-200 px-2 pt-2 pb-1 text-[10px] font-semibold tracking-wide text-neutral-400 uppercase dark:border-white/[0.08] dark:text-fg-faint">
Time range
</div>
@foreach (['last_24h' => 'Last 24 hours', 'last_7d' => 'Last 7 days', 'last_30d' => 'Last 30 days', 'all' => 'All time'] as $value => $label)
<button type="button" class="listbox-option"
wire:click="$set('filterDate', '{{ $value }}')"
@click="close()">
<span>{{ $label }}</span>
@if ($filterDate === $value)
<span class="text-accent"></span>
@endif
</button>
@endforeach
</x-table.dropdown>
<x-table.dropdown panel-class="w-44!">
<x-slot:trigger><button type="button" class="button" aria-haspopup="listbox" :aria-expanded="open">
<x-reicon name="sort-direction" class="size-3.5" />
Sort
</button></x-slot:trigger>
@foreach (['newest' => 'Newest first', 'oldest' => 'Oldest first'] as $value => $label)
<button type="button" class="listbox-option"
wire:click="$set('sortOrder', '{{ $value }}')"
@click="close()">
<span>{{ $label }}</span>
@if ($sortOrder === $value)
<span class="text-accent"></span>
@endif
</button>
@endforeach
</x-table.dropdown>
</div>
</div>
</div>
<div x-cloak x-show="activeTab === 'executions'">
@if ($executions->isEmpty())
<x-empty title="No failures"
description="No failed scheduled executions match the current filters."
icon-name="check-circle" size="sm" />
@else
<div x-data="{
page: 1,
perPage: 10,
total: {{ $executions->count() }},
get lastPage() { return Math.max(1, Math.ceil(this.total / this.perPage)); },
get firstVisibleRow() { return ((this.page - 1) * this.perPage) + 1; },
get lastVisibleRow() { return Math.min(this.page * this.perPage, this.total); },
goToPage(page) { this.page = Math.min(Math.max(page, 1), this.lastPage); }
}">
<div class="data-table">
<div class="data-table-header scheduled-executions-table-grid">
<span>Type</span>
<span>Resource</span>
<span>Server</span>
<span>Started</span>
<span>Duration</span>
<span>Message</span>
</div>
@foreach ($executions as $execution)
@php
$typeLabel = match ($execution['type']) {
'backup' => 'Backup',
'task' => 'Task',
'cleanup' => 'Cleanup',
default => ucfirst($execution['type']),
};
@endphp
<div wire:key="exec-{{ $execution['type'] }}-{{ $execution['id'] }}"
x-show="{{ $loop->index }} >= (page - 1) * perPage && {{ $loop->index }} < page * perPage"
class="data-table-row scheduled-executions-table-grid border-b border-neutral-200 last:border-b-0 dark:border-white/[0.07]">
<div>
<span
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
{{ $typeLabel }}
</span>
</div>
<div class="min-w-0 truncate text-[12px] font-medium text-black dark:text-fg">
{{ $execution['resource_name'] }}
@if ($execution['resource_type'])
<span class="text-[10px] font-normal text-neutral-400">
{{ $execution['resource_type'] }}
</span>
@endif
</div>
<div class="truncate text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $execution['server_name'] }}
</div>
<div class="text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $execution['created_at']->format('Y-m-d H:i') }}
</div>
<div class="text-[11px] text-neutral-500 dark:text-fg-dim">
@if ($execution['finished_at'] && $execution['created_at'])
{{ \Carbon\Carbon::parse($execution['created_at'])->diffInSeconds(\Carbon\Carbon::parse($execution['finished_at'])) }}s
@elseif ($execution['status'] === 'running')
<x-loading class="size-3.5" />
@else
-
@endif
</div>
<div class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-dim"
title="{{ $execution['message'] }}">
{{ Str::limit($execution['message'], 80) }}
</div>
</div>
@endforeach
</div>
<x-client-pagination
summary="`${firstVisibleRow}-${lastVisibleRow} of ${total}`"
page-size-model="perPage" storage-key="coolify.page-size.scheduled-jobs"
previous-action="goToPage(page - 1)" next-action="goToPage(page + 1)"
next-disabled="page >= lastPage" />
</div>
@endif
</div>
<div x-cloak x-show="activeTab === 'scheduler-runs'">
@if ($managerRuns->isEmpty())
<x-empty title="No manager runs"
description="Scheduler manager activity appears here after its next run."
icon-name="refresh" size="sm" />
@else
<div x-data="{
page: 1,
perPage: 10,
total: {{ $managerRuns->count() }},
get lastPage() { return Math.max(1, Math.ceil(this.total / this.perPage)); },
get firstVisibleRow() { return ((this.page - 1) * this.perPage) + 1; },
get lastVisibleRow() { return Math.min(this.page * this.perPage, this.total); },
goToPage(page) { this.page = Math.min(Math.max(page, 1), this.lastPage); }
}">
<div class="data-table">
<div class="data-table-header scheduler-runs-table-grid">
<span>Time</span>
<span>Event</span>
<span>Duration</span>
<span>Dispatched</span>
<span>Skipped</span>
</div>
@foreach ($managerRuns as $run)
<div wire:key="run-{{ md5(serialize($run)) }}"
x-show="{{ $loop->index }} >= (page - 1) * perPage && {{ $loop->index }} < page * perPage"
class="data-table-row scheduler-runs-table-grid border-b border-neutral-200 last:border-b-0 dark:border-white/[0.07]">
<div class="font-mono text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $run['timestamp'] }}
</div>
<div class="min-w-0 truncate text-[12px] text-black dark:text-fg">
{{ $run['message'] }}
</div>
<div class="text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $run['duration_ms'] !== null ? $run['duration_ms'] . 'ms' : '-' }}
</div>
<div class="text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $run['dispatched'] ?? '-' }}
</div>
<div class="text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $run['skipped'] ?? '-' }}
</div>
</div>
@endforeach
</div>
<x-client-pagination
summary="`${firstVisibleRow}-${lastVisibleRow} of ${total}`"
page-size-model="perPage" storage-key="coolify.page-size.scheduled-jobs"
previous-action="goToPage(page - 1)" next-action="goToPage(page + 1)"
next-disabled="page >= lastPage" />
</div>
@endif
</div>
<div x-cloak x-show="activeTab === 'skipped-jobs'">
@if ($skipLogs->isEmpty())
<x-empty title="No skipped jobs"
description="All scheduled jobs met their dispatch conditions."
icon-name="check-circle" size="sm" />
@else
<div class="data-table">
<div class="data-table-header skipped-jobs-table-grid">
<span>Time</span>
<span>Type</span>
<span>Resource</span>
<span>Reason</span>
</div>
@foreach ($skipLogs as $skip)
@php
$reasonLabel = match ($skip['reason']) {
'server_not_functional' => 'Server not functional',
'subscription_unpaid' => 'Subscription unpaid',
'database_deleted' => 'Database deleted',
'server_deleted' => 'Server deleted',
'resource_deleted' => 'Resource deleted',
'application_not_running' => 'Application not running',
'service_not_running' => 'Service not running',
default => ucfirst(str_replace('_', ' ', $skip['reason'])),
};
@endphp
<div wire:key="skip-{{ md5(serialize($skip)) }}"
class="data-table-row skipped-jobs-table-grid border-b border-neutral-200 last:border-b-0 dark:border-white/[0.07]">
<div class="font-mono text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $skip['timestamp'] }}
</div>
<div>
<span
class="rounded-full bg-neutral-100 px-2 py-0.5 text-[10px] font-medium capitalize text-neutral-600 dark:bg-white/[0.06] dark:text-fg-dim">
{{ str_replace('_', ' ', $skip['type']) }}
</span>
</div>
<div class="min-w-0 truncate text-[12px] text-black dark:text-fg">
@if ($skip['link'] ?? null)
<a href="{{ $skip['link'] }}"
class="font-medium text-coollabs hover:underline dark:text-warning">
{{ $skip['resource_name'] }}
</a>
@else
{{ $skip['resource_name'] ?? $skip['context']['task_name'] ?? $skip['context']['server_name'] ?? 'Deleted resource' }}
@endif
</div>
<div class="min-w-0 truncate text-[11px] text-neutral-500 dark:text-fg-dim">
{{ $reasonLabel }}
</div>
</div>
@endforeach
</div>
<x-table-pagination :from="$skipTotalCount === 0 ? 0 : $skipPage + 1"
:to="min($skipPage + $skipLogs->count(), $skipTotalCount)" :total="$skipTotalCount"
:current-page="$skipCurrentPage"
:last-page="max(1, (int) ceil($skipTotalCount / $skipDefaultTake))"
wire-target="skipPreviousPage,skipNextPage" previous-action="skipPreviousPage"
next-action="skipNextPage" />
@endif
</div>
</x-application.settings-section>
</div>
</x-settings.layout>
</div>