Functional scheduled executions.
Display last executions. Support for Services.
This commit is contained in:
		
							parent
							
								
									9bbe9567c7
								
							
						
					
					
						commit
						e2e6813632
					
				| @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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': | ||||
|  | ||||
							
								
								
									
										28
									
								
								app/Livewire/Project/Shared/ScheduledTask/Executions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/Livewire/Project/Shared/ScheduledTask/Executions.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace App\Livewire\Project\Shared\ScheduledTask; | ||||
| 
 | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Livewire\Component; | ||||
| 
 | ||||
| class Executions extends Component | ||||
| { | ||||
|     public $backup; | ||||
|     public $executions = []; | ||||
|     public $selectedKey; | ||||
|     public function getListeners() | ||||
|     { | ||||
|         return [ | ||||
|             "selectTask", | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function selectTask($key): void | ||||
|     { | ||||
|         if ($key == $this->selectedKey) { | ||||
|             $this->selectedKey = null; | ||||
|             return; | ||||
|         } | ||||
|         $this->selectedKey = $key; | ||||
|     } | ||||
| } | ||||
| @ -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'); | ||||
|  | ||||
| @ -23,6 +23,7 @@ return new class extends Migration | ||||
| 
 | ||||
|             $table->foreignId('application_id')->nullable(); | ||||
|             $table->foreignId('service_id')->nullable(); | ||||
|             $table->foreignId('team_id'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,10 @@ | ||||
|                 @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'" | ||||
|                 href="#">Environment | ||||
|                 Variables</a> | ||||
|             <a :class="activeTab === 'scheduled-tasks' && 'text-white'" | ||||
|                 @click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'" | ||||
|                 href="#">Scheduled Tasks | ||||
|             </a> | ||||
|             <a :class="activeTab === 'danger' && 'text-white'" | ||||
|                 @click.prevent="activeTab = 'danger';
 | ||||
|                 window.location.hash = 'danger'" | ||||
| @ -161,6 +165,9 @@ | ||||
|                     <livewire:project.shared.environment-variable.all :resource="$service" /> | ||||
|                 </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 === 'danger'"> | ||||
|                 <livewire:project.shared.danger :resource="$service" /> | ||||
|             </div> | ||||
|  | ||||
| @ -8,24 +8,18 @@ | ||||
|     <div class="flex flex-wrap gap-2"> | ||||
|         @forelse($resource->scheduled_tasks as $task) | ||||
|             <a class="flex flex-col box" | ||||
| 
 | ||||
|                 @if ($resource->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 | ||||
|                 <div><span class="font-bold text-warning">{{ $task->name }}<span></div> | ||||
|                 <div>Frequency: {{ $task->frequency }}</div> | ||||
|                 <div>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}</div> | ||||
|                 <div>Next run: @todo</div> | ||||
|             </a> | ||||
|         @empty | ||||
|             <div>No scheduled tasks configured.</div> | ||||
|         @endforelse | ||||
|     </div> | ||||
| 
 | ||||
|     {{-- @if ($type === 'service-database' && $selectedBackup) | ||||
|         <div class="pt-10"> | ||||
|             <livewire:project.database.backup-edit key="{{ $selectedBackup->id }}" :backup="$selectedBackup" :s3s="$s3s" | ||||
|                 :status="data_get($database, 'status')" /> | ||||
|             <h3 class="py-4">Executions</h3> | ||||
|             <livewire:project.database.backup-executions key="{{ $selectedBackup->id }}" :backup="$selectedBackup" | ||||
|                 :executions="$selectedBackup->executions" /> | ||||
|         </div> | ||||
|     @endif --}} | ||||
| </div> | ||||
|  | ||||
| @ -0,0 +1,27 @@ | ||||
| <div class="flex flex-col-reverse gap-2"> | ||||
|     @forelse($executions as $execution) | ||||
|             <a class="flex flex-col box" wire:click="selectTask({{ data_get($execution, 'id') }})" | ||||
|                 @class([ | ||||
|                     'border-green-500' => data_get($execution, 'status') === 'success', | ||||
|                     'border-red-500' => data_get($execution, 'status') === 'failed', | ||||
|                 ])> | ||||
|                 @if (data_get($execution, 'status') === 'running') | ||||
|                 <div class="absolute top-2 right-2"> | ||||
|                     <x-loading /> | ||||
|                 </div> | ||||
|                 @endif | ||||
|                 <div>Status: {{ data_get($execution, 'status') }}</div> | ||||
|                 <div>Started At: {{ data_get($execution, 'created_at') }}</div> | ||||
|                 @if (data_get($execution, 'id') == $selectedKey) | ||||
|                     @if (data_get($execution, 'message')) | ||||
|                         <div>Output: <pre>{{ data_get($execution, 'message') }}</pre></div> | ||||
|                     @else | ||||
|                         <div>No output was recorded for this execution.</div> | ||||
|                     @endif | ||||
|                 @endif | ||||
|             </a> | ||||
|         </a> | ||||
|     @empty | ||||
|         <div>No executions found.</div> | ||||
|     @endforelse | ||||
| </div> | ||||
| @ -7,7 +7,11 @@ | ||||
|     </x-modal> | ||||
| 
 | ||||
|     <h1>Scheduled Backup</h1> | ||||
|     @if ($type === 'application') | ||||
|     <livewire:project.application.heading :application="$resource" /> | ||||
|     @elseif ($type === 'service') | ||||
|     <livewire:project.service.navbar :service="$resource" :parameters="$parameters" /> | ||||
|     @endif | ||||
| 
 | ||||
|     <form wire:submit="submit"> | ||||
|         <div class="flex flex-col gap-2 pb-10"> | ||||
| @ -17,10 +21,6 @@ | ||||
|                     Save | ||||
|                 </x-forms.button> | ||||
| 
 | ||||
|                 {{-- @if (Str::of($status)->startsWith('running')) | ||||
|                     <livewire:project.database.backup-now :backup="$backup" /> | ||||
|                 @endif --}} | ||||
| 
 | ||||
|                 <x-forms.button isError isModal modalId="{{ $modalId }}"> | ||||
|                     Delete | ||||
|                 </x-forms.button> | ||||
| @ -33,4 +33,10 @@ | ||||
|         <x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required /> | ||||
|         <x-forms.input placeholder="php" id="task.container" label="Container name" /> | ||||
|     </form> | ||||
| 
 | ||||
|     <div class="pt-10"> | ||||
|         <h3 class="py-4">Recent executions</h3> | ||||
|         <livewire:project.shared.scheduled-task.executions key="{{ $task->id }}" selectedKey="" | ||||
|             :executions="$task->executions->take(-20)" /> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @ -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 () { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user