fix: improve scheduled task adding/removing

This commit is contained in:
Andras Bacsai 2024-05-14 15:19:28 +02:00
parent f06065337c
commit 317dc10af4
12 changed files with 123 additions and 56 deletions

View File

@ -77,8 +77,12 @@ public function handle(): void
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'); $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
} }
}); });
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
$this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
} }
if (count($this->containers) == 0) { if (count($this->containers) == 0) {
throw new \Exception('ScheduledTaskJob failed: No containers running.'); throw new \Exception('ScheduledTaskJob failed: No containers running.');
} }

View File

@ -3,7 +3,6 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Actions\Docker\GetContainersStatus; use App\Actions\Docker\GetContainersStatus;
use App\Jobs\ContainerStatusJob;
use App\Models\Service; use App\Models\Service;
use Livewire\Component; use Livewire\Component;

View File

@ -2,11 +2,14 @@
namespace App\Livewire\Project\Shared\ScheduledTask; namespace App\Livewire\Project\Shared\ScheduledTask;
use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class Add extends Component class Add extends Component
{ {
public $parameters; public $parameters;
public string $type;
public Collection $containerNames;
public string $name; public string $name;
public string $command; public string $command;
public string $frequency; public string $frequency;
@ -29,6 +32,9 @@ class Add extends Component
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
if ($this->containerNames->count() > 0) {
$this->container = $this->containerNames->first();
}
} }
public function submit() public function submit()
@ -40,6 +46,11 @@ public function submit()
$this->dispatch('error', 'Invalid Cron / Human expression.'); $this->dispatch('error', 'Invalid Cron / Human expression.');
return; return;
} }
if (empty($this->container) || $this->container == 'null') {
if ($this->type == 'service') {
$this->container = $this->subServiceName;
}
}
$this->dispatch('saveScheduledTask', [ $this->dispatch('saveScheduledTask', [
'name' => $this->name, 'name' => $this->name,
'command' => $this->command, 'command' => $this->command,

View File

@ -3,14 +3,13 @@
namespace App\Livewire\Project\Shared\ScheduledTask; namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask; use App\Models\ScheduledTask;
use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class All extends Component class All extends Component
{ {
public $resource; public $resource;
public string|null $modalId = null; public Collection $containerNames;
public ?string $variables = null; public ?string $variables = null;
public array $parameters; public array $parameters;
protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit']; protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit'];
@ -18,7 +17,19 @@ class All extends Component
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->modalId = new Cuid2(7); if ($this->resource->type() == 'service') {
$this->containerNames = $this->resource->applications()->pluck('name');
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
ray($this->containerNames);
} elseif ($this->resource->type() == 'application') {
if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parseCompose();
$containers = collect($parsed['services'])->keys();
$this->containerNames = $containers;
} else {
$this->containerNames = collect([]);
}
}
} }
public function refreshTasks() public function refreshTasks()
{ {

View File

@ -17,6 +17,7 @@ class Show extends Component
public string $type; public string $type;
protected $rules = [ protected $rules = [
'task.enabled' => 'required|boolean',
'task.name' => 'required|string', 'task.name' => 'required|string',
'task.command' => 'required|string', 'task.command' => 'required|string',
'task.frequency' => 'required|string', 'task.frequency' => 'required|string',
@ -45,9 +46,18 @@ public function mount()
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first(); $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
} }
public function instantSave()
{
$this->validateOnly('task.enabled');
$this->task->save(['enabled' => $this->task->enabled]);
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
}
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
$this->task->name = str($this->task->name)->trim()->value();
$this->task->container = str($this->task->container)->trim()->value();
$this->task->save(); $this->task->save();
$this->dispatch('success', 'Scheduled task updated.'); $this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks'); $this->dispatch('refreshTasks');
@ -60,11 +70,9 @@ public function delete()
if ($this->type == 'application') { if ($this->type == 'application') {
return redirect()->route('project.application.configuration', $this->parameters); return redirect()->route('project.application.configuration', $this->parameters);
} } else {
else {
return redirect()->route('project.service.configuration', $this->parameters); return redirect()->route('project.service.configuration', $this->parameters);
} }
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e); return handleError($e);
} }

View File

@ -677,6 +677,17 @@ public function server()
{ {
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
public function byUuid(string $uuid) {
$app = $this->applications()->whereUuid($uuid)->first();
if ($app) {
return $app;
}
$db = $this->databases()->whereUuid($uuid)->first();
if ($db) {
return $db;
}
return null;
}
public function byName(string $name) public function byName(string $name)
{ {
$app = $this->applications()->whereName($name)->first(); $app = $this->applications()->whereName($name)->first();

View File

@ -16,6 +16,10 @@
@click.prevent="activeTab = 'storages'; @click.prevent="activeTab = 'storages';
window.location.hash = 'storages'" window.location.hash = 'storages'"
href="#">Storages</a> href="#">Storages</a>
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
href="#">Scheduled Tasks
</a>
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'" <a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
@click.prevent="activeTab = 'execute-command'; @click.prevent="activeTab = 'execute-command';
window.location.hash = 'execute-command'" window.location.hash = 'execute-command'"
@ -103,10 +107,13 @@ class="w-4 h-4 dark:text-warning text-coollabs"
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $application->uuid]) }}"> href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $application->uuid]) }}">
Settings Settings
</a> </a>
<x-modal-confirmation action="restartApplication({{ $application->id }})" @if (str($application->status)->contains('running'))
isErrorButton buttonTitle="Restart"> <x-modal-confirmation action="restartApplication({{ $application->id }})"
This application will be unavailable during the restart. <br>Please think again. isErrorButton buttonTitle="Restart">
</x-modal-confirmation> This application will be unavailable during the restart. <br>Please think
again.
</x-modal-confirmation>
@endif
</div> </div>
</div> </div>
</div> </div>
@ -169,6 +176,9 @@ class="w-4 h-4 dark:text-warning text-coollabs"
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" /> <livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
@endforeach @endforeach
</div> </div>
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
<livewire:project.shared.scheduled-task.all :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'webhooks'"> <div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$service" /> <livewire:project.shared.webhooks :resource="$service" />
</div> </div>

View File

@ -10,14 +10,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'" <a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''" @click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
href="#">General</a> href="#">General</a>
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
href="#">Storages
</a>
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
href="#">Scheduled Tasks
</a>
@if (str($serviceDatabase?->databaseType())->contains('mysql') || @if (str($serviceDatabase?->databaseType())->contains('mysql') ||
str($serviceDatabase?->databaseType())->contains('postgres') || str($serviceDatabase?->databaseType())->contains('postgres') ||
str($serviceDatabase?->databaseType())->contains('mariadb')) str($serviceDatabase?->databaseType())->contains('mariadb'))
@ -30,28 +22,12 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
<div x-cloak x-show="activeTab === 'general'" class="h-full"> <div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.service-application-view :application="$serviceApplication" /> <livewire:project.service.service-application-view :application="$serviceApplication" />
</div> </div>
<div x-cloak x-show="activeTab === 'storages'">
<div class="flex items-center gap-2">
<h2>Storages</h2>
</div>
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
<span class="dark:text-warning">Please modify storage layout in your Docker Compose file.</span>
<livewire:project.service.storage wire:key="application-{{ $serviceApplication->id }}"
:resource="$serviceApplication" />
</div>
@endisset @endisset
@isset($serviceDatabase) @isset($serviceDatabase)
<div x-cloak x-show="activeTab === 'general'" class="h-full"> <div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.database :database="$serviceDatabase" /> <livewire:project.service.database :database="$serviceDatabase" />
</div> </div>
<div x-cloak x-show="activeTab === 'storages'">
<div class="flex items-center gap-2">
<h2>Storages</h2>
</div>
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
<span class="dark:text-warning">Please modify storage layout in your Docker Compose file.</span>
<livewire:project.service.storage wire:key="application-{{ $serviceDatabase->id }}" :resource="$serviceDatabase" />
</div>
<div x-cloak x-show="activeTab === 'backups'"> <div x-cloak x-show="activeTab === 'backups'">
<div class="flex gap-2 "> <div class="flex gap-2 ">
<h2 class="pb-4">Scheduled Backups</h2> <h2 class="pb-4">Scheduled Backups</h2>
@ -62,9 +38,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
<livewire:project.database.scheduled-backups :database="$serviceDatabase" /> <livewire:project.database.scheduled-backups :database="$serviceDatabase" />
</div> </div>
@endisset @endisset
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
<livewire:project.shared.scheduled-task.all :resource="$service" />
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,27 @@
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'> <form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
<x-forms.input autofocus placeholder="Run cron" id="name" label="Name" /> <x-forms.input autofocus placeholder="Run cron" id="name" label="Name" />
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" /> <x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" />
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" /> <x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" />
<x-forms.input placeholder="php" id="container" @if ($type === 'application')
helper="You can leave it empty if your resource only have one container." label="Container name" /> @if ($containerNames->count() > 1)
<x-forms.button @click="slideOverOpen=false" type="submit"> <x-forms.select id="container" label="Container name">
@foreach ($containerNames as $containerName)
<option value="{{ $containerName }}">{{ $containerName }}</option>
@endforeach
</x-forms.select>
@else
<x-forms.input placeholder="php" id="container"
helper="You can leave it empty if your resource only have one container." label="Container name" />
@endif
@elseif ($type === 'service')
<x-forms.select id="container" label="Container name">
@foreach ($containerNames as $containerName)
<option value="{{ $containerName }}">{{ $containerName }}</option>
@endforeach
</x-forms.select>
@endif
<x-forms.button @click="modalOpen=false" type="submit">
Save Save
</x-forms.button> </x-forms.button>
</form> </form>

View File

@ -1,8 +1,12 @@
<div> <div>
<div class="flex gap-2"> <div class="flex gap-2">
<h2>Scheduled Tasks</h2> <h2>Scheduled Tasks</h2>
<x-modal-input buttonTitle="+ Add" title="New Scheduled Task"> <x-modal-input buttonTitle="+ Add" title="New Scheduled Task" :closeOutside=false>
<livewire:project.shared.scheduled-task.add /> @if ($resource->type() == 'application')
<livewire:project.shared.scheduled-task.add :type="$resource->type()" :containerNames="$containerNames"/>
@elseif ($resource->type() == 'service')
<livewire:project.shared.scheduled-task.add :type="$resource->type()" :containerNames="$containerNames"/>
@endif
</x-modal-input> </x-modal-input>
</div> </div>
<div class="flex flex-wrap gap-2 pt-4"> <div class="flex flex-wrap gap-2 pt-4">
@ -11,7 +15,12 @@
<a class="box" <a class="box"
href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}"> href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
<span class="flex flex-col"> <span class="flex flex-col">
<span class="font-bold">{{ $task->name }}</span> <span class="text-lg font-bold">{{ $task->name }}
@if ($task->container)
<span class="text-xs font-normal">({{ $task->container }})</span>
@endif
</span>
<span>Frequency: {{ $task->frequency }}</span> <span>Frequency: {{ $task->frequency }}</span>
<span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }} <span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}
</span> </span>
@ -21,7 +30,11 @@
<a class="box" <a class="box"
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}"> href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
<span class="flex flex-col"> <span class="flex flex-col">
<span class="font-bold">{{ $task->name }}</span> <span class="text-lg font-bold">{{ $task->name }}
@if ($task->container)
<span class="text-xs font-normal">({{ $task->container }})</span>
@endif
</span>
<span>Frequency: {{ $task->frequency }}</span> <span>Frequency: {{ $task->frequency }}</span>
<span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }} <span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}
</span> </span>

View File

@ -12,7 +12,7 @@
</div> </div>
@endif @endif
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([ <a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
'flex flex-col border-l border-dashed transition-colors box-without-bg bg-coolgray-100 hover:bg-coolgray-100 cursor-pointer', 'flex flex-col border-l transition-colors box-without-bg bg-coolgray-100 hover:bg-coolgray-200 cursor-pointer',
'bg-coolgray-200 dark:text-white hover:bg-coolgray-200' => 'bg-coolgray-200 dark:text-white hover:bg-coolgray-200' =>
data_get($execution, 'id') == $selectedKey, data_get($execution, 'id') == $selectedKey,
'border-green-500' => data_get($execution, 'status') === 'success', 'border-green-500' => data_get($execution, 'status') === 'success',

View File

@ -1,13 +1,13 @@
<div> <div>
<h1>Scheduled Task</h1>
@if ($type === 'application') @if ($type === 'application')
<h1>Scheduled Task</h1>
<livewire:project.application.heading :application="$resource" /> <livewire:project.application.heading :application="$resource" />
@elseif ($type === 'service') @elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" /> <livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
@endif @endif
<form wire:submit="submit" class="w-full"> <form wire:submit="submit" class="w-full">
<div class="flex flex-col gap-2 pb-4"> <div class="flex flex-col gap-2 pb-2">
<div class="flex items-end gap-2 pt-4"> <div class="flex items-end gap-2 pt-4">
<h2>Scheduled Task</h2> <h2>Scheduled Task</h2>
<x-forms.button type="submit"> <x-forms.button type="submit">
@ -17,13 +17,23 @@
You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>. You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>.
</x-modal-confirmation> </x-modal-confirmation>
</div> </div>
<div class="w-48">
<x-forms.checkbox instantSave id="task.enabled" label="Enabled" />
</div>
</div> </div>
<div class="flex w-full gap-2"> <div class="flex w-full gap-2">
<x-forms.input placeholder="Name" id="task.name" label="Name" required /> <x-forms.input placeholder="Name" id="task.name" label="Name" required />
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required /> <x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required /> <x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
<x-forms.input placeholder="php" helper="You can leave it empty if your resource only have one container." @if ($type === 'application')
id="task.container" label="Container name" /> <x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one container." id="task.container"
label="Container name" />
@elseif ($type === 'service')
<x-forms.input placeholder="php"
helper="You can leave it empty if your resource only have one service in your stack. Otherwise use the stack name, without the random generated id. So if you have a mysql service in your stack, use mysql."
id="task.container" label="Service name" />
@endif
</div> </div>
</form> </form>