From e2e68136329a05e0e3189da5260d575b44d9ef43 Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Fri, 5 Jan 2024 15:06:36 +1000 Subject: [PATCH] Functional scheduled executions. Display last executions. Support for Services. --- app/Jobs/ScheduledTaskJob.php | 80 ++++++++++++++----- .../Project/Shared/ScheduledTask/All.php | 1 + .../Shared/ScheduledTask/Executions.php | 28 +++++++ app/Models/Service.php | 4 + ...31_173041_create_scheduled_tasks_table.php | 1 + .../livewire/project/service/index.blade.php | 7 ++ .../shared/scheduled-task/all.blade.php | 16 ++-- .../scheduled-task/executions.blade.php | 27 +++++++ .../shared/scheduled-task/show.blade.php | 14 +++- routes/web.php | 2 + 10 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 app/Livewire/Project/Shared/ScheduledTask/Executions.php create mode 100644 resources/views/livewire/project/shared/scheduled-task/executions.blade.php diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index f810e746a..104b5f7e3 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -3,17 +3,18 @@ namespace App\Jobs; use App\Models\ScheduledTask; +use App\Models\ScheduledTaskExecution; use App\Models\Server; use App\Models\Application; use App\Models\Service; -use Carbon\Carbon; +use App\Models\Team; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Str; +use Illuminate\Support\Collection; use Throwable; class ScheduledTaskJob implements ShouldQueue @@ -25,13 +26,10 @@ class ScheduledTaskJob implements ShouldQueue public ScheduledTask $task; public Application|Service $resource; - public ?string $container_name = null; - public ?string $directory_name = null; - public ?ScheduledTaskExecution $backup_log = null; + public ?ScheduledTaskExecution $task_log = null; public string $task_status = 'failed'; - public int $size = 0; - public ?string $backup_output = null; - public ?S3Storage $s3 = null; + public ?string $task_output = null; + public array $containers = []; public function __construct($task) { @@ -41,6 +39,7 @@ class ScheduledTaskJob implements ShouldQueue } else if ($application = $task->application()->first()) { $this->resource = $application; } + $this->team = Team::find($task->team_id); } public function middleware(): array @@ -55,23 +54,62 @@ class ScheduledTaskJob implements ShouldQueue public function handle(): void { - file_put_contents('/tmp/scheduled-job-run', 'ran in handle'); try { - echo($this->resource->type()); - file_put_contents('/tmp/scheduled-job-run-'.$this->task->id, $this->task->name); + $this->task_log = ScheduledTaskExecution::create([ + 'scheduled_task_id' => $this->task->id, + ]); + + $this->server = $this->resource->destination->server; + + if ($this->resource->type() == 'application') { + $containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0); + if ($containers->count() > 0) { + $containers->each(function ($container) { + $this->containers[] = str_replace('/', '', $container['Names']); + }); + } + } + elseif ($this->resource->type() == 'service') { + $this->resource->applications()->get()->each(function ($application) { + if (str(data_get($application, 'status'))->contains('running')) { + $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'); + } + }); + } + + if (count($this->containers) == 0) { + throw new \Exception('ScheduledTaskJob failed: No containers running.'); + } + + if (count($this->containers) > 1 && empty($this->task->container)) { + throw new \Exception('ScheduledTaskJob failed: More than one container exists but no container name was provided.'); + } + + foreach ($this->containers as $containerName) { + if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) { + $cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"'; + $exec = "docker exec {$containerName} {$cmd}"; + $this->task_output = instant_remote_process([$exec], $this->server, true); + $this->task_log->update([ + 'status' => 'success', + 'message' => $this->task_output, + ]); + return; + } + } + + // No valid container was found. + throw new \Exception('ScheduledTaskJob failed: No valid container was found. Is the container name correct?'); + } catch (\Throwable $e) { + if ($this->task_log) { + $this->task_log->update([ + 'status' => 'failed', + 'message' => $this->task_output ?? $e->getMessage(), + ]); + } send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); throw $e; - } finally { - // BackupCreated::dispatch($this->team->id); - } - } - private function add_to_backup_output($output): void - { - if ($this->backup_output) { - $this->backup_output = $this->backup_output . "\n" . $output; - } else { - $this->backup_output = $output; } } } diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 7f61c30cc..4a876e72a 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -33,6 +33,7 @@ class All extends Component $task->command = $data['command']; $task->frequency = $data['frequency']; $task->container = $data['container']; + $task->team_id = currentTeam()->id; switch ($this->resource->type()) { case 'application': diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php new file mode 100644 index 000000000..5351c3d4c --- /dev/null +++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php @@ -0,0 +1,28 @@ +selectedKey) { + $this->selectedKey = null; + return; + } + $this->selectedKey = $key; + } +} diff --git a/app/Models/Service.php b/app/Models/Service.php index eb0d96670..7f71ff865 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -396,6 +396,10 @@ class Service extends BaseModel } return null; } + public function scheduled_tasks(): HasMany + { + return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); + } public function environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc'); diff --git a/database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php b/database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php index b108cecf8..c3c2e2f48 100644 --- a/database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php +++ b/database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php @@ -23,6 +23,7 @@ return new class extends Migration $table->foreignId('application_id')->nullable(); $table->foreignId('service_id')->nullable(); + $table->foreignId('team_id'); }); } diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index 5f4363b31..3462730ec 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -26,6 +26,10 @@ @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" href="#">Environment Variables + Scheduled Tasks + +
+ +
diff --git a/resources/views/livewire/project/shared/scheduled-task/all.blade.php b/resources/views/livewire/project/shared/scheduled-task/all.blade.php index 7a1f359ef..b860e00b1 100644 --- a/resources/views/livewire/project/shared/scheduled-task/all.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/all.blade.php @@ -8,24 +8,18 @@
@forelse($resource->scheduled_tasks as $task) type() == 'application') href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}"> + @elseif ($resource->type() == 'service') + href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}"> + @endif
{{ $task->name }}
Frequency: {{ $task->frequency }}
Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}
-
Next run: @todo
@empty
No scheduled tasks configured.
@endforelse
- - {{-- @if ($type === 'service-database' && $selectedBackup) -
- -

Executions

- -
- @endif --}} diff --git a/resources/views/livewire/project/shared/scheduled-task/executions.blade.php b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php new file mode 100644 index 000000000..9f0fd9208 --- /dev/null +++ b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php @@ -0,0 +1,27 @@ +
+ @forelse($executions as $execution) + data_get($execution, 'status') === 'success', + 'border-red-500' => data_get($execution, 'status') === 'failed', + ])> + @if (data_get($execution, 'status') === 'running') +
+ +
+ @endif +
Status: {{ data_get($execution, 'status') }}
+
Started At: {{ data_get($execution, 'created_at') }}
+ @if (data_get($execution, 'id') == $selectedKey) + @if (data_get($execution, 'message')) +
Output:
{{ data_get($execution, 'message') }}
+ @else +
No output was recorded for this execution.
+ @endif + @endif +
+ + @empty +
No executions found.
+ @endforelse +
diff --git a/resources/views/livewire/project/shared/scheduled-task/show.blade.php b/resources/views/livewire/project/shared/scheduled-task/show.blade.php index a53ed6fbb..e9ab765ee 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -7,7 +7,11 @@

Scheduled Backup

+ @if ($type === 'application') + @elseif ($type === 'service') + + @endif
@@ -17,10 +21,6 @@ Save - {{-- @if (Str::of($status)->startsWith('running')) - - @endif --}} - Delete @@ -33,4 +33,10 @@ + +
+

Recent executions

+ +
diff --git a/routes/web.php b/routes/web.php index 126ff5bef..1ee3fa4a6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -143,6 +143,8 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service.configuration'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/command', ExecuteContainerCommand::class)->name('project.service.command'); + Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.service.scheduled-tasks'); + }); Route::middleware(['auth'])->group(function () {