projectUuid = data_get($parameters, 'project_uuid'); $this->environmentUuid = data_get($parameters, 'environment_uuid'); $teamId = $this->resourceTeamId(); $this->projects = Project::query()->where('team_id', $teamId)->orderByRaw('LOWER(name)')->get(); $servers = Server::query()->where('team_id', $teamId)->get(); $this->servers = $servers->reject(fn ($server) => $server->isBuildServer()); $this->buildServers = $servers->filter(fn ($server) => $server->isBuildServer()); } public function toggleVolumeCloning(bool $value) { $this->cloneVolumeData = $value; } public function toggleVolumeMigration(bool $value): void { $this->migrateVolumeData = $value; } public function cloneTo($destination_uuid, $environment_id = null) { try { $this->authorize('update', $this->resource); if ($this->cloneVolumeData) { $this->authorize(CloneVolumeDataWithSourceStopped::sourceAbility($this->resource), $this->resource); } $new_environment = $this->resource->environment; if ($environment_id !== null) { $new_environment = $this->findResourceTeamEnvironment($environment_id); if (! $new_environment) { return $this->addError('environment_id', 'Environment not found.'); } } $new_destination = $this->findResourceTeamDestination($destination_uuid); if (! $new_destination) { return $this->addError('destination_id', 'Destination not found.'); } $uuid = new_public_id(); $server = $new_destination->server; if (! $server->canHostResources()) { return $this->addError('destination_id', 'The selected server cannot host resources.'); } auditLog('ui.resource.clone_started', [ 'team_id' => $this->resource->team()?->id, 'resource_uuid' => $this->resource->uuid, 'resource_name' => $this->resource->name, 'resource_type' => class_basename($this->resource), 'destination_uuid' => $new_destination->uuid, 'environment_id' => $new_environment->id, ]); if ($this->resource->getMorphClass() === Application::class) { $new_resource = clone_application($this->resource, $new_destination, [ 'uuid' => $uuid, 'environment_id' => $new_environment->id, ], $this->cloneVolumeData); $route = route('project.application.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'application_uuid' => $new_resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } elseif ( $this->resource->getMorphClass() === StandalonePostgresql::class || $this->resource->getMorphClass() === StandaloneMongodb::class || $this->resource->getMorphClass() === StandaloneMysql::class || $this->resource->getMorphClass() === StandaloneMariadb::class || $this->resource->getMorphClass() === StandaloneRedis::class || $this->resource->getMorphClass() === StandaloneKeydb::class || $this->resource->getMorphClass() === StandaloneDragonfly::class || $this->resource->getMorphClass() === StandaloneClickhouse::class || $this->resource->getMorphClass() === StandaloneSqlite::class ) { $uuid = new_public_id(); $new_resource = $this->resource->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ 'uuid' => $uuid, 'name' => clone_resource_name($this->resource->name, $uuid), 'status' => 'exited', 'started_at' => null, 'environment_id' => $new_environment->id, 'destination_id' => $new_destination->id, 'destination_type' => $new_destination->getMorphClass(), ]); $new_resource->save(); $tags = $this->resource->tags; foreach ($tags as $tag) { $new_resource->tags()->attach($tag->id); } $new_resource->persistentStorages()->delete(); $pendingVolumeClones = []; $persistentVolumes = $this->resource->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { $originalName = $volume->name; $newName = ''; if (str_starts_with($originalName, 'postgres-data-')) { $newName = 'postgres-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'mysql-data-')) { $newName = 'mysql-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'redis-data-')) { $newName = 'redis-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'clickhouse-data-')) { $newName = 'clickhouse-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'mariadb-data-')) { $newName = 'mariadb-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'mongodb-data-')) { $newName = 'mongodb-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'keydb-data-')) { $newName = 'keydb-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'dragonfly-data-')) { $newName = 'dragonfly-data-'.$new_resource->uuid; } elseif (str_starts_with($originalName, 'sqlite-data-')) { $newName = 'sqlite-data-'.$new_resource->uuid; } else { if (str_starts_with($volume->name, $this->resource->uuid)) { $newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid); } else { $newName = $new_resource->uuid.'-'.$volume->name; } } $newPersistentVolume = $volume->replicate([ 'id', 'created_at', 'updated_at', 'uuid', ])->fill([ 'name' => $newName, 'resource_id' => $new_resource->id, ]); $newPersistentVolume->save(); if ($this->cloneVolumeData) { $pendingVolumeClones[] = new VolumeCloneJob( $volume->name, $newPersistentVolume->name, $this->resource->destination->server, $new_resource->destination->server, $newPersistentVolume, ); } } CloneVolumeDataWithSourceStopped::run($this->resource, $pendingVolumeClones); $fileStorages = $this->resource->fileStorages()->get(); foreach ($fileStorages as $storage) { $newStorage = $storage->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ 'resource_id' => $new_resource->id, ]); $newStorage->save(); } $scheduledBackups = $this->resource->scheduledBackups()->get(); foreach ($scheduledBackups as $backup) { $uuid = new_public_id(); $newBackup = $backup->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ 'uuid' => $uuid, 'database_id' => $new_resource->id, 'database_type' => $new_resource->getMorphClass(), 'team_id' => $this->resourceTeamId(), ]); $newBackup->save(); $backup->copyS3StoragesTo($newBackup); } $environmentVaribles = $this->resource->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $payload = [ 'resourceable_id' => $new_resource->id, 'resourceable_type' => $new_resource->getMorphClass(), ]; $newEnvironmentVariable = $environmentVarible->replicate([ 'id', 'created_at', 'updated_at', ])->fill($payload); $newEnvironmentVariable->save(); } $this->resource->cloneSecretManagerLinkTo($new_resource); $route = route('project.database.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'database_uuid' => $new_resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } elseif ($this->resource->type() === 'service') { $new_resource = clone_service($this->resource, $new_destination, [ 'environment_id' => $new_environment->id, ], $this->cloneVolumeData); $route = route('project.service.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'service_uuid' => $new_resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } } catch (\Throwable $e) { return handleError($e, $this); } } public function moveTo($environment_id) { try { $this->authorize('update', $this->resource); $new_environment = $this->findResourceTeamEnvironment($environment_id); if (! $new_environment) { return $this->addError('environment_id', 'Environment not found.'); } $this->resource->fill([ 'environment_id' => $new_environment->id, ])->save(); if ($this->resource->type() === 'application') { $route = route('project.application.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'application_uuid' => $this->resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } elseif (str($this->resource->type())->startsWith('standalone-')) { $route = route('project.database.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'database_uuid' => $this->resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } elseif ($this->resource->type() === 'service') { $route = route('project.service.configuration', [ 'project_uuid' => $new_environment->project->uuid, 'environment_uuid' => $new_environment->uuid, 'service_uuid' => $this->resource->uuid, ]).'#resource-operations'; return redirect()->to($route); } } catch (\Throwable $e) { return handleError($e, $this); } } public function migrateTo(string $destination_uuid) { try { $this->authorize('update', $this->resource); $new_destination = $this->findResourceTeamDestination($destination_uuid); if (! $new_destination) { return $this->dispatch('error', 'Destination not found.'); } $result = MigrateResourceToDestination::run( $this->resource, $new_destination, $this->migrateVolumeData, ); $this->dispatch('success', $result['message']); $this->resource->loadMissing('environment.project'); $projectUuid = $this->projectUuid ?? $this->resource->environment?->project?->uuid; $environmentUuid = $this->environmentUuid ?? $this->resource->environment?->uuid; if (! $projectUuid || ! $environmentUuid) { return null; } if ($this->resource->type() === 'application') { $route = route('project.application.configuration', [ 'project_uuid' => $projectUuid, 'environment_uuid' => $environmentUuid, 'application_uuid' => $this->resource->uuid, ]).'#resource-operations'; } elseif (str($this->resource->type())->startsWith('standalone-')) { $route = route('project.database.configuration', [ 'project_uuid' => $projectUuid, 'environment_uuid' => $environmentUuid, 'database_uuid' => $this->resource->uuid, ]).'#resource-operations'; } elseif ($this->resource->type() === 'service') { $route = route('project.service.configuration', [ 'project_uuid' => $projectUuid, 'environment_uuid' => $environmentUuid, 'service_uuid' => $this->resource->uuid, ]).'#resource-operations'; } else { return null; } return redirect()->to($route); } catch (ValidationException $e) { $message = collect($e->errors())->flatten()->first() ?? $e->getMessage(); return $this->dispatch('error', $message); } catch (\Throwable $e) { return handleError($e, $this); } } private function resourceTeamId(): int { return $this->resource->team()?->id ?? throw new RuntimeException('The resource has no team.'); } /** Cloning and moving stay inside the resource team; the session team can differ after a team switch. */ private function findResourceTeamEnvironment(int|string|null $environmentId): ?Environment { return Environment::query() ->whereRelation('project', 'team_id', $this->resourceTeamId()) ->find($environmentId); } private function findResourceTeamDestination(?string $uuid): StandaloneDocker|SwarmDocker|null { if (blank($uuid)) { return null; } $teamId = $this->resourceTeamId(); $destination = StandaloneDocker::query()->whereRelation('server', 'team_id', $teamId)->where('uuid', $uuid)->first() ?? SwarmDocker::query()->whereRelation('server', 'team_id', $teamId)->where('uuid', $uuid)->first(); return $destination?->server?->canHostResources() ? $destination : null; } public function render() { return view('livewire.project.shared.resource-operations'); } }