From adecf328fc95661e61819ef98483158d0e94cd6c Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Mon, 1 Jan 2024 10:33:16 -0800 Subject: [PATCH 01/21] WIP start of scheduled tasks. --- .../Project/Shared/ScheduledTask/Add.php | 59 ++++++++ .../Project/Shared/ScheduledTask/All.php | 134 ++++++++++++++++++ .../Project/Shared/ScheduledTask/Show.php | 60 ++++++++ app/Models/Application.php | 5 + app/Models/ScheduledTask.php | 88 ++++++++++++ ...31_173041_create_scheduled_tasks_table.php | 36 +++++ .../application/configuration.blade.php | 6 + .../shared/scheduled-task/add.blade.php | 15 ++ .../shared/scheduled-task/all.blade.php | 16 +++ .../shared/scheduled-task/show.blade.php | 21 +++ 10 files changed, 440 insertions(+) create mode 100644 app/Livewire/Project/Shared/ScheduledTask/Add.php create mode 100644 app/Livewire/Project/Shared/ScheduledTask/All.php create mode 100644 app/Livewire/Project/Shared/ScheduledTask/Show.php create mode 100644 app/Models/ScheduledTask.php create mode 100644 database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php create mode 100644 resources/views/livewire/project/shared/scheduled-task/add.blade.php create mode 100644 resources/views/livewire/project/shared/scheduled-task/all.blade.php create mode 100644 resources/views/livewire/project/shared/scheduled-task/show.blade.php diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php new file mode 100644 index 000000000..84f790ad4 --- /dev/null +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -0,0 +1,59 @@ + 'clear']; + protected $rules = [ + 'name' => 'required|string', + 'command' => 'required|string', + 'frequency' => 'required|string', + 'container' => 'nullable|string', + ]; + protected $validationAttributes = [ + 'name' => 'name', + 'command' => 'command', + 'frequency' => 'frequency', + 'container' => 'container', + ]; + + public function mount() + { + $this->parameters = get_route_parameters(); + } + + public function submit() + { + error_log("*** IN SUBMIT"); + $this->validate(); + $isValid = validate_cron_expression($this->frequency); + if (!$isValid) { + $this->dispatch('error', 'Invalid Cron / Human expression.'); + return; + } + $this->dispatch('saveScheduledTask', [ + 'name' => $this->name, + 'command' => $this->command, + 'frequency' => $this->frequency, + 'container' => $this->container, + ]); + $this->clear(); + } + + public function clear() + { + $this->name = ''; + $this->command = ''; + $this->frequency = ''; + $this->container = ''; + } +} diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php new file mode 100644 index 000000000..1562a4cae --- /dev/null +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -0,0 +1,134 @@ + 'submit']; + + public function mount() + { + $resourceClass = get_class($this->resource); + $resourceWithPreviews = ['App\Models\Application']; + $simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile')); + if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) { + $this->showPreview = true; + } + $this->modalId = new Cuid2(7); + $this->getDevView(); + } + public function getDevView() + { + $this->variables = $this->resource->scheduled_tasks->map(function ($item) { + error_log("** got one"); + return "$item->name=$item->command"; + })->sort(); + + error_log(print_r($this->variables,1)); + } + public function saveVariables($isPreview) + { + if ($isPreview) { + $variables = parseEnvFormatToArray($this->variablesPreview); + $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete(); + } else { + $variables = parseEnvFormatToArray($this->variables); + $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); + } + foreach ($variables as $key => $variable) { + if ($isPreview) { + $found = $this->resource->environment_variables_preview()->where('key', $key)->first(); + } else { + $found = $this->resource->environment_variables()->where('key', $key)->first(); + } + if ($found) { + if ($found->is_shown_once) { + continue; + } + $found->value = $variable; + $found->save(); + continue; + } else { + $task = new ScheduledTask(); + $task->key = $key; + $task->value = $variable; + $task->is_build_time = false; + $task->is_preview = $isPreview ? true : false; + switch ($this->resource->type()) { + case 'application': + $task->application_id = $this->resource->id; + break; + case 'standalone-postgresql': + $task->standalone_postgresql_id = $this->resource->id; + break; + case 'standalone-redis': + $task->standalone_redis_id = $this->resource->id; + break; + case 'standalone-mongodb': + $task->standalone_mongodb_id = $this->resource->id; + break; + case 'standalone-mysql': + $task->standalone_mysql_id = $this->resource->id; + break; + case 'standalone-mariadb': + $task->standalone_mariadb_id = $this->resource->id; + break; + case 'service': + $task->service_id = $this->resource->id; + break; + } + $task->save(); + } + } + if ($isPreview) { + $this->dispatch('success', 'Preview environment variables updated successfully.'); + } else { + $this->dispatch('success', 'Environment variables updated successfully.'); + } + $this->refreshTasks(); + } + public function refreshTasks() + { + $this->resource->refresh(); + $this->getDevView(); + } + + public function submit($data) + { + error_log("** submitting the beast"); + try { + $task = new ScheduledTask(); + $task->name = $data['name']; + $task->command = $data['command']; + $task->frequency = $data['frequency']; + $task->container = $data['container']; + + switch ($this->resource->type()) { + case 'application': + $task->application_id = $this->resource->id; + break; + case 'standalone-postgresql': + $task->standalone_postgresql_id = $this->resource->id; + break; + case 'service': + $task->service_id = $this->resource->id; + break; + } + $task->save(); + $this->refreshTasks(); + $this->dispatch('success', 'Scheduled task added successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } +} diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php new file mode 100644 index 000000000..b65c36531 --- /dev/null +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -0,0 +1,60 @@ + 'required|string', + 'task.command' => 'required|string', + ]; + protected $validationAttributes = [ + 'name' => 'Name', + 'command' => 'Command', + ]; + + public function mount() + { + $this->modalId = new Cuid2(7); + $this->parameters = get_route_parameters(); + } + + public function lock() + { + $this->task->is_shown_once = true; + $this->task->save(); + $this->dispatch('refreshTasks'); + } + public function instantSave() + { + $this->submit(); + } + public function submit() + { + $this->validate(); + $this->task->save(); + $this->dispatch('success', 'Environment variable updated successfully.'); + $this->dispatch('refreshTasks'); + } + + public function delete() + { + try { + $this->task->delete(); + $this->dispatch('refreshTasks'); + } catch (\Exception $e) { + return handleError($e); + } + } +} diff --git a/app/Models/Application.php b/app/Models/Application.php index 018d6ec91..fc1b7d500 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -315,6 +315,11 @@ public function nixpacks_environment_variables_preview(): HasMany return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%'); } + public function scheduled_tasks(): HasMany + { + return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); + } + public function private_key() { return $this->belongsTo(PrivateKey::class); diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php new file mode 100644 index 000000000..79468dd2a --- /dev/null +++ b/app/Models/ScheduledTask.php @@ -0,0 +1,88 @@ + 'string', + 'command' => 'string', + 'frequency' => 'string', + 'container' => 'string', + ]; + + // protected static function booted() + // { + // static::created(function ($scheduled_task) { + // error_log("*** IN CREATED"); + // if ($scheduled_task->application_id) { + // $found = ModelsScheduledTask::where('id', $scheduled_task->id)->where('application_id', $scheduled_task->application_id)->first(); + // $application = Application::find($scheduled_task->application_id); + // if (!$found) { + // ModelsScheduledTask::create([ + // 'name' => $scheduled_task->name, + // 'command' => $scheduled_task->command, + // 'frequency' => $scheduled_task->frequency, + // 'container' => $scheduled_task->container, + // 'application_id' => $scheduled_task->application_id, + // ]); + // } + // } + // }); + // } + public function service() + { + return $this->belongsTo(Service::class); + } + // protected function value(): Attribute + // { + // return Attribute::make( + // get: fn (?string $value = null) => $this->get_scheduled_tasks($value), + // set: fn (?string $value = null) => $this->set_scheduled_tasks($value), + // ); + // } + + private function get_scheduled_tasks(?string $scheduled_task = null): string|null + { + error_log("** in get_scheduled_tasks"); + // // $team_id = currentTeam()->id; + // if (!$scheduled_task) { + // return null; + // } + // $scheduled_task = trim(decrypt($scheduled_task)); + // if (Str::startsWith($scheduled_task, '{{') && Str::endsWith($scheduled_task, '}}') && Str::contains($scheduled_task, 'global.')) { + // $variable = Str::after($scheduled_task, 'global.'); + // $variable = Str::before($variable, '}}'); + // $variable = Str::of($variable)->trim()->value; + // // $scheduled_task = GlobalScheduledTask::where('name', $scheduled_task)->where('team_id', $team_id)->first()?->value; + // ray('global env variable'); + // return $scheduled_task; + // } + // return $scheduled_task; + } + + private function set_scheduled_tasks(?string $scheduled_task = null): string|null + { + error_log("** in set_scheduled_tasks"); + // if (is_null($scheduled_task) && $scheduled_task == '') { + // return null; + // } + // $scheduled_task = trim($scheduled_task); + // return encrypt($scheduled_task); + } + + // protected function key(): Attribute + // { + // error_log("** in key()"); + + // // return Attribute::make( + // // set: fn (string $value) => Str::of($value)->trim(), + // // ); + // } +} 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 new file mode 100644 index 000000000..b108cecf8 --- /dev/null +++ b/database/migrations/2023_12_31_173041_create_scheduled_tasks_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('uuid')->unique(); + $table->boolean('enabled')->default(true); + $table->string('name'); + $table->string('command'); + $table->string('frequency'); + $table->string('container')->nullable(); + $table->timestamps(); + + $table->foreignId('application_id')->nullable(); + $table->foreignId('service_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('scheduled_tasks'); + } +}; diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 5d2936649..61fbc6c66 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -54,6 +54,9 @@ href="#">Resource Limits @endif + Scheduled Tasks + Danger Zone @@ -97,6 +100,9 @@
+
+ +
diff --git a/resources/views/livewire/project/shared/scheduled-task/add.blade.php b/resources/views/livewire/project/shared/scheduled-task/add.blade.php new file mode 100644 index 000000000..133c8cead --- /dev/null +++ b/resources/views/livewire/project/shared/scheduled-task/add.blade.php @@ -0,0 +1,15 @@ + + diff --git a/resources/views/livewire/project/shared/scheduled-task/all.blade.php b/resources/views/livewire/project/shared/scheduled-task/all.blade.php new file mode 100644 index 000000000..1c42cdc00 --- /dev/null +++ b/resources/views/livewire/project/shared/scheduled-task/all.blade.php @@ -0,0 +1,16 @@ +
+
+
+

Scheduled Tasks

+ + Add + +
+
Scheduled Tasks for this resource.
+
+ @forelse ($resource->scheduled_tasks as $task) + + @empty +
No scheduled tasks 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 new file mode 100644 index 000000000..d3a9bce47 --- /dev/null +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -0,0 +1,21 @@ +
+ + +

Are you sure you want to delete this scheduled task ({{ $task->name }})?

+
+
+
+ + +
+ + Update + + + Delete + +
+ +
From 7913a639b54ec5a385f84f9b4ef67fd957158d36 Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Mon, 1 Jan 2024 18:23:29 -0800 Subject: [PATCH 02/21] Complete add/edit/delete for scheduled tasks. Refactor views. --- app/Console/Kernel.php | 30 +++++++ .../Project/Shared/ScheduledTask/Add.php | 1 - .../Project/Shared/ScheduledTask/All.php | 83 +------------------ .../Project/Shared/ScheduledTask/Show.php | 46 ++++++---- app/Models/ScheduledTask.php | 80 +++--------------- .../shared/scheduled-task/all.blade.php | 43 ++++++---- .../shared/scheduled-task/show.blade.php | 37 ++++++--- routes/web.php | 3 + 8 files changed, 129 insertions(+), 194 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 2a8e857e2..267572b39 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -5,12 +5,14 @@ use App\Jobs\CheckLogDrainContainerJob; use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\DatabaseBackupJob; +use App\Jobs\ScheduledTaskJob; use App\Jobs\InstanceAutoUpdateJob; use App\Jobs\ContainerStatusJob; use App\Jobs\PullHelperImageJob; use App\Jobs\ServerStatusJob; use App\Models\InstanceSettings; use App\Models\ScheduledDatabaseBackup; +use App\Models\ScheduledTask; use App\Models\Server; use App\Models\Team; use Illuminate\Console\Scheduling\Schedule; @@ -30,6 +32,7 @@ protected function schedule(Schedule $schedule): void $this->check_resources($schedule); $this->check_scheduled_backups($schedule); $this->pull_helper_image($schedule); + $this->check_scheduled_tasks($schedule); } else { // Instance Jobs $schedule->command('horizon:snapshot')->everyFiveMinutes(); @@ -41,6 +44,7 @@ protected function schedule(Schedule $schedule): void $this->check_scheduled_backups($schedule); $this->check_resources($schedule); $this->pull_helper_image($schedule); + $this->check_scheduled_tasks($schedule); } } private function pull_helper_image($schedule) @@ -107,6 +111,32 @@ private function check_scheduled_backups($schedule) } } + private function check_scheduled_tasks($schedule) { + $scheduled_tasks = ScheduledTask::all(); + if ($scheduled_tasks->isEmpty()) { + ray('no scheduled tasks'); + return; + } + foreach ($scheduled_tasks as $scheduled_task) { + $service = $scheduled_task->service()->get(); + $application = $scheduled_task->application()->get(); + + if (!$application && !$service) { + ray('application/service attached to scheduled task does not exist'); + $scheduled_task->delete(); + continue; + } + + if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { + $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; + } + $schedule->job(new ScheduledTaskJob( + task: $scheduled_task + ))->cron($scheduled_task->frequency)->onOneServer(); + } + + } + protected function commands(): void { $this->load(__DIR__ . '/Commands'); diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index 84f790ad4..3cc5428b8 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -33,7 +33,6 @@ public function mount() public function submit() { - error_log("*** IN SUBMIT"); $this->validate(); $isValid = validate_cron_expression($this->frequency); if (!$isValid) { diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 1562a4cae..7f61c30cc 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -10,102 +10,23 @@ class All extends Component { public $resource; - public bool $showPreview = false; public string|null $modalId = null; public ?string $variables = null; - public ?string $variablesPreview = null; + public array $parameters; protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit']; public function mount() { - $resourceClass = get_class($this->resource); - $resourceWithPreviews = ['App\Models\Application']; - $simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile')); - if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) { - $this->showPreview = true; - } + $this->parameters = get_route_parameters(); $this->modalId = new Cuid2(7); - $this->getDevView(); - } - public function getDevView() - { - $this->variables = $this->resource->scheduled_tasks->map(function ($item) { - error_log("** got one"); - return "$item->name=$item->command"; - })->sort(); - - error_log(print_r($this->variables,1)); - } - public function saveVariables($isPreview) - { - if ($isPreview) { - $variables = parseEnvFormatToArray($this->variablesPreview); - $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete(); - } else { - $variables = parseEnvFormatToArray($this->variables); - $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); - } - foreach ($variables as $key => $variable) { - if ($isPreview) { - $found = $this->resource->environment_variables_preview()->where('key', $key)->first(); - } else { - $found = $this->resource->environment_variables()->where('key', $key)->first(); - } - if ($found) { - if ($found->is_shown_once) { - continue; - } - $found->value = $variable; - $found->save(); - continue; - } else { - $task = new ScheduledTask(); - $task->key = $key; - $task->value = $variable; - $task->is_build_time = false; - $task->is_preview = $isPreview ? true : false; - switch ($this->resource->type()) { - case 'application': - $task->application_id = $this->resource->id; - break; - case 'standalone-postgresql': - $task->standalone_postgresql_id = $this->resource->id; - break; - case 'standalone-redis': - $task->standalone_redis_id = $this->resource->id; - break; - case 'standalone-mongodb': - $task->standalone_mongodb_id = $this->resource->id; - break; - case 'standalone-mysql': - $task->standalone_mysql_id = $this->resource->id; - break; - case 'standalone-mariadb': - $task->standalone_mariadb_id = $this->resource->id; - break; - case 'service': - $task->service_id = $this->resource->id; - break; - } - $task->save(); - } - } - if ($isPreview) { - $this->dispatch('success', 'Preview environment variables updated successfully.'); - } else { - $this->dispatch('success', 'Environment variables updated successfully.'); - } - $this->refreshTasks(); } public function refreshTasks() { $this->resource->refresh(); - $this->getDevView(); } public function submit($data) { - error_log("** submitting the beast"); try { $task = new ScheduledTask(); $task->name = $data['name']; diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index b65c36531..23cb0e41a 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -4,47 +4,52 @@ use App\Models\ScheduledTask as ModelsScheduledTask; use Livewire\Component; +use App\Models\Application; +use App\Models\Service; use Visus\Cuid2\Cuid2; class Show extends Component { public $parameters; + public Application|Service $resource; public ModelsScheduledTask $task; public ?string $modalId = null; - public bool $isDisabled = false; - public bool $isLocked = false; public string $type; protected $rules = [ 'task.name' => 'required|string', 'task.command' => 'required|string', + 'task.frequency' => 'required|string', + 'task.container' => 'nullable|string', ]; protected $validationAttributes = [ - 'name' => 'Name', - 'command' => 'Command', + 'name' => 'name', + 'command' => 'command', + 'frequency' => 'frequency', + 'container' => 'container', ]; public function mount() { - $this->modalId = new Cuid2(7); $this->parameters = get_route_parameters(); + + if (data_get($this->parameters, 'application_uuid')) { + $this->type = 'application'; + $this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail(); + } else if (data_get($this->parameters, 'service_uuid')) { + $this->type = 'service'; + $this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail(); + } + + $this->modalId = new Cuid2(7); + $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first(); } - public function lock() - { - $this->task->is_shown_once = true; - $this->task->save(); - $this->dispatch('refreshTasks'); - } - public function instantSave() - { - $this->submit(); - } public function submit() { $this->validate(); $this->task->save(); - $this->dispatch('success', 'Environment variable updated successfully.'); + $this->dispatch('success', 'Scheduled task updated successfully.'); $this->dispatch('refreshTasks'); } @@ -52,7 +57,14 @@ public function delete() { try { $this->task->delete(); - $this->dispatch('refreshTasks'); + + if ($this->type == 'application') { + return redirect()->route('project.application.configuration', $this->parameters); + } + else { + return redirect()->route('project.service.configuration', $this->parameters); + } + } catch (\Exception $e) { return handleError($e); } diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index 79468dd2a..2ff391c59 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -2,87 +2,27 @@ namespace App\Models; -use App\Models\ScheduledTask as ModelsScheduledTask; -use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Str; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; class ScheduledTask extends BaseModel { protected $guarded = []; - protected $casts = [ - 'name' => 'string', - 'command' => 'string', - 'frequency' => 'string', - 'container' => 'string', - ]; - // protected static function booted() - // { - // static::created(function ($scheduled_task) { - // error_log("*** IN CREATED"); - // if ($scheduled_task->application_id) { - // $found = ModelsScheduledTask::where('id', $scheduled_task->id)->where('application_id', $scheduled_task->application_id)->first(); - // $application = Application::find($scheduled_task->application_id); - // if (!$found) { - // ModelsScheduledTask::create([ - // 'name' => $scheduled_task->name, - // 'command' => $scheduled_task->command, - // 'frequency' => $scheduled_task->frequency, - // 'container' => $scheduled_task->container, - // 'application_id' => $scheduled_task->application_id, - // ]); - // } - // } - // }); - // } public function service() { return $this->belongsTo(Service::class); } - // protected function value(): Attribute - // { - // return Attribute::make( - // get: fn (?string $value = null) => $this->get_scheduled_tasks($value), - // set: fn (?string $value = null) => $this->set_scheduled_tasks($value), - // ); - // } - - private function get_scheduled_tasks(?string $scheduled_task = null): string|null + public function application() { - error_log("** in get_scheduled_tasks"); - // // $team_id = currentTeam()->id; - // if (!$scheduled_task) { - // return null; - // } - // $scheduled_task = trim(decrypt($scheduled_task)); - // if (Str::startsWith($scheduled_task, '{{') && Str::endsWith($scheduled_task, '}}') && Str::contains($scheduled_task, 'global.')) { - // $variable = Str::after($scheduled_task, 'global.'); - // $variable = Str::before($variable, '}}'); - // $variable = Str::of($variable)->trim()->value; - // // $scheduled_task = GlobalScheduledTask::where('name', $scheduled_task)->where('team_id', $team_id)->first()?->value; - // ray('global env variable'); - // return $scheduled_task; - // } - // return $scheduled_task; + return $this->belongsTo(Application::class); } - - private function set_scheduled_tasks(?string $scheduled_task = null): string|null + public function latest_log(): HasOne { - error_log("** in set_scheduled_tasks"); - // if (is_null($scheduled_task) && $scheduled_task == '') { - // return null; - // } - // $scheduled_task = trim($scheduled_task); - // return encrypt($scheduled_task); + return $this->hasOne(ScheduledTaskExecution::class)->latest(); + } + public function executions(): HasMany + { + return $this->hasMany(ScheduledTaskExecution::class); } - - // protected function key(): Attribute - // { - // error_log("** in key()"); - - // // return Attribute::make( - // // set: fn (string $value) => Str::of($value)->trim(), - // // ); - // } } 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 1c42cdc00..7a1f359ef 100644 --- a/resources/views/livewire/project/shared/scheduled-task/all.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/all.blade.php @@ -1,16 +1,31 @@ -
-
-
-

Scheduled Tasks

- + Add - -
-
Scheduled Tasks for this resource.
+
+
+

Scheduled Tasks

+ + Add +
- @forelse ($resource->scheduled_tasks as $task) - - @empty -
No scheduled tasks found.
- @endforelse + +
+ @forelse($resource->scheduled_tasks as $task) + +
{{ $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/show.blade.php b/resources/views/livewire/project/shared/scheduled-task/show.blade.php index d3a9bce47..a53ed6fbb 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -5,17 +5,32 @@ class="font-bold text-warning">({{ $task->name }})?

-
- - -
- - Update - - - Delete - + +

Scheduled Backup

+ + + +
+
+

Scheduled Task

+ + Save + + + {{-- @if (Str::of($status)->startsWith('running')) + + @endif --}} + + + Delete + + +
+ + + + +
diff --git a/routes/web.php b/routes/web.php index ffd907344..126ff5bef 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,6 +16,7 @@ use App\Livewire\Project\EnvironmentEdit; use App\Livewire\Project\Shared\ExecuteContainerCommand; use App\Livewire\Project\Shared\Logs; +use App\Livewire\Project\Shared\ScheduledTask\Show as ScheduledTaskShow; use App\Livewire\Security\ApiTokens; use App\Livewire\Server\All; use App\Livewire\Server\Create; @@ -127,6 +128,8 @@ Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/logs', Logs::class)->name('project.application.logs'); Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/command', ExecuteContainerCommand::class)->name('project.application.command'); + Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/tasks/{task_uuid}', ScheduledTaskShow::class)->name('project.application.scheduled-tasks'); + // Databases Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration'); From 9bbe9567c71b9a6baed9408357d47a3816590a5b Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Mon, 1 Jan 2024 18:23:58 -0800 Subject: [PATCH 03/21] Start scheduled task job execution. --- app/Jobs/ScheduledTaskJob.php | 77 +++++++++++++++++++ app/Models/ScheduledTaskExecution.php | 15 ++++ ...create_scheduled_task_executions_table.php | 31 ++++++++ 3 files changed, 123 insertions(+) create mode 100644 app/Jobs/ScheduledTaskJob.php create mode 100644 app/Models/ScheduledTaskExecution.php create mode 100644 database/migrations/2024_01_01_231053_create_scheduled_task_executions_table.php diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php new file mode 100644 index 000000000..f810e746a --- /dev/null +++ b/app/Jobs/ScheduledTaskJob.php @@ -0,0 +1,77 @@ +task = $task; + if ($service = $task->service()->first()) { + $this->resource = $service; + } else if ($application = $task->application()->first()) { + $this->resource = $application; + } + } + + public function middleware(): array + { + return [new WithoutOverlapping($this->task->id)]; + } + + public function uniqueId(): int + { + return $this->task->id; + } + + 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); + } catch (\Throwable $e) { + 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/Models/ScheduledTaskExecution.php b/app/Models/ScheduledTaskExecution.php new file mode 100644 index 000000000..de13fefb0 --- /dev/null +++ b/app/Models/ScheduledTaskExecution.php @@ -0,0 +1,15 @@ +belongsTo(ScheduledTask::class); + } +} diff --git a/database/migrations/2024_01_01_231053_create_scheduled_task_executions_table.php b/database/migrations/2024_01_01_231053_create_scheduled_task_executions_table.php new file mode 100644 index 000000000..27ace08d4 --- /dev/null +++ b/database/migrations/2024_01_01_231053_create_scheduled_task_executions_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('uuid')->unique(); + $table->enum('status', ['success', 'failed', 'running'])->default('running'); + $table->longText('message')->nullable(); + $table->foreignId('scheduled_task_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('scheduled_task_executions'); + } +}; From e2e68136329a05e0e3189da5260d575b44d9ef43 Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Fri, 5 Jan 2024 15:06:36 +1000 Subject: [PATCH 04/21] 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 @@ 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; } } } 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 @@ 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': 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 @@ 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'); 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 @@ public function up(): void $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 @@
- - {{-- @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 @@ + 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 @@ class="font-bold text-warning">({{ $task->name }})?

Scheduled Backup

+ @if ($type === 'application') + @elseif ($type === 'service') + + @endif
@@ -17,10 +21,6 @@ class="font-bold text-warning">({{ $task->name }})?

Save - {{-- @if (Str::of($status)->startsWith('running')) - - @endif --}} - Delete @@ -33,4 +33,10 @@ class="font-bold text-warning">({{ $task->name }})?

+ +
+

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::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 () { From 512197021bdfdeabda0fabc0f669564368a57abf Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Fri, 5 Jan 2024 15:26:51 +1000 Subject: [PATCH 05/21] Minor cleanup. --- app/Livewire/Project/Shared/ScheduledTask/Executions.php | 1 - .../views/livewire/project/shared/scheduled-task/show.blade.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php index 5351c3d4c..9c1ec7cc5 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php @@ -7,7 +7,6 @@ class Executions extends Component { - public $backup; public $executions = []; public $selectedKey; public function getListeners() 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 e9ab765ee..cbeb971b1 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -6,7 +6,7 @@ class="font-bold text-warning">({{ $task->name }})?

-

Scheduled Backup

+

Scheduled Task

@if ($type === 'application') @elseif ($type === 'service') From 557e1407d0f7b3ca62af64e3cb12be5b0bb18bcb Mon Sep 17 00:00:00 2001 From: Stuart Rowlands Date: Sat, 6 Jan 2024 15:24:57 +1000 Subject: [PATCH 06/21] Added database import feature. --- app/Livewire/Project/Database/Import.php | 129 ++++++++++++++++++ bootstrap/helpers/remoteProcess.php | 41 ++++++ config/livewire.php | 4 +- docker/dev-ssu/Dockerfile | 4 + docker/prod-ssu/Dockerfile | 5 + .../project/database/import.blade.php | 41 ++++++ .../project/database/configuration.blade.php | 8 ++ 7 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 app/Livewire/Project/Database/Import.php create mode 100644 resources/views/livewire/project/database/import.blade.php diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php new file mode 100644 index 000000000..5b7c7de88 --- /dev/null +++ b/app/Livewire/Project/Database/Import.php @@ -0,0 +1,129 @@ +parameters = get_route_parameters(); + $this->getContainers(); + } + + public function getContainers() + { + $this->containers = collect(); + if (!data_get($this->parameters, 'database_uuid')) { + abort(404); + } + + $resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + abort(404); + } + } + } + } + } + $this->resource = $resource; + $this->server = $this->resource->destination->server; + $this->container = $this->resource->uuid; + if (str(data_get($this,'resource.status'))->startsWith('running')) { + $this->containers->push($this->container); + } + + if ($this->containers->count() > 1) { + $this->validated = false; + $this->validationMsg = 'The database service has more than one container running. Cannot import.'; + } + + if ($this->resource->getMorphClass() == 'App\Models\StandaloneRedis' + || $this->resource->getMorphClass() == 'App\Models\StandaloneMongodb') { + $this->validated = false; + $this->validationMsg = 'This database type is not currently supported.'; + } + + } + + public function runImport() + { + $this->validate([ + 'file' => 'required|file|max:102400' + ]); + + $this->importRunning = true; + $this->scpInProgress = true; + + try { + $uploadedFilename = $this->file->store('backup-import'); + $path = \Storage::path($uploadedFilename); + $tmpPath = '/tmp/' . basename($uploadedFilename); + + // SCP the backup file to the server. + instant_scp($path, $tmpPath, $this->server); + $this->scpInProgress = false; + + $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; + + switch ($this->resource->getMorphClass()) { + case 'App\Models\StandaloneMariadb': + $this->importCommands[] = "docker exec {$this->container} sh -c 'mariadb -u\$MARIADB_USER -p\$MARIADB_PASSWORD \$MARIADB_DATABASE < {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + case 'App\Models\StandaloneMysql': + $this->importCommands[] = "docker exec {$this->container} sh -c 'mysql -u\$MYSQL_USER -p\$MYSQL_PASSWORD \$MYSQL_DATABASE < {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + case 'App\Models\StandalonePostgresql': + $this->importCommands[] = "docker exec {$this->container} sh -c 'pg_restore -U \$POSTGRES_USER -d \$POSTGRES_DB {$tmpPath}'"; + $this->importCommands[] = "rm {$tmpPath}"; + break; + } + + $this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$tmpPath}'"; + $this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'"; + + if (!empty($this->importCommands)) { + $activity = remote_process($this->importCommands, $this->server, ignore_errors: true); + $this->dispatch('newMonitorActivity', $activity->id); + } + } catch (\Throwable $e) { + $this->validated = false; + $this->validationMsg = $e->getMessage(); + } + + } + + +} diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 91e42c5be..f49c7cafc 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -67,6 +67,47 @@ function savePrivateKeyToFs(Server $server) return $location; } +function generateScpCommand(Server $server, string $source, string $dest) +{ + $user = $server->user; + $port = $server->port; + $privateKeyLocation = savePrivateKeyToFs($server); + $timeout = config('constants.ssh.command_timeout'); + $connectionTimeout = config('constants.ssh.connection_timeout'); + $serverInterval = config('constants.ssh.server_interval'); + + $scp_command = "timeout $timeout scp "; + $scp_command .= "-i {$privateKeyLocation} " + . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + . '-o PasswordAuthentication=no ' + . "-o ConnectTimeout=$connectionTimeout " + . "-o ServerAliveInterval=$serverInterval " + . '-o RequestTTY=no ' + . '-o LogLevel=ERROR ' + . "-P {$port} " + . "{$source} " + . "{$user}@{$server->ip}:{$dest}"; + + return $scp_command; +} +function instant_scp(string $source, string $dest, Server $server, $throwError = true) +{ + $timeout = config('constants.ssh.command_timeout'); + $scp_command = generateScpCommand($server, $source, $dest); + $process = Process::timeout($timeout)->run($scp_command); + $output = trim($process->output()); + $exitCode = $process->exitCode(); + if ($exitCode !== 0) { + if (!$throwError) { + return null; + } + return excludeCertainErrors($process->errorOutput(), $exitCode); + } + if ($output === 'null') { + $output = null; + } + return $output; +} function generateSshCommand(Server $server, string $command, bool $isMux = true) { $user = $server->user; diff --git a/config/livewire.php b/config/livewire.php index b1a3bf555..83229fcea 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -53,7 +53,9 @@ 'temporary_file_upload' => [ 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'rules' => [ // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'file', 'max:256000' + ], 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... diff --git a/docker/dev-ssu/Dockerfile b/docker/dev-ssu/Dockerfile index df98985f4..310c1265b 100644 --- a/docker/dev-ssu/Dockerfile +++ b/docker/dev-ssu/Dockerfile @@ -37,3 +37,7 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ ;fi" +RUN { \ + echo 'upload_max_filesize=256M'; \ + echo 'post_max_size=256M'; \ + } > /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile index 79c0a839c..a00db4e82 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod-ssu/Dockerfile @@ -62,3 +62,8 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ echo 'arm64' && \ curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ ;fi" + +RUN { \ + echo 'upload_max_filesize=256M'; \ + echo 'post_max_size=256M'; \ + } > /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php new file mode 100644 index 000000000..64ef28105 --- /dev/null +++ b/resources/views/livewire/project/database/import.blade.php @@ -0,0 +1,41 @@ +
+
+ + + + This is a destructive action, existing data will be replaced! +
+ + @if (!$validated) +
{{ $validationMsg }}
+ @else + @if (!$importRunning) +
+
+ + @error('file') {{ $message }} @enderror + Import +
+ +
+
+
+ @endif + @endif + + @if ($scpInProgress) +
Database backup is being copied to server..
+ @endif + +
+ +
+
diff --git a/resources/views/project/database/configuration.blade.php b/resources/views/project/database/configuration.blade.php index 49fcf5d1c..ed77a159f 100644 --- a/resources/views/project/database/configuration.blade.php +++ b/resources/views/project/database/configuration.blade.php @@ -39,6 +39,11 @@ window.location.hash = 'resource-limits'" href="#">Resource Limits + Import + +
+ +
From 5ee29c6072cb69472b8d98bc552858f679d398e3 Mon Sep 17 00:00:00 2001 From: Ray Berger Date: Sun, 7 Jan 2024 22:32:54 +0000 Subject: [PATCH 07/21] fix typos --- app/Console/Commands/Init.php | 36 +++++++++---------- app/Livewire/Project/Application/General.php | 2 +- app/Models/Server.php | 4 +-- app/Notifications/Channels/EmailChannel.php | 10 +++--- .../views/components/pricing-plans.blade.php | 4 +-- .../resources/breadcrumbs.blade.php | 2 +- .../emails/s3-connection-error.blade.php | 2 +- .../notifications/email-settings.blade.php | 2 +- .../project/application/previews.blade.php | 2 +- .../project/new/docker-compose.blade.php | 4 +-- .../project/shared/destination.blade.php | 2 +- .../views/livewire/settings/email.blade.php | 2 +- scripts/install.sh | 2 +- templates/compose/appwrite.yaml | 2 +- templates/compose/embystat.yaml | 2 +- templates/service-templates.json | 2 +- 16 files changed, 40 insertions(+), 40 deletions(-) diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index b0073e1f0..8903b435f 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -142,83 +142,83 @@ private function cleanup_stucked_resources() try { $applications = Application::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($applications as $application) { - echo "Deleting stucked application: {$application->name}\n"; + echo "Deleting stuck application: {$application->name}\n"; $application->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked application: {$e->getMessage()}\n"; + echo "Error in cleaning stuck application: {$e->getMessage()}\n"; } try { $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($postgresqls as $postgresql) { - echo "Deleting stucked postgresql: {$postgresql->name}\n"; + echo "Deleting stuck postgresql: {$postgresql->name}\n"; $postgresql->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked postgresql: {$e->getMessage()}\n"; + echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n"; } try { $redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($redis as $redis) { - echo "Deleting stucked redis: {$redis->name}\n"; + echo "Deleting stuck redis: {$redis->name}\n"; $redis->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked redis: {$e->getMessage()}\n"; + echo "Error in cleaning stuck redis: {$e->getMessage()}\n"; } try { $mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($mongodbs as $mongodb) { - echo "Deleting stucked mongodb: {$mongodb->name}\n"; + echo "Deleting stuck mongodb: {$mongodb->name}\n"; $mongodb->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked mongodb: {$e->getMessage()}\n"; + echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n"; } try { $mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($mysqls as $mysql) { - echo "Deleting stucked mysql: {$mysql->name}\n"; + echo "Deleting stuck mysql: {$mysql->name}\n"; $mysql->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked mysql: {$e->getMessage()}\n"; + echo "Error in cleaning stuck mysql: {$e->getMessage()}\n"; } try { $mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($mariadbs as $mariadb) { - echo "Deleting stucked mariadb: {$mariadb->name}\n"; + echo "Deleting stuck mariadb: {$mariadb->name}\n"; $mariadb->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked mariadb: {$e->getMessage()}\n"; + echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n"; } try { $services = Service::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($services as $service) { - echo "Deleting stucked service: {$service->name}\n"; + echo "Deleting stuck service: {$service->name}\n"; $service->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked service: {$e->getMessage()}\n"; + echo "Error in cleaning stuck service: {$e->getMessage()}\n"; } try { $serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($serviceApps as $serviceApp) { - echo "Deleting stucked serviceapp: {$serviceApp->name}\n"; + echo "Deleting stuck serviceapp: {$serviceApp->name}\n"; $serviceApp->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n"; + echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; } try { $serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($serviceDbs as $serviceDb) { - echo "Deleting stucked serviceapp: {$serviceDb->name}\n"; + echo "Deleting stuck serviceapp: {$serviceDb->name}\n"; $serviceDb->forceDelete(); } } catch (\Throwable $e) { - echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n"; + echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; } // Cleanup any resources that are not attached to any environment or destination or server diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index f54a46e09..919167afe 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -201,7 +201,7 @@ public function resetDefaultLabels($showToaster = true) public function updatedApplicationFqdn() { $this->resetDefaultLabels(false); - $this->dispatch('success', 'Labels reseted to default!'); + $this->dispatch('success', 'Labels reset to default!'); } public function submit($showToaster = true) { diff --git a/app/Models/Server.php b/app/Models/Server.php index 291a9d479..e8e0332e0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -358,9 +358,9 @@ public function isLogDrainEnabled() public function validateOS(): bool | Stringable { $os_release = instant_remote_process(['cat /etc/os-release'], $this); - $datas = collect(explode("\n", $os_release)); + $data = collect(explode("\n", $os_release)); $collectedData = collect([]); - foreach ($datas as $data) { + foreach ($data as $data) { $item = Str::of($data)->trim(); $collectedData->put($item->before('=')->value(), $item->after('=')->lower()->replace('"', '')->value()); } diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index 99afcf5a7..da8ef812e 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -14,8 +14,8 @@ public function send(SendsEmail $notifiable, Notification $notification): void { try { $this->bootConfigs($notifiable); - $recepients = $notifiable->getRecepients($notification); - if (count($recepients) === 0) { + $recipients = $notifiable->getRecepients($notification); + if (count($recipients) === 0) { throw new Exception('No email recipients found'); } @@ -24,7 +24,7 @@ public function send(SendsEmail $notifiable, Notification $notification): void [], [], fn (Message $message) => $message - ->to($recepients) + ->to($recipients) ->subject($mailMessage->subject) ->html((string)$mailMessage->render()) ); @@ -35,8 +35,8 @@ public function send(SendsEmail $notifiable, Notification $notification): void } ray($e->getMessage()); $message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:"; - if (isset($recepients)) { - $message .= implode(', ', $recepients); + if (isset($recipients)) { + $message .= implode(', ', $recipients); } if (isset($mailMessage)) { $message .= " with subject: {$mailMessage->subject}"; diff --git a/resources/views/components/pricing-plans.blade.php b/resources/views/components/pricing-plans.blade.php index 7837bc0f5..3b37f6ef1 100644 --- a/resources/views/components/pricing-plans.blade.php +++ b/resources/views/components/pricing-plans.blade.php @@ -302,7 +302,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
Once you connected your server, Coolify will start managing it and do a - lot of adminstrative tasks for you. You can also write your own scripts to + lot of administrative tasks for you. You can also write your own scripts to automate your server*.
@@ -384,7 +384,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
Powerful API
- Programatically deploy, query, and manage your servers & resources. + Programmatically deploy, query, and manage your servers & resources. Integrate to your CI/CD pipelines, or build your own custom integrations. *
diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index b7e24eb61..8291fd904 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -1,7 +1,7 @@