Functional scheduled executions.

Display last executions.
Support for Services.
This commit is contained in:
Stuart Rowlands 2024-01-05 15:06:36 +10:00
parent 9bbe9567c7
commit e2e6813632
10 changed files with 144 additions and 36 deletions

View File

@ -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 @@ public function __construct($task)
} else if ($application = $task->application()->first()) {
$this->resource = $application;
}
$this->team = Team::find($task->team_id);
}
public function middleware(): array
@ -55,23 +54,62 @@ public function uniqueId(): int
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;
}
}
}

View File

@ -33,6 +33,7 @@ public function submit($data)
$task->command = $data['command'];
$task->frequency = $data['frequency'];
$task->container = $data['container'];
$task->team_id = currentTeam()->id;
switch ($this->resource->type()) {
case 'application':

View 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;
}
}

View File

@ -396,6 +396,10 @@ public function byName(string $name)
}
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');

View File

@ -23,6 +23,7 @@ public function up(): void
$table->foreignId('application_id')->nullable();
$table->foreignId('service_id')->nullable();
$table->foreignId('team_id');
});
}

View File

@ -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 @@ class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
<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>

View File

@ -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>

View File

@ -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>

View File

@ -7,7 +7,11 @@ class="font-bold text-warning">({{ $task->name }})</span>?</p>
</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 @@ class="font-bold text-warning">({{ $task->name }})</span>?</p>
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 @@ class="font-bold text-warning">({{ $task->name }})</span>?</p>
<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>

View File

@ -143,6 +143,8 @@
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 () {