Merge branch 'next' into new-magic

This commit is contained in:
🏔️ Peak 2024-11-07 12:03:06 +01:00 committed by GitHub
commit 8aa4212281
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 244 additions and 110 deletions

View file

@ -25,6 +25,10 @@ class StartMongodb
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
if (isDev()) {
$this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name;
}
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",

View file

@ -48,7 +48,7 @@ class Kernel extends ConsoleKernel
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
$schedule->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
// Server Jobs
$this->checkResources($schedule);
@ -120,7 +120,6 @@ class Kernel extends ConsoleKernel
} else {
$servers = $this->allServers->get();
}
// $schedule->job(new \App\Jobs\ResourcesCheck)->everyMinute()->onOneServer();
foreach ($servers as $server) {
$serverTimezone = $server->settings->server_timezone;

View file

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ScheduledTaskDone implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View file

@ -3,14 +3,15 @@
namespace App\Jobs;
use App\Models\TeamInvitation;
use App\Models\Waitlist;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
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\Facades\Log;
class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
@ -18,34 +19,21 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
public function __construct() {}
// public function uniqueId(): string
// {
// return $this->container_name;
// }
public function middleware(): array
{
return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()];
}
public function handle(): void
{
try {
// $this->cleanup_waitlist();
$this->cleanupInvitationLink();
} catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
}
try {
$this->cleanup_invitation_link();
} catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
Log::error('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
}
}
private function cleanup_waitlist()
{
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get();
foreach ($waitlist as $item) {
$item->delete();
}
}
private function cleanup_invitation_link()
private function cleanupInvitationLink()
{
$invitation = TeamInvitation::all();
foreach ($invitation as $item) {

View file

@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
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\Facades\Log;
@ -23,6 +24,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public ?string $usageBefore = null;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
}
public function __construct(public Server $server, public bool $manualCleanup = false) {}
public function handle(): void

View file

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Events\ScheduledTaskDone;
use App\Models\Application;
use App\Models\ScheduledTask;
use App\Models\ScheduledTaskExecution;
@ -19,7 +20,7 @@ class ScheduledTaskJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?Team $team = null;
public Team $team;
public Server $server;
@ -47,7 +48,7 @@ class ScheduledTaskJob implements ShouldQueue
} else {
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
}
$this->team = Team::find($task->team_id);
$this->team = Team::findOrFail($task->team_id);
$this->server_timezone = $this->getServerTimezone();
}
@ -125,6 +126,7 @@ class ScheduledTaskJob implements ShouldQueue
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
} finally {
ScheduledTaskDone::dispatch($this->team->id);
}
}
}

View file

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Actions\Server\ResourcesCheck;
use App\Actions\Server\ServerCheck;
use App\Models\Server;
use Illuminate\Bus\Queueable;
@ -25,6 +26,7 @@ class ServerCheckNewJob implements ShouldBeEncrypted, ShouldQueue
{
try {
ServerCheck::run($this->server);
ResourcesCheck::dispatch($this->server);
} catch (\Throwable $e) {
return handleError($e);
}

View file

@ -36,8 +36,10 @@ class Docker extends Component
$this->servers = Server::isUsable()->get();
if ($server_id) {
$this->selectedServer = $this->servers->find($server_id);
$this->serverId = $this->selectedServer->id;
} else {
$this->selectedServer = $this->servers->first();
$this->serverId = $this->selectedServer->id;
}
$this->generateName();
}

View file

@ -2,23 +2,60 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Locked;
use Livewire\Component;
class Executions extends Component
{
public $executions = [];
public ScheduledTask $task;
public $selectedKey;
#[Locked]
public int $taskId;
public $task;
#[Locked]
public Collection $executions;
#[Locked]
public ?int $selectedKey = null;
#[Locked]
public ?string $serverTimezone = null;
public function getListeners()
{
$teamId = Auth::user()->currentTeam()->id;
return [
'selectTask',
"echo-private:team.{$teamId},ScheduledTaskDone" => 'refreshExecutions',
];
}
public function mount($taskId)
{
try {
$this->taskId = $taskId;
$this->task = ScheduledTask::findOrFail($taskId);
$this->executions = $this->task->executions()->take(20)->get();
$this->serverTimezone = data_get($this->task, 'application.destination.server.settings.server_timezone');
if (! $this->serverTimezone) {
$this->serverTimezone = data_get($this->task, 'service.destination.server.settings.server_timezone');
}
if (! $this->serverTimezone) {
$this->serverTimezone = 'UTC';
}
} catch (\Exception $e) {
return handleError($e);
}
}
public function refreshExecutions(): void
{
$this->executions = $this->task->executions()->take(20)->get();
}
public function selectTask($key): void
{
if ($key == $this->selectedKey) {
@ -29,38 +66,9 @@ class Executions extends Component
$this->selectedKey = $key;
}
public function server()
{
if (! $this->task) {
return null;
}
if ($this->task->application) {
if ($this->task->application->destination && $this->task->application->destination->server) {
return $this->task->application->destination->server;
}
} elseif ($this->task->service) {
if ($this->task->service->destination && $this->task->service->destination->server) {
return $this->task->service->destination->server;
}
}
return null;
}
public function getServerTimezone()
{
$server = $this->server();
if (! $server) {
return 'UTC';
}
return $server->settings->server_timezone;
}
public function formatDateInServerTimezone($date)
{
$serverTimezone = $this->getServerTimezone();
$serverTimezone = $this->serverTimezone;
$dateObj = new \DateTime($date);
try {
$dateObj->setTimezone(new \DateTimeZone($serverTimezone));

View file

@ -2,74 +2,124 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Jobs\ScheduledTaskJob;
use App\Models\Application;
use App\Models\ScheduledTask as ModelsScheduledTask;
use App\Models\ScheduledTask;
use App\Models\Service;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $parameters;
public Application|Service $resource;
public ModelsScheduledTask $task;
public ScheduledTask $task;
public ?string $modalId = null;
#[Locked]
public array $parameters;
#[Locked]
public string $type;
public string $scheduledTaskName;
#[Validate(['boolean'])]
public bool $isEnabled = false;
protected $rules = [
'task.enabled' => 'required|boolean',
'task.name' => 'required|string',
'task.command' => 'required|string',
'task.frequency' => 'required|string',
'task.container' => 'nullable|string',
];
#[Validate(['string', 'required'])]
public string $name;
protected $validationAttributes = [
'name' => 'name',
'command' => 'command',
'frequency' => 'frequency',
'container' => 'container',
];
#[Validate(['string', 'required'])]
public string $command;
public function mount()
#[Validate(['string', 'required'])]
public string $frequency;
#[Validate(['string', 'nullable'])]
public ?string $container = null;
#[Locked]
public ?string $application_uuid;
#[Locked]
public ?string $service_uuid;
#[Locked]
public string $task_uuid;
public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null)
{
$this->parameters = get_route_parameters();
try {
$this->task_uuid = $task_uuid;
if ($application_uuid) {
$this->type = 'application';
$this->application_uuid = $application_uuid;
$this->resource = Application::ownedByCurrentTeam()->where('uuid', $application_uuid)->firstOrFail();
} elseif ($service_uuid) {
$this->type = 'service';
$this->service_uuid = $service_uuid;
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail();
}
$this->parameters = [
'environment_name' => $environment_name,
'project_uuid' => $project_uuid,
'application_uuid' => $application_uuid,
'service_uuid' => $service_uuid,
];
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->task = $this->resource->scheduled_tasks()->where('uuid', $task_uuid)->firstOrFail();
$this->syncData();
} catch (\Exception $e) {
return handleError($e);
}
}
$this->modalId = new Cuid2;
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
$this->scheduledTaskName = $this->task->name;
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->task->enabled = $this->isEnabled;
$this->task->name = str($this->name)->trim()->value();
$this->task->command = str($this->command)->trim()->value();
$this->task->frequency = str($this->frequency)->trim()->value();
$this->task->container = str($this->container)->trim()->value();
$this->task->save();
} else {
$this->isEnabled = $this->task->enabled;
$this->name = $this->task->name;
$this->command = $this->task->command;
$this->frequency = $this->task->frequency;
$this->container = $this->task->container;
}
}
public function instantSave()
{
$this->validateOnly('task.enabled');
$this->task->save(['enabled' => $this->task->enabled]);
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
try {
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
$this->refreshTasks();
} catch (\Exception $e) {
return handleError($e);
}
}
public function submit()
{
$this->validate();
$this->task->name = str($this->task->name)->trim()->value();
$this->task->container = str($this->task->container)->trim()->value();
$this->task->save();
$this->dispatch('success', 'Scheduled task updated.');
$this->dispatch('refreshTasks');
try {
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
} catch (\Exception $e) {
return handleError($e);
}
}
public function refreshTasks()
{
try {
$this->task->refresh();
} catch (\Exception $e) {
return handleError($e);
}
}
public function delete()
@ -78,12 +128,22 @@ class Show extends Component
$this->task->delete();
if ($this->type === 'application') {
return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName);
return redirect()->route('project.application.configuration', $this->parameters, $this->task->name);
} else {
return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName);
return redirect()->route('project.service.configuration', $this->parameters, $this->task->name);
}
} catch (\Exception $e) {
return handleError($e);
}
}
public function executeNow()
{
try {
ScheduledTaskJob::dispatch($this->task);
$this->dispatch('success', 'Scheduled task executed.');
} catch (\Exception $e) {
return handleError($e);
}
}
}

View file

@ -172,6 +172,11 @@ class Application extends BaseModel
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
}
public static function ownedByCurrentTeam()
{
return Application::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
}
public function getContainersToStop(bool $previewDeployments = false): array
{
$containers = $previewDeployments

View file

@ -133,6 +133,11 @@ class Service extends BaseModel
return $this->morphToMany(Tag::class, 'taggable');
}
public static function ownedByCurrentTeam()
{
return Service::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
}
public function getContainersToStop(): array
{
$containersToStop = [];

View file

@ -37,6 +37,11 @@ class ServiceApplication extends BaseModel
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
}
public static function ownedByCurrentTeam()
{
return ServiceApplication::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
}
public function isRunning()
{
return str($this->status)->contains('running');

View file

@ -24,6 +24,16 @@ class ServiceDatabase extends BaseModel
});
}
public static function ownedByCurrentTeamAPI(int $teamId)
{
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
}
public static function ownedByCurrentTeam()
{
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
}
public function restart()
{
$container_id = $this->name.'-'.$this->service->uuid;

View file

@ -16,6 +16,11 @@
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($resource->isRunning())
<x-forms.button type="button" wire:click="executeNow">
Execute Now
</x-forms.button>
@endif
<x-modal-confirmation title="Confirm Scheduled Task Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete({{ $task->id }})" :actions="['The selected scheduled task will be permanently deleted.']" confirmationText="{{ $task->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Scheduled Task Name below"
@ -24,27 +29,26 @@
</div>
<div class="w-48">
<x-forms.checkbox instantSave id="task.enabled" label="Enabled" />
<x-forms.checkbox instantSave id="isEnabled" label="Enabled" />
</div>
<div class="flex gap-2 w-full">
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
<x-forms.input placeholder="Name" id="name" label="Name" required />
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" required />
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" required />
@if ($type === 'application')
<x-forms.input placeholder="php"
helper="You can leave this empty if your resource only has one container." id="task.container"
helper="You can leave this empty if your resource only has one container." id="container"
label="Container name" />
@elseif ($type === 'service')
<x-forms.input placeholder="php"
helper="You can leave this empty if your resource only has one service in your stack. Otherwise use the stack name, without the random generated ID. So if you have a mysql service in your stack, use mysql."
id="task.container" label="Service name" />
id="container" label="Service name" />
@endif
</div>
</form>
<div class="pt-4">
<h3 class="py-4">Recent executions <span class="text-xs text-neutral-500">(click to check output)</span></h3>
<livewire:project.shared.scheduled-task.executions :task="$task" key="{{ $task->id }}" selectedKey=""
:executions="$task->executions->take(20)" />
<livewire:project.shared.scheduled-task.executions :taskId="$task->id" />
</div>
</div>