diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b642fdf5..1ba4d1876 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -238,7 +238,7 @@ After completing these steps, you'll have a fresh development setup. ### Contributing a New Service To add a new service to Coolify, please refer to our documentation: -[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service) +[Adding a New Service](https://coolify.io/docs/get-started/contribute/service) ### Contributing to Documentation diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php new file mode 100644 index 000000000..a5dcc6cf4 --- /dev/null +++ b/app/Actions/Proxy/StopProxy.php @@ -0,0 +1,56 @@ +isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; + $timeout = 30; + + $process = $this->stopContainer($containerName, $timeout); + + $startTime = Carbon::now()->getTimestamp(); + while ($process->running()) { + if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { + $this->forceStopContainer($containerName, $server); + break; + } + usleep(100000); + } + + $this->removeContainer($containerName, $server); + } catch (\Throwable $e) { + return handleError($e); + } finally { + $server->proxy->force_stop = $forceStop; + $server->proxy->status = 'exited'; + $server->save(); + } + } + + private function stopContainer(string $containerName, int $timeout): InvokedProcess + { + return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); + } + + private function forceStopContainer(string $containerName, Server $server) + { + instant_remote_process(["docker kill $containerName"], $server, throwError: false); + } + + private function removeContainer(string $containerName, Server $server) + { + instant_remote_process(["docker rm -f $containerName"], $server, throwError: false); + } +} diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index cbeb6b55d..0044e86b4 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2881,198 +2881,198 @@ class ApplicationsController extends Controller ); } - #[OA\Post( - summary: 'Execute Command', - description: "Execute a command on the application's current container.", - path: '/applications/{uuid}/execute', - operationId: 'execute-command-application', - security: [ - ['bearerAuth' => []], - ], - tags: ['Applications'], - parameters: [ - new OA\Parameter( - name: 'uuid', - in: 'path', - description: 'UUID of the application.', - required: true, - schema: new OA\Schema( - type: 'string', - format: 'uuid', - ) - ), - ], - requestBody: new OA\RequestBody( - required: true, - description: 'Command to execute.', - content: new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'command' => ['type' => 'string', 'description' => 'Command to execute.'], - ], - ), - ), - ), - responses: [ - new OA\Response( - response: 200, - description: "Execute a command on the application's current container.", - content: [ - new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'message' => ['type' => 'string', 'example' => 'Command executed.'], - 'response' => ['type' => 'string'], - ] - ) - ), - ] - ), - new OA\Response( - response: 401, - ref: '#/components/responses/401', - ), - new OA\Response( - response: 400, - ref: '#/components/responses/400', - ), - new OA\Response( - response: 404, - ref: '#/components/responses/404', - ), - ] - )] - public function execute_command_by_uuid(Request $request) - { - // TODO: Need to review this from security perspective, to not allow arbitrary command execution - $allowedFields = ['command']; - $teamId = getTeamIdFromToken(); - if (is_null($teamId)) { - return invalidTokenResponse(); - } - $uuid = $request->route('uuid'); - if (! $uuid) { - return response()->json(['message' => 'UUID is required.'], 400); - } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); - if (! $application) { - return response()->json(['message' => 'Application not found.'], 404); - } - $return = validateIncomingRequest($request); - if ($return instanceof \Illuminate\Http\JsonResponse) { - return $return; - } - $validator = customApiValidator($request->all(), [ - 'command' => 'string|required', - ]); + // #[OA\Post( + // summary: 'Execute Command', + // description: "Execute a command on the application's current container.", + // path: '/applications/{uuid}/execute', + // operationId: 'execute-command-application', + // security: [ + // ['bearerAuth' => []], + // ], + // tags: ['Applications'], + // parameters: [ + // new OA\Parameter( + // name: 'uuid', + // in: 'path', + // description: 'UUID of the application.', + // required: true, + // schema: new OA\Schema( + // type: 'string', + // format: 'uuid', + // ) + // ), + // ], + // requestBody: new OA\RequestBody( + // required: true, + // description: 'Command to execute.', + // content: new OA\MediaType( + // mediaType: 'application/json', + // schema: new OA\Schema( + // type: 'object', + // properties: [ + // 'command' => ['type' => 'string', 'description' => 'Command to execute.'], + // ], + // ), + // ), + // ), + // responses: [ + // new OA\Response( + // response: 200, + // description: "Execute a command on the application's current container.", + // content: [ + // new OA\MediaType( + // mediaType: 'application/json', + // schema: new OA\Schema( + // type: 'object', + // properties: [ + // 'message' => ['type' => 'string', 'example' => 'Command executed.'], + // 'response' => ['type' => 'string'], + // ] + // ) + // ), + // ] + // ), + // new OA\Response( + // response: 401, + // ref: '#/components/responses/401', + // ), + // new OA\Response( + // response: 400, + // ref: '#/components/responses/400', + // ), + // new OA\Response( + // response: 404, + // ref: '#/components/responses/404', + // ), + // ] + // )] + // public function execute_command_by_uuid(Request $request) + // { + // // TODO: Need to review this from security perspective, to not allow arbitrary command execution + // $allowedFields = ['command']; + // $teamId = getTeamIdFromToken(); + // if (is_null($teamId)) { + // return invalidTokenResponse(); + // } + // $uuid = $request->route('uuid'); + // if (! $uuid) { + // return response()->json(['message' => 'UUID is required.'], 400); + // } + // $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + // if (! $application) { + // return response()->json(['message' => 'Application not found.'], 404); + // } + // $return = validateIncomingRequest($request); + // if ($return instanceof \Illuminate\Http\JsonResponse) { + // return $return; + // } + // $validator = customApiValidator($request->all(), [ + // 'command' => 'string|required', + // ]); - $extraFields = array_diff(array_keys($request->all()), $allowedFields); - if ($validator->fails() || ! empty($extraFields)) { - $errors = $validator->errors(); - if (! empty($extraFields)) { - foreach ($extraFields as $field) { - $errors->add($field, 'This field is not allowed.'); - } - } + // $extraFields = array_diff(array_keys($request->all()), $allowedFields); + // if ($validator->fails() || ! empty($extraFields)) { + // $errors = $validator->errors(); + // if (! empty($extraFields)) { + // foreach ($extraFields as $field) { + // $errors->add($field, 'This field is not allowed.'); + // } + // } - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $errors, - ], 422); - } + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => $errors, + // ], 422); + // } - $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); - $status = getContainerStatus($application->destination->server, $container['Names']); + // $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); + // $status = getContainerStatus($application->destination->server, $container['Names']); - if ($status !== 'running') { - return response()->json([ - 'message' => 'Application is not running.', - ], 400); - } + // if ($status !== 'running') { + // return response()->json([ + // 'message' => 'Application is not running.', + // ], 400); + // } - $commands = collect([ - executeInDocker($container['Names'], $request->command), - ]); + // $commands = collect([ + // executeInDocker($container['Names'], $request->command), + // ]); - $res = instant_remote_process(command: $commands, server: $application->destination->server); + // $res = instant_remote_process(command: $commands, server: $application->destination->server); - return response()->json([ - 'message' => 'Command executed.', - 'response' => $res, - ]); - } + // return response()->json([ + // 'message' => 'Command executed.', + // 'response' => $res, + // ]); + // } - private function validateDataApplications(Request $request, Server $server) - { - $teamId = getTeamIdFromToken(); + // private function validateDataApplications(Request $request, Server $server) + // { + // $teamId = getTeamIdFromToken(); - // Validate ports_mappings - if ($request->has('ports_mappings')) { - $ports = []; - foreach (explode(',', $request->ports_mappings) as $portMapping) { - $port = explode(':', $portMapping); - if (in_array($port[0], $ports)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'ports_mappings' => 'The first number before : should be unique between mappings.', - ], - ], 422); - } - $ports[] = $port[0]; - } - } - // Validate custom_labels - if ($request->has('custom_labels')) { - if (! isBase64Encoded($request->custom_labels)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - $customLabels = base64_decode($request->custom_labels); - if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - } - if ($request->has('domains') && $server->isProxyShouldRun()) { - $uuid = $request->uuid; - $fqdn = $request->domains; - $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); - $fqdn = str($fqdn)->replaceStart(',', '')->trim(); - $errors = []; - $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { - if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: '.$domain; - } + // // Validate ports_mappings + // if ($request->has('ports_mappings')) { + // $ports = []; + // foreach (explode(',', $request->ports_mappings) as $portMapping) { + // $port = explode(':', $portMapping); + // if (in_array($port[0], $ports)) { + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => [ + // 'ports_mappings' => 'The first number before : should be unique between mappings.', + // ], + // ], 422); + // } + // $ports[] = $port[0]; + // } + // } + // // Validate custom_labels + // if ($request->has('custom_labels')) { + // if (! isBase64Encoded($request->custom_labels)) { + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => [ + // 'custom_labels' => 'The custom_labels should be base64 encoded.', + // ], + // ], 422); + // } + // $customLabels = base64_decode($request->custom_labels); + // if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => [ + // 'custom_labels' => 'The custom_labels should be base64 encoded.', + // ], + // ], 422); + // } + // } + // if ($request->has('domains') && $server->isProxyShouldRun()) { + // $uuid = $request->uuid; + // $fqdn = $request->domains; + // $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); + // $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + // $errors = []; + // $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + // if (filter_var($domain, FILTER_VALIDATE_URL) === false) { + // $errors[] = 'Invalid domain: '.$domain; + // } - return str($domain)->trim()->lower(); - }); - if (count($errors) > 0) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $errors, - ], 422); - } - if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'domains' => 'One of the domain is already used.', - ], - ], 422); - } - } - } + // return str($domain)->trim()->lower(); + // }); + // if (count($errors) > 0) { + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => $errors, + // ], 422); + // } + // if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => [ + // 'domains' => 'One of the domain is already used.', + // ], + // ], 422); + // } + // } + // } } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index b72d2ade3..5dbdbf215 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -329,13 +329,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } else { $this->write_deployment_configurations(); } - $this->execute_remote_command( - [ - "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", - 'hidden' => true, - 'ignore_errors' => true, - ] - ); + $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}"); + $this->graceful_shutdown_container($this->deployment_uuid); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); } @@ -1366,13 +1361,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } } $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage."); - $this->execute_remote_command( - [ - 'command' => "docker rm -f {$this->deployment_uuid}", - 'ignore_errors' => true, - 'hidden' => true, - ] - ); + $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}"); + $this->graceful_shutdown_container($this->deployment_uuid); $this->execute_remote_command( [ $runCommand, diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php new file mode 100644 index 000000000..7fc716f70 --- /dev/null +++ b/app/Jobs/RestartProxyJob.php @@ -0,0 +1,46 @@ +server->uuid))->dontRelease()]; + } + + public function __construct(public Server $server) {} + + public function handle() + { + try { + StopProxy::run($this->server); + + $this->server->proxy->force_stop = false; + $this->server->save(); + StartProxy::run($this->server, force: true); + + CheckProxy::run($this->server, true); + } catch (\Throwable $e) { + return handleError($e); + } + } +} diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index 4a7e4124e..48eede4e5 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -4,11 +4,10 @@ namespace App\Livewire\Server\Proxy; use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\StartProxy; +use App\Actions\Proxy\StopProxy; use App\Events\ProxyStatusChanged; +use App\Jobs\RestartProxyJob; use App\Models\Server; -use Carbon\Carbon; -use Illuminate\Process\InvokedProcess; -use Illuminate\Support\Facades\Process; use Livewire\Component; class Deploy extends Component @@ -65,6 +64,7 @@ class Deploy extends Component public function restart() { try { + RestartProxyJob::dispatch($this->server); $this->dispatch('checkProxy'); } catch (\Throwable $e) { return handleError($e, $this); @@ -97,43 +97,10 @@ class Deploy extends Component public function stop(bool $forceStop = true) { try { - $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; - $timeout = 30; - - $process = $this->stopContainer($containerName, $timeout); - - $startTime = Carbon::now()->getTimestamp(); - while ($process->running()) { - if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { - $this->forceStopContainer($containerName); - break; - } - usleep(100000); - } - - $this->removeContainer($containerName); + StopProxy::run($this->server, $forceStop); + $this->dispatch('proxyStatusUpdated'); } catch (\Throwable $e) { return handleError($e, $this); - } finally { - $this->server->proxy->force_stop = $forceStop; - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->dispatch('proxyStatusUpdated'); } } - - private function stopContainer(string $containerName, int $timeout): InvokedProcess - { - return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); - } - - private function forceStopContainer(string $containerName) - { - instant_remote_process(["docker kill $containerName"], $this->server, throwError: false); - } - - private function removeContainer(string $containerName) - { - instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false); - } } diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php index 1b0599ffe..bb5ed0aa8 100644 --- a/app/Livewire/SettingsBackup.php +++ b/app/Livewire/SettingsBackup.php @@ -15,6 +15,8 @@ class SettingsBackup extends Component { public InstanceSettings $settings; + public Server $server; + public ?StandalonePostgresql $database = null; public ScheduledDatabaseBackup|null|array $backup = []; @@ -46,6 +48,7 @@ class SettingsBackup extends Component return redirect()->route('dashboard'); } else { $settings = instanceSettings(); + $this->server = Server::findOrFail(0); $this->database = StandalonePostgresql::whereName('coolify-db')->first(); $s3s = S3Storage::whereTeamId(0)->get() ?? []; if ($this->database) { @@ -60,6 +63,10 @@ class SettingsBackup extends Component $this->database->save(); } $this->backup = $this->database->scheduledBackups->first(); + if ($this->backup && ! $this->server->isFunctional()) { + $this->backup->enabled = false; + $this->backup->save(); + } $this->executions = $this->backup->executions; } $this->settings = $settings; diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 858f3b187..4426c7812 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -4,7 +4,7 @@ namespace App\Livewire; use App\Models\InstanceSettings; use App\Models\Team; -use App\Notifications\Test; +use App\Notifications\TransactionalEmails\Test; use Illuminate\Support\Facades\RateLimiter; use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index a452ef8dd..8a9a95107 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -12,8 +12,9 @@ class EmailChannel public function send(SendsEmail $notifiable, Notification $notification): void { $useInstanceEmailSettings = $notifiable->emailNotificationSettings->use_instance_email_settings; + $isTransactionalEmail = data_get($notification, 'isTransactionalEmail', false); $customEmails = data_get($notification, 'emails', null); - if ($useInstanceEmailSettings) { + if ($useInstanceEmailSettings || $isTransactionalEmail) { $settings = instanceSettings(); } else { $settings = $notifiable->emailNotificationSettings; @@ -49,8 +50,8 @@ class EmailChannel $settings->smtp_port, $encryption ); - $transport->setUsername($settings->smtp_username); - $transport->setPassword($settings->smtp_password); + $transport->setUsername($settings->smtp_username ?? ''); + $transport->setPassword($settings->smtp_password ?? ''); $mailer = new \Symfony\Component\Mailer\Mailer($transport); diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index 30ace99dc..fce3eb948 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -16,9 +16,10 @@ class InvitationLink extends CustomEmailNotification return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) + public function __construct(public User $user, public bool $isTransactionalEmail) { $this->onQueue('high'); + $this->isTransactionalEmail = true; } public function toMail(): MailMessage diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php index 4593ddb0d..031a2d95a 100644 --- a/app/Notifications/TransactionalEmails/ResetPassword.php +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -17,10 +17,11 @@ class ResetPassword extends Notification public InstanceSettings $settings; - public function __construct($token) + public function __construct($token, public bool $isTransactionalEmail) { $this->settings = instanceSettings(); $this->token = $token; + $this->isTransactionalEmail = true; } public static function createUrlUsing($callback) diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index eeb32a254..951c007d9 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -8,9 +8,10 @@ use Illuminate\Notifications\Messages\MailMessage; class Test extends CustomEmailNotification { - public function __construct(public string $emails) + public function __construct(public string $emails, public string $isTransactionalEmail) { $this->onQueue('high'); + $this->isTransactionalEmail = true; } public function via(): array diff --git a/lang/az.json b/lang/az.json new file mode 100644 index 000000000..973c70c2f --- /dev/null +++ b/lang/az.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Daxil ol", + "auth.login.authentik": "Authentik ilə daxil ol", + "auth.login.azure": "Azure ilə daxil ol", + "auth.login.bitbucket": "Bitbucket ilə daxil ol", + "auth.login.github": "Github ilə daxil ol", + "auth.login.gitlab": "GitLab ilə daxil ol", + "auth.login.google": "Google ilə daxil ol", + "auth.login.infomaniak": "Infomaniak ilə daxil ol", + "auth.already_registered": "Qeytiyatınız var?", + "auth.confirm_password": "Şifrəni təsdiqləyin", + "auth.forgot_password": "Şifrəmi unutdum", + "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər", + "auth.register_now": "Qeydiyyat", + "auth.logout": "Çıxış", + "auth.register": "Qeydiyyat", + "auth.registration_disabled": "Qeydiyyat bağlıdır. Administratorla əlaqə saxlayın.", + "auth.reset_password": "Şifrənin bərpası", + "auth.failed": "Bu məlumatlar bizim qeydlərimizlə uyğun gəlmir.", + "auth.failed.callback": "Giriş təminatçısından geri çağırma işlənə bilmədi.", + "auth.failed.password": "Daxil etdiyiniz şifrə yanlışdır.", + "auth.failed.email": "Bu e-poçt ünvanı ilə istifadəçi tapılmadı.", + "auth.throttle": "Çox sayda uğursuz giriş cəhdi. Zəhmət olmasa :seconds saniyə sonra yenidən cəhd edin.", + "input.name": "Ad", + "input.email": "E-poçt", + "input.password": "Şifrə", + "input.password.again": "Şifrəni təkrar daxil edin", + "input.code": "Bir dəfəlik kod", + "input.recovery_code": "Bərpa kodu", + "button.save": "Yadda saxla", + "repository.url": "Nümunələr
Publik repozitoriyalar üçün https://... istifadə edin.
Özəl repozitoriyalar üçün git@... istifadə edin.

https://github.com/coollabsio/coolify-examples main branch-ı seçiləcək
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch-ı seçiləcək.
https://gitea.com/sedlav/expressjs.git main branch-ı seçiləcək.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch-ı seçiləcək.", + "service.stop": "Bu xidmət dayandırılacaq.", + "resource.docker_cleanup": "Docker təmizlənməsini işə salın (istifadə olunmayan şəkillər və builder keşini silin).", + "resource.non_persistent": "Bütün qeyri-daimi məlumatlar silinəcək.", + "resource.delete_volumes": "Bu resursla əlaqəli bütün həcm məlumatları tamamilə silinəcək.", + "resource.delete_connected_networks": "Bu resursla əlaqəli bütün əvvəlcədən təyin olunmamış şəbəkələr tamamilə silinəcək.", + "resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.", + "database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.", + "warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).

Əvəzində öz domeninizdən istifadə edin." +} diff --git a/lang/id.json b/lang/id.json new file mode 100644 index 000000000..d35556402 --- /dev/null +++ b/lang/id.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Masuk", + "auth.login.authentik": "Masuk dengan Authentik", + "auth.login.azure": "Masuk dengan Microsoft", + "auth.login.bitbucket": "Masuk dengan Bitbucket", + "auth.login.github": "Masuk dengan GitHub", + "auth.login.gitlab": "Masuk dengan Gitlab", + "auth.login.google": "Masuk dengan Google", + "auth.login.infomaniak": "Masuk dengan Infomaniak", + "auth.already_registered": "Sudah terdaftar?", + "auth.confirm_password": "Konfirmasi kata sandi", + "auth.forgot_password": "Lupa kata sandi", + "auth.forgot_password_send_email": "Kirim email reset kata sandi", + "auth.register_now": "Daftar", + "auth.logout": "Keluar", + "auth.register": "Daftar", + "auth.registration_disabled": "Pendaftaran dinonaktifkan. Harap hubungi administrator.", + "auth.reset_password": "Reset kata sandi", + "auth.failed": "Kredensial ini tidak cocok dengan catatan kami.", + "auth.failed.callback": "Gagal memproses callback dari penyedia login.", + "auth.failed.password": "Kata sandi yang diberikan salah.", + "auth.failed.email": "Kami tidak dapat menemukan pengguna dengan alamat e-mail tersebut.", + "auth.throttle": "Terlalu banyak percobaan login. Silakan coba lagi dalam :seconds detik.", + "input.name": "Nama", + "input.email": "Email", + "input.password": "Kata sandi", + "input.password.again": "Kata sandi lagi", + "input.code": "Kode sekali pakai", + "input.recovery_code": "Kode pemulihan", + "button.save": "Simpan", + "repository.url": "Contoh
Untuk repositori Publik, gunakan https://....
Untuk repositori Privat, gunakan git@....

https://github.com/coollabsio/coolify-examples cabang main akan dipilih
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify cabang nodejs-fastify akan dipilih.
https://gitea.com/sedlav/expressjs.git cabang main akan dipilih.
https://gitlab.com/andrasbacsai/nodejs-example.git cabang main akan dipilih.", + "service.stop": "Layanan ini akan dihentikan.", + "resource.docker_cleanup": "Jalankan Pembersihan Docker (hapus gambar yang tidak digunakan dan cache builder).", + "resource.non_persistent": "Semua data non-persisten akan dihapus.", + "resource.delete_volumes": "Hapus permanen semua volume yang terkait dengan sumber daya ini.", + "resource.delete_connected_networks": "Hapus permanen semua jaringan non-predefined yang terkait dengan sumber daya ini.", + "resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.", + "database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.", + "warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).

Gunakan domain Anda sendiri sebagai gantinya." +} diff --git a/lang/pt-br.json b/lang/pt-br.json new file mode 100644 index 000000000..2e793890f --- /dev/null +++ b/lang/pt-br.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Entrar", + "auth.login.authentik": "Entrar com Authentik", + "auth.login.azure": "Entrar com Microsoft", + "auth.login.bitbucket": "Entrar com Bitbucket", + "auth.login.github": "Entrar com GitHub", + "auth.login.gitlab": "Entrar com Gitlab", + "auth.login.google": "Entrar com Google", + "auth.login.infomaniak": "Entrar com Infomaniak", + "auth.already_registered": "Já tem uma conta?", + "auth.confirm_password": "Confirmar senha", + "auth.forgot_password": "Esqueceu a senha", + "auth.forgot_password_send_email": "Enviar e-mail para redefinir senha", + "auth.register_now": "Cadastre-se", + "auth.logout": "Sair", + "auth.register": "Cadastrar", + "auth.registration_disabled": "O registro está desativado. Por favor, contate o administrador.", + "auth.reset_password": "Redefinir senha", + "auth.failed": "Essas credenciais não correspondem aos nossos registros.", + "auth.failed.callback": "Falha ao processar o callback do provedor de login.", + "auth.failed.password": "A senha fornecida está incorreta.", + "auth.failed.email": "Não encontramos nenhum usuário com esse endereço de e-mail.", + "auth.throttle": "Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.", + "input.name": "Nome", + "input.email": "E-mail", + "input.password": "Senha", + "input.password.again": "Senha novamente", + "input.code": "Código de uso único", + "input.recovery_code": "Código de recuperação", + "button.save": "Salvar", + "repository.url": "Exemplos
Para repositórios públicos, use https://....
Para repositórios privados, use git@....

https://github.com/coollabsio/coolify-examples main branch será selecionado
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch será selecionado.
https://gitea.com/sedlav/expressjs.git main branch será selecionado.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch será selecionado.", + "service.stop": "Este serviço será parado.", + "resource.docker_cleanup": "Executar limpeza do Docker (remover imagens não utilizadas e cache de build).", + "resource.non_persistent": "Todos os dados não persistentes serão excluídos.", + "resource.delete_volumes": "Excluir permanentemente todos os volumes associados a este recurso.", + "resource.delete_connected_networks": "Excluir permanentemente todas as redes não predefinidas associadas a este recurso.", + "resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.", + "database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.", + "warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).

Use seu próprio domínio em vez disso." +} diff --git a/lang/tr.json b/lang/tr.json index 3cbcee409..663c756f9 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -27,5 +27,13 @@ "input.code": "Tek Kullanımlık Kod", "input.recovery_code": "Kurtarma Kodu", "button.save": "Kaydet", - "repository.url": "Örnekler
Halka açık depolar için https://... kullanın.
Özel depolar için git@... kullanın.

https://github.com/coollabsio/coolify-examples main dalı seçilecek
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek.
https://gitea.com/sedlav/expressjs.git main dalı seçilecek.
https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek." + "repository.url": "Örnekler
Halka açık depolar için https://... kullanın.
Özel depolar için git@... kullanın.

https://github.com/coollabsio/coolify-examples main dalı seçilecek
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek.
https://gitea.com/sedlav/expressjs.git main dalı seçilecek.
https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek.", + "service.stop": "Bu servis durdurulacak.", + "resource.docker_cleanup": "Docker temizliği çalıştır (kullanılmayan imajları ve oluşturucu önbelleğini kaldır).", + "resource.non_persistent": "Tüm kalıcı olmayan veriler silinecek.", + "resource.delete_volumes": "Bu kaynakla ilişkili tüm hacimler kalıcı olarak silinecek.", + "resource.delete_connected_networks": "Bu kaynakla ilişkili önceden tanımlanmamış tüm ağlar kalıcı olarak silinecek.", + "resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.", + "database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.", + "warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).

Bunun yerine kendi domaininizi kullanın." } diff --git a/openapi.json b/openapi.json index 98447067e..dbbc3dc24 100644 --- a/openapi.json +++ b/openapi.json @@ -2771,80 +2771,6 @@ ] } }, - "\/applications\/{uuid}\/execute": { - "post": { - "tags": [ - "Applications" - ], - "summary": "Execute Command", - "description": "Execute a command on the application's current container.", - "operationId": "execute-command-application", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "description": "Command to execute.", - "required": true, - "content": { - "application\/json": { - "schema": { - "properties": { - "command": { - "type": "string", - "description": "Command to execute." - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Execute a command on the application's current container.", - "content": { - "application\/json": { - "schema": { - "properties": { - "message": { - "type": "string", - "example": "Command executed." - }, - "response": { - "type": "string" - } - }, - "type": "object" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "400": { - "$ref": "#\/components\/responses\/400" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, "\/databases": { "get": { "tags": [ diff --git a/openapi.yaml b/openapi.yaml index ba4b7193e..b8f34ef19 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1905,52 +1905,6 @@ paths: security: - bearerAuth: [] - '/applications/{uuid}/execute': - post: - tags: - - Applications - summary: 'Execute Command' - description: "Execute a command on the application's current container." - operationId: execute-command-application - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true - schema: - type: string - format: uuid - requestBody: - description: 'Command to execute.' - required: true - content: - application/json: - schema: - properties: - command: - type: string - description: 'Command to execute.' - type: object - responses: - '200': - description: "Execute a command on the application's current container." - content: - application/json: - schema: - properties: - message: { type: string, example: 'Command executed.' } - response: { type: string } - type: object - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] /databases: get: tags: diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 52d58882e..38ae7c427 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.403" + "version": "4.0.0-beta.404" }, "nightly": { - "version": "4.0.0-beta.404" + "version": "4.0.0-beta.405" }, "helper": { "version": "1.0.8" diff --git a/resources/views/components/forms/copy-button.blade.php b/resources/views/components/forms/copy-button.blade.php index 39be2bc6a..12fadc595 100644 --- a/resources/views/components/forms/copy-button.blade.php +++ b/resources/views/components/forms/copy-button.blade.php @@ -1,8 +1,9 @@ @props(['text']) -
+