From b60f8df17ae133d3b1c639e15bdd0b312a78ed74 Mon Sep 17 00:00:00 2001 From: buttercubz Date: Sun, 9 Jun 2024 12:14:55 -0400 Subject: [PATCH 01/71] feat: spanish translation --- lang/es.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/es.json diff --git a/lang/es.json b/lang/es.json new file mode 100644 index 000000000..866f5ae88 --- /dev/null +++ b/lang/es.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Iniciar Sesión", + "auth.login.azure": "Acceder con Microsoft", + "auth.login.bitbucket": "Acceder con Bitbucket", + "auth.login.github": "Acceder con GitHub", + "auth.login.gitlab": "Acceder con Gitlab", + "auth.login.google": "Acceder con Google", + "auth.already_registered": "¿Ya estás registrado?", + "auth.confirm_password": "Confirmar contraseña", + "auth.forgot_password": "¿Olvidaste tu contraseña?", + "auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña", + "auth.register_now": "Registrar", + "auth.logout": "Cerrar sesión", + "auth.register": "Registrar", + "auth.registration_disabled": "El registro está desactivado. Por favor contacta con el administrador.", + "auth.reset_password": "Cambiar contraseña", + "auth.failed": "Las credenciales no coinciden con nuestro registro..", + "auth.failed.callback": "Fallo el proceso de inicio de sesión con el proveedor.", + "auth.failed.password": "La contraseña es incorrecta.", + "auth.failed.email": "No encontramos un usuario con ese correo.", + "auth.throttle": "Demasiados intentos. Por favor Inténtalo en :seconds segundos.", + "input.name": "Nombre", + "input.email": "Correo", + "input.password": "Contraseña", + "input.password.again": "Escribe la contraseña otra vez", + "input.code": "Código de único uso", + "input.recovery_code": "Código de recuperación", + "button.save": "Guardar", + "repository.url": "Examples
Para repositorios publicos, usar https://....
Para repositorios privados, usar git@....

https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." +} From 55e2e29696986b15168465d9b14bbd01c2a6d97c Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 10 Jun 2024 02:50:10 +0900 Subject: [PATCH 02/71] Add Japanese language support --- lang/ja.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/ja.json diff --git a/lang/ja.json b/lang/ja.json new file mode 100644 index 000000000..4652a3b17 --- /dev/null +++ b/lang/ja.json @@ -0,0 +1,30 @@ +{ + "auth.login": "ログイン", + "auth.login.azure": "Microsoftでログイン", + "auth.login.bitbucket": "Bitbucketでログイン", + "auth.login.github": "GitHubでログイン", + "auth.login.gitlab": "Gitlabでログイン", + "auth.login.google": "Googleでログイン", + "auth.already_registered": "すでに登録済みですか?", + "auth.confirm_password": "パスワードを確認", + "auth.forgot_password": "パスワードを忘れた", + "auth.forgot_password_send_email": "パスワードリセットメールを送信", + "auth.register_now": "今すぐ登録", + "auth.logout": "ログアウト", + "auth.register": "登録", + "auth.registration_disabled": "登録は無効です。管理者に連絡してください。", + "auth.reset_password": "パスワードをリセット", + "auth.failed": "これらの資格情報は記録と一致しません。", + "auth.failed.callback": "ログインプロバイダーからのコールバックの処理に失敗しました。", + "auth.failed.password": "提供されたパスワードが正しくありません。", + "auth.failed.email": "そのメールアドレスのユーザーが見つかりません。", + "auth.throttle": "ログイン試行回数が多すぎます。:seconds秒後にもう一度お試しください。", + "input.name": "名前", + "input.email": "メール", + "input.password": "パスワード", + "input.password.again": "パスワード再入力", + "input.code": "ワンタイムコード", + "input.recovery_code": "リカバリーコード", + "button.save": "保存", + "repository.url": "
公開リポジトリの場合はhttps://...を使用してください。
プライベートリポジトリの場合はgit@...を使用してください。

https://github.com/coollabsio/coolify-examples mainブランチが選択されます
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。
https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。
https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。" +} From 665cd454efb32ec01f30be3de51e84ade9613a11 Mon Sep 17 00:00:00 2001 From: systematicRealm <119763173+systematicRealm@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:23:18 +0300 Subject: [PATCH 03/71] =?UTF-8?q?=F0=9F=8C=90=20ADD:=20Arabic=20Language?= =?UTF-8?q?=20Support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/ar.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/ar.json diff --git a/lang/ar.json b/lang/ar.json new file mode 100644 index 000000000..c5ec96c8d --- /dev/null +++ b/lang/ar.json @@ -0,0 +1,30 @@ +{ + "auth.login": "تسجيل الدخول", + "auth.login.azure": "تسجيل الدخول باستخدام Microsoft", + "auth.login.bitbucket": "تسجيل الدخول باستخدام Bitbucket", + "auth.login.github": "تسجيل الدخول باستخدام GitHub", + "auth.login.gitlab": "تسجيل الدخول باستخدام Gitlab", + "auth.login.google": "تسجيل الدخول باستخدام Google", + "auth.already_registered": "هل سبق لك التسجيل؟", + "auth.confirm_password": "تأكيد كلمة المرور", + "auth.forgot_password": "نسيت كلمة المرور", + "auth.forgot_password_send_email": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور", + "auth.register_now": "تسجيل", + "auth.logout": "تسجيل الخروج", + "auth.register": "تسجيل", + "auth.registration_disabled": "تم تعطيل التسجيل. يرجى التواصل مع المسؤول.", + "auth.reset_password": "إعادة تعيين كلمة المرور", + "auth.failed": "هذه البيانات لا تتطابق مع سجلاتنا.", + "auth.failed.callback": "فشل في معالجة استدعاء من مزود تسجيل الدخول.", + "auth.failed.password": "كلمة المرور المقدمة غير صحيحة.", + "auth.failed.email": "لا يمكننا العثور على مستخدم بهذا البريد الإلكتروني.", + "auth.throttle": "عدد محاولات تسجيل الدخول كثيرة جدًا. يرجى المحاولة مرة أخرى في :seconds ثانية.", + "input.name": "الاسم", + "input.email": "البريد الإلكتروني", + "input.password": "كلمة المرور", + "input.password.again": "كلمة المرور مرة أخرى", + "input.code": "الرمز لمرة واحدة", + "input.recovery_code": "رمز الاسترداد", + "button.save": "حفظ", + "repository.url": "أمثلة
للمستودعات العامة، استخدم https://....
للمستودعات الخاصة، استخدم git@....

سيتم تحديد الفرع main لـ https://github.com/coollabsio/coolify-examples
سيتم تحديد الفرع nodejs-fastify لـ https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify
سيتم تحديد الفرع main لـ https://gitea.com/sedlav/expressjs.git
سيتم تحديد الفرع main لـ https://gitlab.com/andrasbacsai/nodejs-example.git." +} From f4904047b5d720462a5f08c23942f9906ab8ddb0 Mon Sep 17 00:00:00 2001 From: Ling <59746573+ndbiaw@users.noreply.github.com> Date: Tue, 11 Jun 2024 03:36:11 +0700 Subject: [PATCH 04/71] chore: add Vietnamese translate --- lang/vi.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/vi.json diff --git a/lang/vi.json b/lang/vi.json new file mode 100644 index 000000000..548dbe8b7 --- /dev/null +++ b/lang/vi.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Đăng Nhập", + "auth.login.azure": "Đăng Nhập Bằng Microsoft", + "auth.login.bitbucket": "Đăng Nhập Bằng Bitbucket", + "auth.login.github": "Đăng Nhập Bằng GitHub", + "auth.login.gitlab": "Đăng Nhập Bằng Gitlab", + "auth.login.google": "Đăng Nhập Bằng Google", + "auth.already_registered": "Đã đăng ký?", + "auth.confirm_password": "Nhập lại mật khẩu", + "auth.forgot_password": "Quên mật khẩu", + "auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu", + "auth.register_now": "Đăng ký ngay", + "auth.logout": "Đăng xuất", + "auth.register": "Đăng ký", + "auth.registration_disabled": "Đăng ký không khả dụng. Vui lòng liên hệ quản trị viên.", + "auth.reset_password": "Đặt lại mật khẩu", + "auth.failed": "Thông tin đăng nhập không khớp với bất kỳ tài khoản nào.", + "auth.failed.callback": "Xử lý thông tin từ nhà cung cấp đăng nhập thất bại.", + "auth.failed.password": "Mật khẩu bạn cung cấp không chính xác.", + "auth.failed.email": "Không có người dùng nào đã đăng ký với email đó.", + "auth.throttle": "Quá nhiều lần đăng nhập thất bại. Vui lòng thử lại sau :seconds giây.", + "input.name": "Tên", + "input.email": "Email", + "input.password": "Mật khẩu", + "input.password.again": "Mật khẩu lần nữa", + "input.code": "One-time code", + "input.recovery_code": "Mã khôi phục", + "button.save": "Lưu", + "repository.url": "Ví dụ
Với repo công khai, sử dụng https://....
Với repo riêng tư, sử dụng git@....

https://github.com/coollabsio/coolify-examples nhánh chính sẽ được chọn
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nhánh nodejs-fastify sẽ được chọn.
https://gitea.com/sedlav/expressjs.git nhánh chính sẽ được chọn.
https://gitlab.com/andrasbacsai/nodejs-example.git nhánh chính sẽ được chọn." +} From e282686f97e8a4c0ea3242d3ce8b4e961955900a Mon Sep 17 00:00:00 2001 From: Flow Date: Tue, 11 Jun 2024 01:29:37 +0200 Subject: [PATCH 05/71] Add french translation --- lang/fr.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/fr.json diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 000000000..ae7fa0a03 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Connexion", + "auth.login.azure": "Connexion avec Microsoft", + "auth.login.bitbucket": "Connexion avec Bitbucket", + "auth.login.github": "Connexion avec GitHub", + "auth.login.gitlab": "Connexion avec Gitlab", + "auth.login.google": "Connexion avec Google", + "auth.already_registered": "Déjà enregistré ?", + "auth.confirm_password": "Confirmer le mot de passe", + "auth.forgot_password": "Mot de passe oublié", + "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe", + "auth.register_now": "S'enregistrer", + "auth.logout": "Déconnexion", + "auth.register": "S'enregistrer", + "auth.registration_disabled": "L'enregistrement est désactivé. Merci de contacter l'administateur.", + "auth.reset_password": "Réinitialiser le mot de passe", + "auth.failed": "Aucune correspondance n'a été trouvé pour les informations d'identification renseignées.", + "auth.failed.callback": "Erreur lors du processus de retour de la plateforme de connexion.", + "auth.failed.password": "Le mot de passe renseigné est incorrect.", + "auth.failed.email": "Aucun utilisateur avec cette adresse email n'a été trouvé.", + "auth.throttle": "Trop de tentatives de connexion. Merci de réessayer dans :seconds secondes.", + "input.name": "Nom", + "input.email": "Email", + "input.password": "Mot de passe", + "input.password.again": "Mot de passe identique", + "input.code": "Code à usage unique", + "input.recovery_code": "Code de récupération", + "button.save": "Sauvegarder", + "repository.url": "Exemples
Pour les dépôts publiques, utilisez https://....
Pour les dépôts privés, utilisez git@....

https://github.com/coollabsio/coolify-examples main sera la branche selectionnée
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify sera la branche selectionnée.
https://gitea.com/sedlav/expressjs.git main sera la branche selectionnée.
https://gitlab.com/andrasbacsai/nodejs-example.git main sera la branche selectionnée." +} From 85d313a7912483f96dec5969946505864b16f401 Mon Sep 17 00:00:00 2001 From: lopesboa Date: Tue, 11 Jun 2024 12:21:03 -0300 Subject: [PATCH 06/71] chore: add portuguese traslation --- lang/pt.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/pt.json diff --git a/lang/pt.json b/lang/pt.json new file mode 100644 index 000000000..b5dd5c434 --- /dev/null +++ b/lang/pt.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Entrar", + "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.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 de redefinição de senha", + "auth.register_now": "Cadastrar-se", + "auth.logout": "Sair", + "auth.register": "Cadastrar", + "auth.registration_disabled": "Cadastro desativado. Por favor, entre em contato com 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 um 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": "Repetir senha", + "input.code": "Código ú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 a branch main será selecionada
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada.
https://gitea.com/sedlav/expressjs.git a branch main será selecionada.
https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada." +} From a138bb61bcf17fbad76ad2d0046c51038fa042e1 Mon Sep 17 00:00:00 2001 From: Flo Schuessel Date: Tue, 11 Jun 2024 17:35:26 +0200 Subject: [PATCH 07/71] Update logs.blade.php --- resources/views/livewire/project/shared/logs.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/views/livewire/project/shared/logs.blade.php b/resources/views/livewire/project/shared/logs.blade.php index c0eaae105..6b20d716b 100644 --- a/resources/views/livewire/project/shared/logs.blade.php +++ b/resources/views/livewire/project/shared/logs.blade.php @@ -19,7 +19,7 @@ @forelse (data_get($server,'containers',[]) as $container) @empty -
No containers are not running on server: {{ $server->name }}
+
No containers are running on server: {{ $server->name }}
@endforelse @@ -41,7 +41,7 @@
No functional server found for the database.
@endif @empty -
No containers are not running.
+
No containers are running.
@endforelse @elseif ($type === 'service') @@ -56,7 +56,7 @@
No functional server found for the service.
@endif @empty -
No containers are not running.
+
No containers are running.
@endforelse @endif From 6d9454b3513e21a7591ea3dc3aaf36539b46e357 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 11 Jun 2024 19:05:37 +0200 Subject: [PATCH 08/71] chore: Update version numbers to 4.0.0-beta.298 --- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index 33a24edfb..caa659921 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.297', + 'release' => '4.0.0-beta.298', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 06c1e6c66..ddcd3f2d4 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Tue, 11 Jun 2024 22:46:02 +0300 Subject: [PATCH 09/71] chore: add Turkish translations --- lang/tr.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/tr.json diff --git a/lang/tr.json b/lang/tr.json new file mode 100644 index 000000000..255b0d15b --- /dev/null +++ b/lang/tr.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Giriş", + "auth.login.azure": "Microsoft ile Giriş Yap", + "auth.login.bitbucket": "Bitbucket ile Giriş Yap", + "auth.login.github": "GitHub ile Giriş Yap", + "auth.login.gitlab": "GitLab ile Giriş Yap", + "auth.login.google": "Google ile Giriş Yap", + "auth.already_registered": "Zaten kayıtlı mısınız?", + "auth.confirm_password": "Şifreyi Onayla", + "auth.forgot_password": "Şifremi Unuttum", + "auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder", + "auth.register_now": "Kayıt Ol", + "auth.logout": "Çıkış Yap", + "auth.register": "Kayıt Ol", + "auth.registration_disabled": "Kayıt devre dışı bırakıldı. Lütfen yöneticiyle iletişime geçin.", + "auth.reset_password": "Şifreyi Sıfırla", + "auth.failed": "Bu kimlik bilgileri kayıtlarımızla eşleşmiyor.", + "auth.failed.callback": "Giriş sağlayıcıdan gelen istek işlenemedi.", + "auth.failed.password": "Sağlanan şifre yanlış.", + "auth.failed.email": "Bu e-posta adresiyle bir kullanıcı bulamıyoruz.", + "auth.throttle": "Çok fazla giriş denemesi. Lütfen :seconds saniye sonra tekrar deneyin.", + "input.name": "İsim", + "input.email": "E-posta", + "input.password": "Şifre", + "input.password.again": "Şifreyi Tekrar Girin", + "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." +} From b53bb44e42a017a50ed0f18fbd60851fd1f33e9c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 09:56:13 +0200 Subject: [PATCH 10/71] chore: switch to database sessions from redis --- config/session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/session.php b/config/session.php index c7b176a5a..44ca7ded9 100644 --- a/config/session.php +++ b/config/session.php @@ -18,7 +18,7 @@ | */ - 'driver' => env('SESSION_DRIVER', 'redis'), + 'driver' => env('SESSION_DRIVER', 'database'), /* |-------------------------------------------------------------------------- From 784cfb8fba146f12e5f85f620628e57af5dbe6e7 Mon Sep 17 00:00:00 2001 From: arthur <51604173+arthurauffray@users.noreply.github.com> Date: Wed, 12 Jun 2024 21:04:12 +1200 Subject: [PATCH 11/71] Updates to README: grammar, sentence structure, URL formats - Correct grammar mistakes - Updated URL markdown formatting to be uniform throughout (hidden protocol before URL on coolify.io domain) - Converted plain text links to markdown URLs --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 56bee004e..bfa849fa9 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ # About the Project Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. -It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything. +It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else. -Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**. +Imagine having the ease of a cloud but with your own servers. That is **Coolify**. -No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️ +No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️ -For more information, take a look at our landing page [here](https://coolify.io). +For more information, take a look at our landing page at [coolify.io](https://coolify.io). # Installation @@ -22,12 +22,12 @@ # Installation # Support -Contact us [here](https://coolify.io/docs/contact). +Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact). # Donations -To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project. +To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development. -https://coolify.io/sponsorships +[coolify.io/sponsorships](https://coolify.io/sponsorships) Thank you so much! @@ -83,9 +83,9 @@ ## Individuals # Cloud -If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io +If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io) -For more information & pricing, take a look at our landing page [here](https://coolify.io). +For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io). ## Why should I use the Cloud version? The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month. From e922bc207a8ff31bc8857443ef84e2a84b48335f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 11:30:25 +0200 Subject: [PATCH 12/71] chore: Update dependencies and remove unused code --- bootstrap/helpers/shared.php | 2 +- scripts/cloud_upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7994c10af..e84c30fad 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1,9 +1,9 @@ last_version docker compose logs -f From c58f468dc9487a78ac154e90c5a5f50357b1a5c6 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 12 Jun 2024 09:31:14 +0000 Subject: [PATCH 13/71] Fix styling --- bootstrap/helpers/shared.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index e84c30fad..7994c10af 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1,9 +1,9 @@ Date: Wed, 12 Jun 2024 11:35:07 +0200 Subject: [PATCH 14/71] fix: bitbucket link --- app/Models/Application.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 6e55f6626..bebf7c61c 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -235,11 +235,6 @@ public function gitCommitLink($link): string return "{$this->source->html_url}/{$this->git_repository}/commit/{$link}"; } - if (strpos($this->git_repository, 'git@') === 0) { - $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); - - return "https://{$git_repository}/commit/{$link}"; - } if (str($this->git_repository)->contains('bitbucket')) { $git_repository = str_replace('.git', '', $this->git_repository); $url = Url::fromString($git_repository); @@ -248,6 +243,10 @@ public function gitCommitLink($link): string return $url->__toString(); } + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commit/{$link}"; + } return $this->git_repository; } From 2335abac91818445ee3387d4bc8293dd734911d8 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 12 Jun 2024 09:35:55 +0000 Subject: [PATCH 15/71] Fix styling --- app/Models/Application.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Models/Application.php b/app/Models/Application.php index bebf7c61c..e536f8d69 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -245,6 +245,7 @@ public function gitCommitLink($link): string } if (strpos($this->git_repository, 'git@') === 0) { $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commit/{$link}"; } From f332a73122888573ca83f33f85a67c5e1c5f413b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 12:05:08 +0200 Subject: [PATCH 16/71] feat: cancelling a deployment will check if new could be started. --- .../Project/Application/DeploymentNavbar.php | 5 ++-- app/Models/Application.php | 2 +- bootstrap/helpers/applications.php | 29 +++++++++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index cbbe98d99..b3e39d23d 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -54,9 +54,9 @@ public function force_start() public function cancel() { + $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; + $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id; try { - $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; - $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id; $server = Server::find($server_id); if ($this->application_deployment_queue->logs) { $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); @@ -84,6 +84,7 @@ public function cancel() 'current_process_id' => null, 'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, ]); + next_after_cancel($server); } } } diff --git a/app/Models/Application.php b/app/Models/Application.php index bebf7c61c..532fc5d4a 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -531,7 +531,7 @@ public function isDeploymentInprogress() public function get_last_successful_deployment() { - return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'finished')->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first(); + return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::FINISHED)->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first(); } public function get_last_days_deployments() diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 376b0f2aa..8bfa4eac1 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -11,7 +11,7 @@ function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) { $application_id = $application->id; - $deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}"); + $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); $deployment_url = $deployment_link->getPath(); $server_id = $application->destination->server->id; $server_name = $application->destination->server->name; @@ -65,7 +65,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment) function queue_next_deployment(Application $application) { $server_id = $application->destination->server_id; - $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first(); + $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at')->first(); if ($next_found) { $next_found->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, @@ -79,7 +79,7 @@ function queue_next_deployment(Application $application) function next_queuable(string $server_id, string $application_id): bool { - $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at'); + $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at'); $same_application_deployments = $deployments->where('application_id', $application_id); $in_progress = $same_application_deployments->filter(function ($value, $key) { return $value->status === 'in_progress'; @@ -98,3 +98,26 @@ function next_queuable(string $server_id, string $application_id): bool return true; } +function next_after_cancel(?Server $server = null) +{ + if ($server) { + $next_found = ApplicationDeploymentQueue::where('server_id', data_get($server, 'id'))->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at'); + if ($next_found->count() > 0) { + foreach ($next_found as $next) { + $server = Server::find($next->server_id); + $concurrent_builds = $server->settings->concurrent_builds; + $inprogress_deployments = ApplicationDeploymentQueue::where('server_id', $next->server_id)->whereIn('status', [ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at'); + if ($inprogress_deployments->count() < $concurrent_builds) { + $next->update([ + 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, + ]); + + dispatch(new ApplicationDeploymentJob( + application_deployment_queue_id: $next->id, + )); + } + break; + } + } + } +} From ce243529748a1c63a7ebfb930e1c209c6007b2da Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 12 Jun 2024 10:05:54 +0000 Subject: [PATCH 17/71] Fix styling --- bootstrap/helpers/applications.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 8bfa4eac1..816a13853 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -11,7 +11,7 @@ function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) { $application_id = $application->id; - $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); + $deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}"); $deployment_url = $deployment_link->getPath(); $server_id = $application->destination->server->id; $server_name = $application->destination->server->name; From 019cdd2b3a2e3a465e7260422236d7aa77024049 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 12:20:58 +0200 Subject: [PATCH 18/71] fix: compose generator --- app/Http/Controllers/Webhook/Bitbucket.php | 41 +++++++++------ app/Http/Controllers/Webhook/Gitea.php | 24 ++++++--- app/Http/Controllers/Webhook/Github.php | 51 +++++++++++-------- app/Http/Controllers/Webhook/Gitlab.php | 47 ++++++++++------- app/Livewire/Project/Application/Previews.php | 4 ++ .../project/application/previews.blade.php | 2 +- 6 files changed, 109 insertions(+), 60 deletions(-) diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index b9035b755..f666d55cc 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -39,7 +39,7 @@ public function manual(Request $request) $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ''); $x_bitbucket_event = data_get($headers, 'x-event-key.0', ''); $handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']); - if (! $handled_events->contains($x_bitbucket_event)) { + if (!$handled_events->contains($x_bitbucket_event)) { return response([ 'status' => 'failed', 'message' => 'Nothing to do. Event not handled.', @@ -49,13 +49,13 @@ public function manual(Request $request) $branch = data_get($payload, 'push.changes.0.new.name'); $full_name = data_get($payload, 'repository.full_name'); $commit = data_get($payload, 'push.changes.0.new.target.hash'); - if (! $branch) { + if (!$branch) { return response([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', ]); } - ray('Manual webhook bitbucket push event with branch: '.$branch); + ray('Manual webhook bitbucket push event with branch: ' . $branch); } if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { $branch = data_get($payload, 'pullrequest.destination.branch.name'); @@ -79,7 +79,7 @@ public function manual(Request $request) [$algo, $hash] = explode('=', $x_bitbucket_token, 2); $payloadHash = hash_hmac($algo, $payload, $webhook_secret); - if (! hash_equals($hash, $payloadHash) && ! isDev()) { + if (!hash_equals($hash, $payloadHash) && !isDev()) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', @@ -90,19 +90,19 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (! $isFunctional) { + if (!$isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional.', ]); - ray('Server is not functional: '.$application->destination->server->name); + ray('Server is not functional: ' . $application->destination->server->name); continue; } if ($x_bitbucket_event === 'repo:push') { if ($application->isDeployable()) { - ray('Deploying '.$application->name.' with branch '.$branch); + ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -126,16 +126,27 @@ public function manual(Request $request) } if ($x_bitbucket_event === 'pullrequest:created') { if ($application->isPRDeployable()) { - ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); + ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (! $found) { - ApplicationPreview::create([ - 'git_type' => 'bitbucket', - 'application_id' => $application->id, - 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url, - ]); + if (!$found) { + if ($application->build_pack === 'dockercompose') { + $pr_app = ApplicationPreview::create([ + 'git_type' => 'bitbucket', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + 'docker_compose_domains' => $application->docker_compose_domains, + ]); + $pr_app->generate_preview_fqdn_compose(); + } else { + ApplicationPreview::create([ + 'git_type' => 'bitbucket', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } } queue_application_deployment( application: $application, diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index 388481949..e6d91efd6 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -165,12 +165,24 @@ public function manual(Request $request) $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); if (! $found) { - ApplicationPreview::create([ - 'git_type' => 'gitea', - 'application_id' => $application->id, - 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url, - ]); + if ($application->build_pack === 'dockercompose') { + $pr_app = ApplicationPreview::create([ + 'git_type' => 'gitea', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + 'docker_compose_domains' => $application->docker_compose_domains, + ]); + $pr_app->generate_preview_fqdn_compose(); + } else { + ApplicationPreview::create([ + 'git_type' => 'gitea', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } + } queue_application_deployment( application: $application, diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 403438193..87c7e283a 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -73,7 +73,7 @@ public function manual(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Manual Webhook GitHub Push Event with branch: '.$branch); + ray('Manual Webhook GitHub Push Event with branch: ' . $branch); } if ($x_github_event === 'pull_request') { $action = data_get($payload, 'action'); @@ -82,9 +82,9 @@ public function manual(Request $request) $pull_request_html_url = data_get($payload, 'pull_request.html_url'); $branch = data_get($payload, 'pull_request.head.ref'); $base_branch = data_get($payload, 'pull_request.base.ref'); - ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); + ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); } - if (! $branch) { + if (!$branch) { return response('Nothing to do. No branch found in the request.'); } $applications = Application::where('git_repository', 'like', "%$full_name%"); @@ -103,7 +103,7 @@ public function manual(Request $request) foreach ($applications as $application) { $webhook_secret = data_get($application, 'manual_webhook_secret_github'); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); - if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) { + if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { ray('Invalid signature'); $return_payloads->push([ 'application' => $application->name, @@ -114,7 +114,7 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (! $isFunctional) { + if (!$isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', @@ -127,7 +127,7 @@ public function manual(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying '.$application->name.' with branch '.$branch); + ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -169,13 +169,24 @@ public function manual(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (! $found) { - ApplicationPreview::create([ - 'git_type' => 'github', - 'application_id' => $application->id, - 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url, - ]); + if (!$found) { + if ($application->build_pack === 'dockercompose') { + $pr_app = ApplicationPreview::create([ + 'git_type' => 'github', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + 'docker_compose_domains' => $application->docker_compose_domains, + ]); + $pr_app->generate_preview_fqdn_compose(); + } else { + ApplicationPreview::create([ + 'git_type' => 'github', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } } queue_application_deployment( application: $application, @@ -279,7 +290,7 @@ public function normal(Request $request) $webhook_secret = data_get($github_app, 'webhook_secret'); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); if (config('app.env') !== 'local') { - if (! hash_equals($x_hub_signature_256, $hmac)) { + if (!hash_equals($x_hub_signature_256, $hmac)) { return response('Invalid signature.'); } } @@ -302,7 +313,7 @@ public function normal(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Webhook GitHub Push Event: '.$id.' with branch: '.$branch); + ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); } if ($x_github_event === 'pull_request') { $action = data_get($payload, 'action'); @@ -311,9 +322,9 @@ public function normal(Request $request) $pull_request_html_url = data_get($payload, 'pull_request.html_url'); $branch = data_get($payload, 'pull_request.head.ref'); $base_branch = data_get($payload, 'pull_request.base.ref'); - ray('Webhook GitHub Pull Request Event: '.$id.' with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); + ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); } - if (! $id || ! $branch) { + if (!$id || !$branch) { return response('Nothing to do. No id or branch found.'); } $applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false); @@ -332,7 +343,7 @@ public function normal(Request $request) foreach ($applications as $application) { $isFunctional = $application->destination->server->isFunctional(); - if (! $isFunctional) { + if (!$isFunctional) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Server is not functional.', @@ -346,7 +357,7 @@ public function normal(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying '.$application->name.' with branch '.$branch); + ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -388,7 +399,7 @@ public function normal(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (! $found) { + if (!$found) { ApplicationPreview::create([ 'git_type' => 'github', 'application_id' => $application->id, diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index a3d7712eb..7ae376f1f 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -40,7 +40,7 @@ public function manual(Request $request) $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_event = data_get($payload, 'object_kind'); $allowed_events = ['push', 'merge_request']; - if (! in_array($x_gitlab_event, $allowed_events)) { + if (!in_array($x_gitlab_event, $allowed_events)) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Event not allowed. Only push and merge_request events are allowed.', @@ -55,7 +55,7 @@ public function manual(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } - if (! $branch) { + if (!$branch) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', @@ -67,7 +67,7 @@ public function manual(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Manual Webhook GitLab Push Event with branch: '.$branch); + ray('Manual Webhook GitLab Push Event with branch: ' . $branch); } if ($x_gitlab_event === 'merge_request') { $action = data_get($payload, 'object_attributes.action'); @@ -76,7 +76,7 @@ public function manual(Request $request) $full_name = data_get($payload, 'project.path_with_namespace'); $pull_request_id = data_get($payload, 'object_attributes.iid'); $pull_request_html_url = data_get($payload, 'object_attributes.url'); - if (! $branch) { + if (!$branch) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', @@ -84,7 +84,7 @@ public function manual(Request $request) return response($return_payloads); } - ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); + ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); } $applications = Application::where('git_repository', 'like', "%$full_name%"); if ($x_gitlab_event === 'push') { @@ -122,13 +122,13 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (! $isFunctional) { + if (!$isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional', ]); - ray('Server is not functional: '.$application->destination->server->name); + ray('Server is not functional: ' . $application->destination->server->name); continue; } @@ -136,7 +136,7 @@ public function manual(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying '.$application->name.' with branch '.$branch); + ray('Deploying ' . $application->name . ' with branch ' . $branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -171,7 +171,7 @@ public function manual(Request $request) 'application_uuid' => $application->uuid, 'application_name' => $application->name, ]); - ray('Deployments disabled for '.$application->name); + ray('Deployments disabled for ' . $application->name); } } if ($x_gitlab_event === 'merge_request') { @@ -179,13 +179,24 @@ public function manual(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (! $found) { - ApplicationPreview::create([ - 'git_type' => 'gitlab', - 'application_id' => $application->id, - 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url, - ]); + if (!$found) { + if ($application->build_pack === 'dockercompose') { + $pr_app = ApplicationPreview::create([ + 'git_type' => 'gitlab', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + 'docker_compose_domains' => $application->docker_compose_domains, + ]); + $pr_app->generate_preview_fqdn_compose(); + } else { + ApplicationPreview::create([ + 'git_type' => 'gitlab', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } } queue_application_deployment( application: $application, @@ -196,7 +207,7 @@ public function manual(Request $request) is_webhook: true, git_type: 'gitlab' ); - ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); + ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); $return_payloads->push([ 'application' => $application->name, 'status' => 'success', @@ -208,7 +219,7 @@ public function manual(Request $request) 'status' => 'failed', 'message' => 'Preview deployments disabled', ]); - ray('Preview deployments disabled for '.$application->name); + ray('Preview deployments disabled for ' . $application->name); } } elseif ($action === 'closed' || $action === 'close' || $action === 'merge') { $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index ca911339e..ea8a19764 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -131,6 +131,10 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null) } } + public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) { + $this->add($pull_request_id, $pull_request_html_url); + $this->deploy($pull_request_id, $pull_request_html_url); + } public function deploy(int $pull_request_id, ?string $pull_request_html_url = null) { try { diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 79a7b88b0..2f142abbb 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -45,7 +45,7 @@ class="dark:text-warning">{{ $application->destination->server->name }}.< Configure + wire:click="add_and_deploy('{{ data_get($pull_request, 'number') }}', '{{ data_get($pull_request, 'html_url') }}')"> From 2e01665340bf8bd948ad7a2209fbe5fd7e42ad80 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 12 Jun 2024 10:21:47 +0000 Subject: [PATCH 19/71] Fix styling --- app/Http/Controllers/Webhook/Bitbucket.php | 18 ++++++------ app/Http/Controllers/Webhook/Github.php | 28 +++++++++---------- app/Http/Controllers/Webhook/Gitlab.php | 24 ++++++++-------- app/Livewire/Project/Application/Previews.php | 4 ++- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index f666d55cc..059438ff4 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -39,7 +39,7 @@ public function manual(Request $request) $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ''); $x_bitbucket_event = data_get($headers, 'x-event-key.0', ''); $handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']); - if (!$handled_events->contains($x_bitbucket_event)) { + if (! $handled_events->contains($x_bitbucket_event)) { return response([ 'status' => 'failed', 'message' => 'Nothing to do. Event not handled.', @@ -49,13 +49,13 @@ public function manual(Request $request) $branch = data_get($payload, 'push.changes.0.new.name'); $full_name = data_get($payload, 'repository.full_name'); $commit = data_get($payload, 'push.changes.0.new.target.hash'); - if (!$branch) { + if (! $branch) { return response([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', ]); } - ray('Manual webhook bitbucket push event with branch: ' . $branch); + ray('Manual webhook bitbucket push event with branch: '.$branch); } if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { $branch = data_get($payload, 'pullrequest.destination.branch.name'); @@ -79,7 +79,7 @@ public function manual(Request $request) [$algo, $hash] = explode('=', $x_bitbucket_token, 2); $payloadHash = hash_hmac($algo, $payload, $webhook_secret); - if (!hash_equals($hash, $payloadHash) && !isDev()) { + if (! hash_equals($hash, $payloadHash) && ! isDev()) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', @@ -90,19 +90,19 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (!$isFunctional) { + if (! $isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional.', ]); - ray('Server is not functional: ' . $application->destination->server->name); + ray('Server is not functional: '.$application->destination->server->name); continue; } if ($x_bitbucket_event === 'repo:push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); + ray('Deploying '.$application->name.' with branch '.$branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -126,10 +126,10 @@ public function manual(Request $request) } if ($x_bitbucket_event === 'pullrequest:created') { if ($application->isPRDeployable()) { - ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); + ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (!$found) { + if (! $found) { if ($application->build_pack === 'dockercompose') { $pr_app = ApplicationPreview::create([ 'git_type' => 'bitbucket', diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 87c7e283a..a030e31ca 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -73,7 +73,7 @@ public function manual(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Manual Webhook GitHub Push Event with branch: ' . $branch); + ray('Manual Webhook GitHub Push Event with branch: '.$branch); } if ($x_github_event === 'pull_request') { $action = data_get($payload, 'action'); @@ -82,9 +82,9 @@ public function manual(Request $request) $pull_request_html_url = data_get($payload, 'pull_request.html_url'); $branch = data_get($payload, 'pull_request.head.ref'); $base_branch = data_get($payload, 'pull_request.base.ref'); - ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); } - if (!$branch) { + if (! $branch) { return response('Nothing to do. No branch found in the request.'); } $applications = Application::where('git_repository', 'like', "%$full_name%"); @@ -103,7 +103,7 @@ public function manual(Request $request) foreach ($applications as $application) { $webhook_secret = data_get($application, 'manual_webhook_secret_github'); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); - if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { + if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) { ray('Invalid signature'); $return_payloads->push([ 'application' => $application->name, @@ -114,7 +114,7 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (!$isFunctional) { + if (! $isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', @@ -127,7 +127,7 @@ public function manual(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); + ray('Deploying '.$application->name.' with branch '.$branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -169,7 +169,7 @@ public function manual(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (!$found) { + if (! $found) { if ($application->build_pack === 'dockercompose') { $pr_app = ApplicationPreview::create([ 'git_type' => 'github', @@ -290,7 +290,7 @@ public function normal(Request $request) $webhook_secret = data_get($github_app, 'webhook_secret'); $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); if (config('app.env') !== 'local') { - if (!hash_equals($x_hub_signature_256, $hmac)) { + if (! hash_equals($x_hub_signature_256, $hmac)) { return response('Invalid signature.'); } } @@ -313,7 +313,7 @@ public function normal(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); + ray('Webhook GitHub Push Event: '.$id.' with branch: '.$branch); } if ($x_github_event === 'pull_request') { $action = data_get($payload, 'action'); @@ -322,9 +322,9 @@ public function normal(Request $request) $pull_request_html_url = data_get($payload, 'pull_request.html_url'); $branch = data_get($payload, 'pull_request.head.ref'); $base_branch = data_get($payload, 'pull_request.base.ref'); - ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + ray('Webhook GitHub Pull Request Event: '.$id.' with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); } - if (!$id || !$branch) { + if (! $id || ! $branch) { return response('Nothing to do. No id or branch found.'); } $applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false); @@ -343,7 +343,7 @@ public function normal(Request $request) foreach ($applications as $application) { $isFunctional = $application->destination->server->isFunctional(); - if (!$isFunctional) { + if (! $isFunctional) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Server is not functional.', @@ -357,7 +357,7 @@ public function normal(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); + ray('Deploying '.$application->name.' with branch '.$branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -399,7 +399,7 @@ public function normal(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (!$found) { + if (! $found) { ApplicationPreview::create([ 'git_type' => 'github', 'application_id' => $application->id, diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 7ae376f1f..f6e6cf7e7 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -40,7 +40,7 @@ public function manual(Request $request) $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_event = data_get($payload, 'object_kind'); $allowed_events = ['push', 'merge_request']; - if (!in_array($x_gitlab_event, $allowed_events)) { + if (! in_array($x_gitlab_event, $allowed_events)) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Event not allowed. Only push and merge_request events are allowed.', @@ -55,7 +55,7 @@ public function manual(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } - if (!$branch) { + if (! $branch) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', @@ -67,7 +67,7 @@ public function manual(Request $request) $removed_files = data_get($payload, 'commits.*.removed'); $modified_files = data_get($payload, 'commits.*.modified'); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); - ray('Manual Webhook GitLab Push Event with branch: ' . $branch); + ray('Manual Webhook GitLab Push Event with branch: '.$branch); } if ($x_gitlab_event === 'merge_request') { $action = data_get($payload, 'object_attributes.action'); @@ -76,7 +76,7 @@ public function manual(Request $request) $full_name = data_get($payload, 'project.path_with_namespace'); $pull_request_id = data_get($payload, 'object_attributes.iid'); $pull_request_html_url = data_get($payload, 'object_attributes.url'); - if (!$branch) { + if (! $branch) { $return_payloads->push([ 'status' => 'failed', 'message' => 'Nothing to do. No branch found in the request.', @@ -84,7 +84,7 @@ public function manual(Request $request) return response($return_payloads); } - ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); } $applications = Application::where('git_repository', 'like', "%$full_name%"); if ($x_gitlab_event === 'push') { @@ -122,13 +122,13 @@ public function manual(Request $request) continue; } $isFunctional = $application->destination->server->isFunctional(); - if (!$isFunctional) { + if (! $isFunctional) { $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional', ]); - ray('Server is not functional: ' . $application->destination->server->name); + ray('Server is not functional: '.$application->destination->server->name); continue; } @@ -136,7 +136,7 @@ public function manual(Request $request) if ($application->isDeployable()) { $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); if ($is_watch_path_triggered || is_null($application->watch_paths)) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); + ray('Deploying '.$application->name.' with branch '.$branch); $deployment_uuid = new Cuid2(7); queue_application_deployment( application: $application, @@ -171,7 +171,7 @@ public function manual(Request $request) 'application_uuid' => $application->uuid, 'application_name' => $application->name, ]); - ray('Deployments disabled for ' . $application->name); + ray('Deployments disabled for '.$application->name); } } if ($x_gitlab_event === 'merge_request') { @@ -179,7 +179,7 @@ public function manual(Request $request) if ($application->isPRDeployable()) { $deployment_uuid = new Cuid2(7); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); - if (!$found) { + if (! $found) { if ($application->build_pack === 'dockercompose') { $pr_app = ApplicationPreview::create([ 'git_type' => 'gitlab', @@ -207,7 +207,7 @@ public function manual(Request $request) is_webhook: true, git_type: 'gitlab' ); - ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); + ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); $return_payloads->push([ 'application' => $application->name, 'status' => 'success', @@ -219,7 +219,7 @@ public function manual(Request $request) 'status' => 'failed', 'message' => 'Preview deployments disabled', ]); - ray('Preview deployments disabled for ' . $application->name); + ray('Preview deployments disabled for '.$application->name); } } elseif ($action === 'closed' || $action === 'close' || $action === 'merge') { $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index ea8a19764..f29cd43ce 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -131,10 +131,12 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null) } } - public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) { + public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) + { $this->add($pull_request_id, $pull_request_html_url); $this->deploy($pull_request_id, $pull_request_html_url); } + public function deploy(int $pull_request_id, ?string $pull_request_html_url = null) { try { From 9592076d45a5c71dc051bd1216a81d01f02b945f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 12:28:09 +0200 Subject: [PATCH 20/71] fix: do no truncate repositories wtih domain (git) in it --- .../Project/New/PublicGitRepository.php | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 739061f1f..5ba14e250 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -117,19 +117,19 @@ public function load_branch() if (str($this->repository_url)->startsWith('git@')) { $github_instance = str($this->repository_url)->after('git@')->before(':'); $repository = str($this->repository_url)->after(':')->before('.git'); - $this->repository_url = 'https://'.str($github_instance).'/'.$repository; + $this->repository_url = 'https://' . str($github_instance) . '/' . $repository; } if ( (str($this->repository_url)->startsWith('https://') || str($this->repository_url)->startsWith('http://')) && - ! str($this->repository_url)->endsWith('.git') && - (! str($this->repository_url)->contains('github.com') || - ! str($this->repository_url)->contains('git.sr.ht')) + !str($this->repository_url)->endsWith('.git') && + (!str($this->repository_url)->contains('github.com') || + !str($this->repository_url)->contains('git.sr.ht')) ) { - $this->repository_url = $this->repository_url.'.git'; + $this->repository_url = $this->repository_url . '.git'; } - if (str($this->repository_url)->contains('github.com')) { - $this->repository_url = str($this->repository_url)->before('.git')->value(); + if (str($this->repository_url)->contains('github.com') && str($this->repository_url)->endsWith('.git')) { + $this->repository_url = str($this->repository_url)->beforeLast('.git')->value(); } } catch (\Throwable $e) { return handleError($e, $this); @@ -140,8 +140,7 @@ public function load_branch() $this->get_branch(); $this->selected_branch = $this->git_branch; } catch (\Throwable $e) { - ray($e->getMessage()); - if (! $this->branch_found && $this->git_branch == 'main') { + if (!$this->branch_found && $this->git_branch == 'main') { try { $this->git_branch = 'master'; $this->get_branch(); @@ -158,7 +157,7 @@ private function get_git_source() { $this->repository_url_parsed = Url::fromString($this->repository_url); $this->git_host = $this->repository_url_parsed->getHost(); - $this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2); + $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); $this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main'; if ($this->git_host == 'github.com') { @@ -193,10 +192,10 @@ public function submit() $environment_name = $this->parameters['environment_name']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); - if (! $destination) { + if (!$destination) { $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); } - if (! $destination) { + if (!$destination) { throw new \Exception('Destination not found. What?!'); } $destination_class = $destination->getMorphClass(); @@ -207,7 +206,7 @@ public function submit() if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) { $server = $destination->server; $new_service = [ - 'name' => 'service'.str()->random(10), + 'name' => 'service' . str()->random(10), 'docker_compose_raw' => 'coolify', 'environment_id' => $environment->id, 'server_id' => $server->id, From 5c8277ea1d0e56be5d0eabb4825ad56184e1183f Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 12 Jun 2024 10:28:52 +0000 Subject: [PATCH 21/71] Fix styling --- .../Project/New/PublicGitRepository.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 5ba14e250..7ac7883dc 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -117,16 +117,16 @@ public function load_branch() if (str($this->repository_url)->startsWith('git@')) { $github_instance = str($this->repository_url)->after('git@')->before(':'); $repository = str($this->repository_url)->after(':')->before('.git'); - $this->repository_url = 'https://' . str($github_instance) . '/' . $repository; + $this->repository_url = 'https://'.str($github_instance).'/'.$repository; } if ( (str($this->repository_url)->startsWith('https://') || str($this->repository_url)->startsWith('http://')) && - !str($this->repository_url)->endsWith('.git') && - (!str($this->repository_url)->contains('github.com') || - !str($this->repository_url)->contains('git.sr.ht')) + ! str($this->repository_url)->endsWith('.git') && + (! str($this->repository_url)->contains('github.com') || + ! str($this->repository_url)->contains('git.sr.ht')) ) { - $this->repository_url = $this->repository_url . '.git'; + $this->repository_url = $this->repository_url.'.git'; } if (str($this->repository_url)->contains('github.com') && str($this->repository_url)->endsWith('.git')) { $this->repository_url = str($this->repository_url)->beforeLast('.git')->value(); @@ -140,7 +140,7 @@ public function load_branch() $this->get_branch(); $this->selected_branch = $this->git_branch; } catch (\Throwable $e) { - if (!$this->branch_found && $this->git_branch == 'main') { + if (! $this->branch_found && $this->git_branch == 'main') { try { $this->git_branch = 'master'; $this->get_branch(); @@ -157,7 +157,7 @@ private function get_git_source() { $this->repository_url_parsed = Url::fromString($this->repository_url); $this->git_host = $this->repository_url_parsed->getHost(); - $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); + $this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2); $this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main'; if ($this->git_host == 'github.com') { @@ -192,10 +192,10 @@ public function submit() $environment_name = $this->parameters['environment_name']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); - if (!$destination) { + if (! $destination) { $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); } - if (!$destination) { + if (! $destination) { throw new \Exception('Destination not found. What?!'); } $destination_class = $destination->getMorphClass(); @@ -206,7 +206,7 @@ public function submit() if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) { $server = $destination->server; $new_service = [ - 'name' => 'service' . str()->random(10), + 'name' => 'service'.str()->random(10), 'docker_compose_raw' => 'coolify', 'environment_id' => $environment->id, 'server_id' => $server->id, From 7a63a17b664beeee17a60261078b15a0aa1e76c5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 12 Jun 2024 12:30:59 +0200 Subject: [PATCH 22/71] feat: add supaguide logo to donations section --- README.md | 2 +- other/logos/supaguide.png | Bin 0 -> 16916 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 other/logos/supaguide.png diff --git a/README.md b/README.md index bfa849fa9..129c07bf7 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ # Donations bc direct logo quantcdn logo arcjet logo - +supaguide logo ## Github Sponsors ($40+) SerpAPI diff --git a/other/logos/supaguide.png b/other/logos/supaguide.png new file mode 100644 index 0000000000000000000000000000000000000000..195f3ce92a11a2f87ae9482ccbaedbd1fa4e9c46 GIT binary patch literal 16916 zcmeIaWmsIx)-Z^>yK8W1+}+*Xp>cO7xNCsm1PB&1xVt8}yF+ld5F~t^mbOid*V4)*xsi+7>4=Tff!GdFeLH+^(eZas8z@UGZ!NAPG3IA2L1*iQB z1_A`b3I;lZp@Y7z;QYVKzi`4LZNQ-Z!i@o4e-#;jo&LUxn>u=$lF_PJIGS6yTDa1I zfk~KpT7V!R^TGcuK|aLaR3O;%A^$Fe<@};&sVTnS{F;o8ff_oPWdxC*M zBim@`xa%k?@By71m`%-{%q*C_9h`q*feCo?fr<_m?xtkk4)%_2eBOc-zajWQWL(TF%q$c_2xMer0f1!hZ2~t?QyF2p%0A5~R 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 LBvCD967qilD!3iZ literal 0 HcmV?d00001 From 25649f578d74b01f78bbdc6489130f84e11a2348 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 12 Jun 2024 13:33:06 +0200 Subject: [PATCH 23/71] Create de.json --- lang/de.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/de.json diff --git a/lang/de.json b/lang/de.json new file mode 100644 index 000000000..8c9b41de1 --- /dev/null +++ b/lang/de.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Anmelden", + "auth.login.azure": "Mit Microsoft anmelden", + "auth.login.bitbucket": "Mit Bitbucket anmelden", + "auth.login.github": "Mit GitHub anmelden", + "auth.login.gitlab": "Mit GitLab anmelden", + "auth.login.google": "Mit Google anmelden", + "auth.already_registered": "Bereits registriert?", + "auth.confirm_password": "Passwort bestätigen", + "auth.forgot_password": "Passwort vergessen", + "auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden", + "auth.register_now": "Registrieren", + "auth.logout": "Abmelden", + "auth.register": "Registrieren", + "auth.registration_disabled": "Registrierung ist deaktiviert. Bitte kontaktiere einen Administrator.", + "auth.reset_password": "Passwort zurücksetzen", + "auth.failed": "Diese Anmeldedaten wurden nicht gefunden.", + "auth.failed.callback": "Fehlerhafte Verarbeitung der Antwort des Anmeldeanbieters.", + "auth.failed.password": "Das angegebene Passwort ist inkorrekt.", + "auth.failed.email": "Wir können keinen Benutzer mit dieser E-Mail Adresse finden.", + "auth.throttle": "Zu viele anmeldeversuche. Bitte versuchen es in :seconds Sekunden erneut.", + "input.name": "Name", + "input.email": "E-Mail", + "input.password": "Passwort", + "input.password.again": "Passwort wiederholen", + "input.code": "Einmalcode", + "input.recovery_code": "Wiederherstellungscode", + "button.save": "Speichern", + "repository.url": "Beispiele
Für öffentliche Reposetories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." +} From 19b38074a54933521d008392d7ba5921510dc371 Mon Sep 17 00:00:00 2001 From: Rishikesh S Date: Wed, 12 Jun 2024 19:04:00 +0530 Subject: [PATCH 24/71] add zorin as a os type --- scripts/install.sh | 5 +++++ 1 file changed, 5 insertions(+) mode change 100644 => 100755 scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh old mode 100644 new mode 100755 index b55db4f7d..2aaaebaef --- a/scripts/install.sh +++ b/scripts/install.sh @@ -27,6 +27,11 @@ if [ "$OS_TYPE" = "linuxmint" ]; then OS_TYPE="ubuntu" fi +#Check if the OS is zorin, if so, change it to ubuntu +if [ "$OS_TYPE" = "zorin" ]; then + OS_TYPE="ubuntu" +fi + if [ "$OS_TYPE" = "arch" ] || [ "$OS_TYPE" = "archarm" ]; then OS_VERSION="rolling" else From 6bb565ee6768a8a0b2cd52e29c2c1d21e8bd5116 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 13 Jun 2024 08:30:41 +0100 Subject: [PATCH 25/71] Create it.json chore: add Italian translations --- lang/it.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lang/it.json diff --git a/lang/it.json b/lang/it.json new file mode 100644 index 000000000..6e4feb9cc --- /dev/null +++ b/lang/it.json @@ -0,0 +1,30 @@ +{ + "auth.login": "Accedi", + "auth.login.azure": "Accedi con Microsoft", + "auth.login.bitbucket": "Accedi con Bitbucket", + "auth.login.github": "Accedi con GitHub", + "auth.login.gitlab": "Accedi con Gitlab", + "auth.login.google": "Accedi con Google", + "auth.already_registered": "Già registrato?", + "auth.confirm_password": "Conferma password", + "auth.forgot_password": "Password dimenticata", + "auth.forgot_password_send_email": "Invia email per reimpostare la password", + "auth.register_now": "Registrati", + "auth.logout": "Esci", + "auth.register": "Registrati", + "auth.registration_disabled": "La registrazione è disabilitata. Si prega di contattare l'amministratore.", + "auth.reset_password": "Reimposta password", + "auth.failed": "Queste credenziali non corrispondono ai nostri record.", + "auth.failed.callback": "Errore durante l'elaborazione del callback dal provider di accesso.", + "auth.failed.password": "La password fornita non è corretta.", + "auth.failed.email": "Non possiamo trovare un utente con questo indirizzo email.", + "auth.throttle": "Troppi tentativi di accesso. Per favore riprova tra :seconds secondi.", + "input.name": "Nome", + "input.email": "Email", + "input.password": "Password", + "input.password.again": "Ripeti password", + "input.code": "Codice monouso", + "input.recovery_code": "Codice di recupero", + "button.save": "Salva", + "repository.url": "Esempi
Per i repository pubblici, utilizza https://....
Per i repository privati, utilizza git@....

https://github.com/coollabsio/coolify-examples verrà selezionato il branch main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify verrà selezionato il branch nodejs-fastify.
https://gitea.com/sedlav/expressjs.git verrà selezionato il branch main.
https://gitlab.com/andrasbacsai/nodejs-example.git verrà selezionato il branch main." +} From 95d3ebdc2dd8e026afcd0107d58011bbf847e73b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 10:18:35 +0200 Subject: [PATCH 26/71] fix: in services should edit compose file for volumes and envs --- .../project/service/configuration.blade.php | 3 ++- .../shared/environment-variable/all.blade.php | 8 +++++--- .../livewire/project/shared/storages/show.blade.php | 13 ++++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index d1ff54a5c..272de2f4f 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -172,7 +172,8 @@ class="w-4 h-4 dark:text-warning text-coollabs"

Storages

Persistent storage to preserve data between deployments.
- Please modify storage layout in your Docker Compose file. +
If you would like to add a volume, you must add it to + your compose file (General tab).
@foreach ($applications as $application) diff --git a/resources/views/livewire/project/shared/environment-variable/all.blade.php b/resources/views/livewire/project/shared/environment-variable/all.blade.php index d2693b983..42a2b48a7 100644 --- a/resources/views/livewire/project/shared/environment-variable/all.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/all.blade.php @@ -6,19 +6,21 @@ + {{ $view === 'normal' ? 'Developer view' : 'Normal view' }} @endif - {{ $view === 'normal' ? 'Developer view' : 'Normal view' }}
Environment variables (secrets) for this resource.
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
+ helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order." + instantSave>
@endif @if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
Hardcoded variables are not shown here.
+
If you would like to add a variable, you must add it to your compose file (General tab).
@endif @if ($view === 'normal') diff --git a/resources/views/livewire/project/shared/storages/show.blade.php b/resources/views/livewire/project/shared/storages/show.blade.php index 935d0a43b..6b429a535 100644 --- a/resources/views/livewire/project/shared/storages/show.blade.php +++ b/resources/views/livewire/project/shared/storages/show.blade.php @@ -2,8 +2,15 @@
@if ($isReadOnly) @if ($isFirst) - + @if ( + $storage->resource_type === 'App\Models\ServiceApplication' || + $storage->resource_type === 'App\Models\ServiceDatabase') + + @else + + @endif @if ($isService || $startedAt) @else - + @endif @else From 7485c1240bcc275ce90ae5d00801ea357c800e8b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 12:02:52 +0200 Subject: [PATCH 27/71] feat: nixpacks now could reach local dbs internally --- app/Jobs/ApplicationDeploymentJob.php | 140 +++++++++++++++----------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 72d8c0ad1..05fdf5c21 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -188,8 +188,8 @@ public function __construct(int $application_deployment_queue_id) $this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0; $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); - $this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/'); - $this->configuration_dir = application_configuration_dir()."/{$this->application->uuid}"; + $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); + $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); @@ -217,7 +217,7 @@ public function handle(): void $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - if (! $this->server->isFunctional()) { + if (!$this->server->isFunctional()) { $this->application_deployment_queue->addLogEntry('Server is not functional.'); $this->fail('Server is not functional.'); @@ -227,7 +227,7 @@ public function handle(): void // Generate custom host<->ip mapping $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); - if (! is_null($allContainers)) { + if (!is_null($allContainers)) { $allContainers = format_docker_command_output_to_json($allContainers); $ips = collect([]); if (count($allContainers) > 0) { @@ -397,14 +397,14 @@ private function deploy_docker_compose_buildpack() } if (data_get($this->application, 'docker_compose_custom_start_command')) { $this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command; - if (! str($this->docker_compose_custom_start_command)->contains('--project-directory')) { - $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); + if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) { + $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); } } if (data_get($this->application, 'docker_compose_custom_build_command')) { $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; - if (! str($this->docker_compose_custom_build_command)->contains('--project-directory')) { - $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); + if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) { + $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); } } if ($this->pull_request_id === 0) { @@ -425,7 +425,7 @@ private function deploy_docker_compose_buildpack() } else { $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $this->save_environment_variables(); - if (! is_null($this->env_filename)) { + if (!is_null($this->env_filename)) { $services = collect($composeFile['services']); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -536,7 +536,7 @@ private function deploy_dockerfile_buildpack() $this->check_git_if_build_needed(); $this->generate_image_names(); $this->clone_repository(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -560,7 +560,7 @@ private function deploy_nixpacks_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -585,7 +585,7 @@ private function deploy_static_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -664,7 +664,7 @@ private function push_to_docker_registry() return; } - ray('push_to_docker_registry noww: '.$this->production_image_name); + ray('push_to_docker_registry noww: ' . $this->production_image_name); try { instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); $this->application_deployment_queue->addLogEntry('----------------------------------------'); @@ -755,7 +755,7 @@ private function should_skip_build() return true; } - if (! $this->application->isConfigurationChanged()) { + if (!$this->application->isConfigurationChanged()) { $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->generate_compose_file(); $this->push_to_docker_registry(); @@ -811,7 +811,7 @@ private function save_environment_variables() $this->env_filename = ".env-pr-$this->pull_request_id"; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { + if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -833,12 +833,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\''.$real_value.'\''; + $real_value = '\'' . $real_value . '\''; } else { $real_value = escapeEnvVariables($env->real_value); } } - $envs->push($env->key.'='.$real_value); + $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { @@ -852,7 +852,7 @@ private function save_environment_variables() $this->env_filename = '.env'; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { + if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -874,13 +874,13 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\''.$real_value.'\''; + $real_value = '\'' . $real_value . '\''; } else { $real_value = escapeEnvVariables($env->real_value); ray($real_value); } } - $envs->push($env->key.'='.$real_value); + $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { @@ -1233,7 +1233,7 @@ private function deploy_to_additional_destinations() destination: $destination, no_questions_asked: true, ); - $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: ".route('project.application.deployment.show', [ + $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [ 'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'application_uuid' => data_get($this->application, 'uuid'), 'deployment_uuid' => $deployment_uuid, @@ -1295,7 +1295,7 @@ private function check_git_if_build_needed() ], ); } - if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { + if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->application_deployment_queue->commit = $this->commit; $this->application_deployment_queue->save(); @@ -1379,10 +1379,10 @@ private function generate_nixpacks_confs() if (count($aptPkgs) === 0) { data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); } else { - if (! in_array('curl', $aptPkgs)) { + if (!in_array('curl', $aptPkgs)) { $aptPkgs[] = 'curl'; } - if (! in_array('wget', $aptPkgs)) { + if (!in_array('wget', $aptPkgs)) { $aptPkgs[] = 'wget'; } data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); @@ -1417,13 +1417,13 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } @@ -1438,13 +1438,13 @@ private function generate_env_variables() $this->env_args->put('SOURCE_COMMIT', $this->commit); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } } else { foreach ($this->application->build_environment_variables_preview as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } @@ -1468,7 +1468,7 @@ private function generate_compose_file() $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = $labels->filter(function ($value, $key) { - return ! Str::startsWith($value, 'coolify.'); + return !Str::startsWith($value, 'coolify.'); }); $found_caddy_labels = $labels->filter(function ($value, $key) { return Str::startsWith($value, 'caddy_'); @@ -1559,7 +1559,7 @@ private function generate_compose_file() // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; // } // } - if (! is_null($this->env_filename)) { + if (!is_null($this->env_filename)) { $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; } $docker_compose['services'][$this->container_name]['healthcheck'] = [ @@ -1567,27 +1567,27 @@ private function generate_compose_file() 'CMD-SHELL', $this->generate_healthcheck_commands(), ], - 'interval' => $this->application->health_check_interval.'s', - 'timeout' => $this->application->health_check_timeout.'s', + 'interval' => $this->application->health_check_interval . 's', + 'timeout' => $this->application->health_check_timeout . 's', 'retries' => $this->application->health_check_retries, - 'start_period' => $this->application->health_check_start_period.'s', + 'start_period' => $this->application->health_check_start_period . 's', ]; - if (! is_null($this->application->limits_cpuset)) { - data_set($docker_compose, 'services.'.$this->container_name.'.cpuset', $this->application->limits_cpuset); + if (!is_null($this->application->limits_cpuset)) { + data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset); } if ($this->server->isSwarm()) { - data_forget($docker_compose, 'services.'.$this->container_name.'.container_name'); - data_forget($docker_compose, 'services.'.$this->container_name.'.expose'); - data_forget($docker_compose, 'services.'.$this->container_name.'.restart'); + data_forget($docker_compose, 'services.' . $this->container_name . '.container_name'); + data_forget($docker_compose, 'services.' . $this->container_name . '.expose'); + data_forget($docker_compose, 'services.' . $this->container_name . '.restart'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_limit'); - data_forget($docker_compose, 'services.'.$this->container_name.'.memswap_limit'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_swappiness'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_reservation'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpus'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpuset'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpu_shares'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpus'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); $docker_compose['services'][$this->container_name]['deploy'] = [ 'mode' => 'replicated', @@ -1653,7 +1653,7 @@ private function generate_compose_file() } } if ($this->application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.'.$this->container_name.'.healthcheck'); + data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); } if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; @@ -1731,9 +1731,9 @@ private function generate_local_persistent_volumes() $volume_name = $persistentStorage->name; } if ($this->pull_request_id !== 0) { - $volume_name = $volume_name.'-pr-'.$this->pull_request_id; + $volume_name = $volume_name . '-pr-' . $this->pull_request_id; } - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } return $local_persistent_volumes; @@ -1749,7 +1749,7 @@ private function generate_local_persistent_volumes_only_volume_names() $name = $persistentStorage->name; if ($this->pull_request_id !== 0) { - $name = $name.'-pr-'.$this->pull_request_id; + $name = $name . '-pr-' . $this->pull_request_id; } $local_persistent_volumes_names[$name] = [ @@ -1763,7 +1763,7 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_healthcheck_commands() { - if (! $this->application->health_check_port) { + if (!$this->application->health_check_port) { $health_check_port = $this->application->ports_exposes_array[0]; } else { $health_check_port = $this->application->health_check_port; @@ -1841,13 +1841,23 @@ private function build_image() $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); if ($this->force_rebuild) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); } + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + ] + ); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); } else { if ($this->force_rebuild) { @@ -1929,13 +1939,23 @@ private function build_image() $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); if ($this->force_rebuild) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); } + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + ] + ); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); } else { if ($this->force_rebuild) { @@ -1966,7 +1986,7 @@ private function stop_running_container(bool $force = false) $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); if ($this->pull_request_id === 0) { $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id; + return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id; }); } $containers->each(function ($container) { @@ -2098,8 +2118,8 @@ private function run_pre_deployment_command() foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container.'-'.$this->application->uuid)) { - $cmd = "sh -c '".str_replace("'", "'\''", $this->application->pre_deployment_command)."'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ @@ -2124,8 +2144,8 @@ private function run_post_deployment_command() $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container.'-'.$this->application->uuid)) { - $cmd = "sh -c '".str_replace("'", "'\''", $this->application->post_deployment_command)."'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; try { $this->execute_remote_command( @@ -2164,7 +2184,7 @@ private function next(string $status) return; } if ($status === ApplicationDeploymentStatus::FINISHED->value) { - if (! $this->only_this_server) { + if (!$this->only_this_server) { $this->deploy_to_additional_destinations(); } $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); From dffcee6cf1b261d68708d81ec25cd23eb2892896 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Thu, 13 Jun 2024 10:03:36 +0000 Subject: [PATCH 28/71] Fix styling --- app/Jobs/ApplicationDeploymentJob.php | 112 +++++++++++++------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 05fdf5c21..ddcf5c2ff 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -188,8 +188,8 @@ public function __construct(int $application_deployment_queue_id) $this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0; $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); - $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); - $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; + $this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/'); + $this->configuration_dir = application_configuration_dir()."/{$this->application->uuid}"; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); @@ -217,7 +217,7 @@ public function handle(): void $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - if (!$this->server->isFunctional()) { + if (! $this->server->isFunctional()) { $this->application_deployment_queue->addLogEntry('Server is not functional.'); $this->fail('Server is not functional.'); @@ -227,7 +227,7 @@ public function handle(): void // Generate custom host<->ip mapping $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); - if (!is_null($allContainers)) { + if (! is_null($allContainers)) { $allContainers = format_docker_command_output_to_json($allContainers); $ips = collect([]); if (count($allContainers) > 0) { @@ -397,14 +397,14 @@ private function deploy_docker_compose_buildpack() } if (data_get($this->application, 'docker_compose_custom_start_command')) { $this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command; - if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) { - $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); + if (! str($this->docker_compose_custom_start_command)->contains('--project-directory')) { + $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); } } if (data_get($this->application, 'docker_compose_custom_build_command')) { $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; - if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) { - $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); + if (! str($this->docker_compose_custom_build_command)->contains('--project-directory')) { + $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); } } if ($this->pull_request_id === 0) { @@ -425,7 +425,7 @@ private function deploy_docker_compose_buildpack() } else { $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $this->save_environment_variables(); - if (!is_null($this->env_filename)) { + if (! is_null($this->env_filename)) { $services = collect($composeFile['services']); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -536,7 +536,7 @@ private function deploy_dockerfile_buildpack() $this->check_git_if_build_needed(); $this->generate_image_names(); $this->clone_repository(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -560,7 +560,7 @@ private function deploy_nixpacks_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -585,7 +585,7 @@ private function deploy_static_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -664,7 +664,7 @@ private function push_to_docker_registry() return; } - ray('push_to_docker_registry noww: ' . $this->production_image_name); + ray('push_to_docker_registry noww: '.$this->production_image_name); try { instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); $this->application_deployment_queue->addLogEntry('----------------------------------------'); @@ -755,7 +755,7 @@ private function should_skip_build() return true; } - if (!$this->application->isConfigurationChanged()) { + if (! $this->application->isConfigurationChanged()) { $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->generate_compose_file(); $this->push_to_docker_registry(); @@ -811,7 +811,7 @@ private function save_environment_variables() $this->env_filename = ".env-pr-$this->pull_request_id"; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (!is_null($this->commit)) { + if (! is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -833,12 +833,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\'' . $real_value . '\''; + $real_value = '\''.$real_value.'\''; } else { $real_value = escapeEnvVariables($env->real_value); } } - $envs->push($env->key . '=' . $real_value); + $envs->push($env->key.'='.$real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { @@ -852,7 +852,7 @@ private function save_environment_variables() $this->env_filename = '.env'; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (!is_null($this->commit)) { + if (! is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -874,13 +874,13 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\'' . $real_value . '\''; + $real_value = '\''.$real_value.'\''; } else { $real_value = escapeEnvVariables($env->real_value); ray($real_value); } } - $envs->push($env->key . '=' . $real_value); + $envs->push($env->key.'='.$real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { @@ -1233,7 +1233,7 @@ private function deploy_to_additional_destinations() destination: $destination, no_questions_asked: true, ); - $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [ + $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: ".route('project.application.deployment.show', [ 'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'application_uuid' => data_get($this->application, 'uuid'), 'deployment_uuid' => $deployment_uuid, @@ -1295,7 +1295,7 @@ private function check_git_if_build_needed() ], ); } - if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { + if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->application_deployment_queue->commit = $this->commit; $this->application_deployment_queue->save(); @@ -1379,10 +1379,10 @@ private function generate_nixpacks_confs() if (count($aptPkgs) === 0) { data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); } else { - if (!in_array('curl', $aptPkgs)) { + if (! in_array('curl', $aptPkgs)) { $aptPkgs[] = 'curl'; } - if (!in_array('wget', $aptPkgs)) { + if (! in_array('wget', $aptPkgs)) { $aptPkgs[] = 'wget'; } data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); @@ -1417,13 +1417,13 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } @@ -1438,13 +1438,13 @@ private function generate_env_variables() $this->env_args->put('SOURCE_COMMIT', $this->commit); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } } else { foreach ($this->application->build_environment_variables_preview as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } @@ -1468,7 +1468,7 @@ private function generate_compose_file() $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = $labels->filter(function ($value, $key) { - return !Str::startsWith($value, 'coolify.'); + return ! Str::startsWith($value, 'coolify.'); }); $found_caddy_labels = $labels->filter(function ($value, $key) { return Str::startsWith($value, 'caddy_'); @@ -1559,7 +1559,7 @@ private function generate_compose_file() // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; // } // } - if (!is_null($this->env_filename)) { + if (! is_null($this->env_filename)) { $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; } $docker_compose['services'][$this->container_name]['healthcheck'] = [ @@ -1567,27 +1567,27 @@ private function generate_compose_file() 'CMD-SHELL', $this->generate_healthcheck_commands(), ], - 'interval' => $this->application->health_check_interval . 's', - 'timeout' => $this->application->health_check_timeout . 's', + 'interval' => $this->application->health_check_interval.'s', + 'timeout' => $this->application->health_check_timeout.'s', 'retries' => $this->application->health_check_retries, - 'start_period' => $this->application->health_check_start_period . 's', + 'start_period' => $this->application->health_check_start_period.'s', ]; - if (!is_null($this->application->limits_cpuset)) { - data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset); + if (! is_null($this->application->limits_cpuset)) { + data_set($docker_compose, 'services.'.$this->container_name.'.cpuset', $this->application->limits_cpuset); } if ($this->server->isSwarm()) { - data_forget($docker_compose, 'services.' . $this->container_name . '.container_name'); - data_forget($docker_compose, 'services.' . $this->container_name . '.expose'); - data_forget($docker_compose, 'services.' . $this->container_name . '.restart'); + data_forget($docker_compose, 'services.'.$this->container_name.'.container_name'); + data_forget($docker_compose, 'services.'.$this->container_name.'.expose'); + data_forget($docker_compose, 'services.'.$this->container_name.'.restart'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit'); - data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpus'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_limit'); + data_forget($docker_compose, 'services.'.$this->container_name.'.memswap_limit'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_swappiness'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_reservation'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpus'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpuset'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpu_shares'); $docker_compose['services'][$this->container_name]['deploy'] = [ 'mode' => 'replicated', @@ -1653,7 +1653,7 @@ private function generate_compose_file() } } if ($this->application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); + data_forget($docker_compose, 'services.'.$this->container_name.'.healthcheck'); } if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; @@ -1731,9 +1731,9 @@ private function generate_local_persistent_volumes() $volume_name = $persistentStorage->name; } if ($this->pull_request_id !== 0) { - $volume_name = $volume_name . '-pr-' . $this->pull_request_id; + $volume_name = $volume_name.'-pr-'.$this->pull_request_id; } - $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; } return $local_persistent_volumes; @@ -1749,7 +1749,7 @@ private function generate_local_persistent_volumes_only_volume_names() $name = $persistentStorage->name; if ($this->pull_request_id !== 0) { - $name = $name . '-pr-' . $this->pull_request_id; + $name = $name.'-pr-'.$this->pull_request_id; } $local_persistent_volumes_names[$name] = [ @@ -1763,7 +1763,7 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_healthcheck_commands() { - if (!$this->application->health_check_port) { + if (! $this->application->health_check_port) { $health_check_port = $this->application->ports_exposes_array[0]; } else { $health_check_port = $this->application->health_check_port; @@ -1986,7 +1986,7 @@ private function stop_running_container(bool $force = false) $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); if ($this->pull_request_id === 0) { $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id; + return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id; }); } $containers->each(function ($container) { @@ -2118,8 +2118,8 @@ private function run_pre_deployment_command() foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container.'-'.$this->application->uuid)) { + $cmd = "sh -c '".str_replace("'", "'\''", $this->application->pre_deployment_command)."'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ @@ -2144,8 +2144,8 @@ private function run_post_deployment_command() $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container.'-'.$this->application->uuid)) { + $cmd = "sh -c '".str_replace("'", "'\''", $this->application->post_deployment_command)."'"; $exec = "docker exec {$containerName} {$cmd}"; try { $this->execute_remote_command( @@ -2184,7 +2184,7 @@ private function next(string $status) return; } if ($status === ApplicationDeploymentStatus::FINISHED->value) { - if (!$this->only_this_server) { + if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); } $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); From c35f6e926d1f2c42732363115708c37bb5cf0fee Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 12:25:10 +0200 Subject: [PATCH 29/71] refactor: update text color for stderr output in deployment show view --- .../livewire/project/application/deployment/show.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index b65b463f0..32cc15e54 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -90,7 +90,7 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"> $line['hidden'], - 'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr', + 'text-coollabs font-bold whitespace-pre-line' => $line['type'] == 'stderr', ])>[{{ $line['timestamp'] }}] @if ($line['hidden'])

[COMMAND] {{ $line['command'] }}
[OUTPUT] @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://')) From 07ae971ae15386d39a56cc2c407287ba0ed44ca7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 12:29:57 +0200 Subject: [PATCH 30/71] feat: Add Tigris logo to other/logos directory --- README.md | 29 +++++++++++++++-------------- other/logos/tigris.svg | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 other/logos/tigris.svg diff --git a/README.md b/README.md index 129c07bf7..2f61468a1 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,23 @@ # Donations cccareers logo hetzner logo logto logo -bc direct logo -quantcdn logo -arcjet logo -supaguide logo +bc direct logo +quantcdn logo +arcjet logo +supaguide logo +tigris logo ## Github Sponsors ($40+) -SerpAPI -typebot - +SerpAPI +typebot + -Lightspeed.run - FlintCompany -American Cloud -CryptoJobsList -Thompson Edolo -UXWizz +Lightspeed.run + FlintCompany +American Cloud +CryptoJobsList +Thompson Edolo +UXWizz Younes Barrad Automaze Corentin Clichy @@ -109,7 +110,7 @@ # Recognitions

-Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt +Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt coollabsio%2Fcoolify | Trendshift diff --git a/other/logos/tigris.svg b/other/logos/tigris.svg new file mode 100644 index 000000000..367c59f2d --- /dev/null +++ b/other/logos/tigris.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + From e4e9de0a537b267c028aaf05a1785da689288320 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 12:51:55 +0200 Subject: [PATCH 31/71] refactor: Update text color for stderr output in deployment show view --- .../livewire/project/application/deployment/show.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index 32cc15e54..f97914ec2 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -89,8 +89,8 @@ class="fixed top-4 right-16" x-on:click="toggleScroll">count() > 0) @foreach (decode_remote_command_output($application_deployment_queue) as $line) $line['hidden'], - 'text-coollabs font-bold whitespace-pre-line' => $line['type'] == 'stderr', + 'text-coollabs dark:text-warning whitespace-pre-line' => $line['hidden'], + 'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr', ])>[{{ $line['timestamp'] }}] @if ($line['hidden'])

[COMMAND] {{ $line['command'] }}
[OUTPUT] @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://')) From 566faba6e349150c2769cc4c3f58f41c03d495dd Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 13:14:24 +0200 Subject: [PATCH 32/71] fix: handle laravel deployment better --- app/Jobs/ApplicationDeploymentJob.php | 142 +++++++++++++++----------- 1 file changed, 80 insertions(+), 62 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ddcf5c2ff..423487cb1 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -9,6 +9,7 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationPreview; +use App\Models\EnvironmentVariable; use App\Models\GithubApp; use App\Models\GitlabApp; use App\Models\Server; @@ -188,8 +189,8 @@ public function __construct(int $application_deployment_queue_id) $this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0; $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); - $this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/'); - $this->configuration_dir = application_configuration_dir()."/{$this->application->uuid}"; + $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); + $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); @@ -217,7 +218,7 @@ public function handle(): void $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - if (! $this->server->isFunctional()) { + if (!$this->server->isFunctional()) { $this->application_deployment_queue->addLogEntry('Server is not functional.'); $this->fail('Server is not functional.'); @@ -227,7 +228,7 @@ public function handle(): void // Generate custom host<->ip mapping $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); - if (! is_null($allContainers)) { + if (!is_null($allContainers)) { $allContainers = format_docker_command_output_to_json($allContainers); $ips = collect([]); if (count($allContainers) > 0) { @@ -397,14 +398,14 @@ private function deploy_docker_compose_buildpack() } if (data_get($this->application, 'docker_compose_custom_start_command')) { $this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command; - if (! str($this->docker_compose_custom_start_command)->contains('--project-directory')) { - $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); + if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) { + $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); } } if (data_get($this->application, 'docker_compose_custom_build_command')) { $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; - if (! str($this->docker_compose_custom_build_command)->contains('--project-directory')) { - $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); + if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) { + $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); } } if ($this->pull_request_id === 0) { @@ -425,7 +426,7 @@ private function deploy_docker_compose_buildpack() } else { $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $this->save_environment_variables(); - if (! is_null($this->env_filename)) { + if (!is_null($this->env_filename)) { $services = collect($composeFile['services']); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -536,7 +537,7 @@ private function deploy_dockerfile_buildpack() $this->check_git_if_build_needed(); $this->generate_image_names(); $this->clone_repository(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -560,7 +561,7 @@ private function deploy_nixpacks_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -585,7 +586,7 @@ private function deploy_static_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (! $this->force_rebuild) { + if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -664,7 +665,7 @@ private function push_to_docker_registry() return; } - ray('push_to_docker_registry noww: '.$this->production_image_name); + ray('push_to_docker_registry noww: ' . $this->production_image_name); try { instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); $this->application_deployment_queue->addLogEntry('----------------------------------------'); @@ -755,7 +756,7 @@ private function should_skip_build() return true; } - if (! $this->application->isConfigurationChanged()) { + if (!$this->application->isConfigurationChanged()) { $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->generate_compose_file(); $this->push_to_docker_registry(); @@ -811,7 +812,7 @@ private function save_environment_variables() $this->env_filename = ".env-pr-$this->pull_request_id"; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { + if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -833,12 +834,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\''.$real_value.'\''; + $real_value = '\'' . $real_value . '\''; } else { $real_value = escapeEnvVariables($env->real_value); } } - $envs->push($env->key.'='.$real_value); + $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { @@ -852,7 +853,7 @@ private function save_environment_variables() $this->env_filename = '.env'; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (! is_null($this->commit)) { + if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -874,13 +875,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\''.$real_value.'\''; + $real_value = '\'' . $real_value . '\''; } else { $real_value = escapeEnvVariables($env->real_value); - ray($real_value); } } - $envs->push($env->key.'='.$real_value); + $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { @@ -946,9 +946,8 @@ private function save_environment_variables() } } - private function framework_based_notification() + private function laravel_finetunes() { - // Laravel old env variables if ($this->pull_request_id === 0) { $nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); $nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); @@ -956,9 +955,21 @@ private function framework_based_notification() $nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); } - if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) { - $this->application_deployment_queue->addLogEntry('There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel', 'stderr'); + if (!$nixpacks_php_fallback_path) { + $nixpacks_php_fallback_path = new EnvironmentVariable(); + $nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH'; + $nixpacks_php_fallback_path->value = '/index.php'; + $nixpacks_php_fallback_path->application_id = $this->application->id; + $nixpacks_php_fallback_path->save(); } + if (!$nixpacks_php_root_dir) { + $nixpacks_php_root_dir = new EnvironmentVariable(); + $nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR'; + $nixpacks_php_root_dir->value = '/app/public'; + $nixpacks_php_root_dir->application_id = $this->application->id; + $nixpacks_php_root_dir->save(); + } + return [$nixpacks_php_fallback_path, $nixpacks_php_root_dir]; } private function rolling_update() @@ -1005,7 +1016,6 @@ private function rolling_update() $this->application_deployment_queue->addLogEntry('Rolling update completed.'); } } - $this->framework_based_notification(); } private function health_check() @@ -1233,7 +1243,7 @@ private function deploy_to_additional_destinations() destination: $destination, no_questions_asked: true, ); - $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: ".route('project.application.deployment.show', [ + $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [ 'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'application_uuid' => data_get($this->application, 'uuid'), 'deployment_uuid' => $deployment_uuid, @@ -1295,7 +1305,7 @@ private function check_git_if_build_needed() ], ); } - if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { + if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->application_deployment_queue->commit = $this->commit; $this->application_deployment_queue->save(); @@ -1366,12 +1376,14 @@ private function generate_nixpacks_confs() throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers'); } } + if ($this->saved_outputs->get('nixpacks_plan')) { $this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan'); if ($this->nixpacks_plan) { $this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}."); $this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}"); $parsed = Toml::Parse($this->nixpacks_plan); + // Do any modifications here $this->generate_env_variables(); $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); @@ -1379,15 +1391,21 @@ private function generate_nixpacks_confs() if (count($aptPkgs) === 0) { data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); } else { - if (! in_array('curl', $aptPkgs)) { + if (!in_array('curl', $aptPkgs)) { $aptPkgs[] = 'curl'; } - if (! in_array('wget', $aptPkgs)) { + if (!in_array('wget', $aptPkgs)) { $aptPkgs[] = 'wget'; } data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); } data_set($parsed, 'variables', $merged_envs->toArray()); + $is_laravel = data_get($parsed, 'variables.IS_LARAVEL', false); + if ($is_laravel) { + $variables = $this->laravel_finetunes(); + data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value); + data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value); + } $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); } @@ -1417,13 +1435,13 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } @@ -1438,13 +1456,13 @@ private function generate_env_variables() $this->env_args->put('SOURCE_COMMIT', $this->commit); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } } else { foreach ($this->application->build_environment_variables_preview as $env) { - if (! is_null($env->real_value)) { + if (!is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } @@ -1468,7 +1486,7 @@ private function generate_compose_file() $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = $labels->filter(function ($value, $key) { - return ! Str::startsWith($value, 'coolify.'); + return !Str::startsWith($value, 'coolify.'); }); $found_caddy_labels = $labels->filter(function ($value, $key) { return Str::startsWith($value, 'caddy_'); @@ -1559,7 +1577,7 @@ private function generate_compose_file() // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; // } // } - if (! is_null($this->env_filename)) { + if (!is_null($this->env_filename)) { $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; } $docker_compose['services'][$this->container_name]['healthcheck'] = [ @@ -1567,27 +1585,27 @@ private function generate_compose_file() 'CMD-SHELL', $this->generate_healthcheck_commands(), ], - 'interval' => $this->application->health_check_interval.'s', - 'timeout' => $this->application->health_check_timeout.'s', + 'interval' => $this->application->health_check_interval . 's', + 'timeout' => $this->application->health_check_timeout . 's', 'retries' => $this->application->health_check_retries, - 'start_period' => $this->application->health_check_start_period.'s', + 'start_period' => $this->application->health_check_start_period . 's', ]; - if (! is_null($this->application->limits_cpuset)) { - data_set($docker_compose, 'services.'.$this->container_name.'.cpuset', $this->application->limits_cpuset); + if (!is_null($this->application->limits_cpuset)) { + data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset); } if ($this->server->isSwarm()) { - data_forget($docker_compose, 'services.'.$this->container_name.'.container_name'); - data_forget($docker_compose, 'services.'.$this->container_name.'.expose'); - data_forget($docker_compose, 'services.'.$this->container_name.'.restart'); + data_forget($docker_compose, 'services.' . $this->container_name . '.container_name'); + data_forget($docker_compose, 'services.' . $this->container_name . '.expose'); + data_forget($docker_compose, 'services.' . $this->container_name . '.restart'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_limit'); - data_forget($docker_compose, 'services.'.$this->container_name.'.memswap_limit'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_swappiness'); - data_forget($docker_compose, 'services.'.$this->container_name.'.mem_reservation'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpus'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpuset'); - data_forget($docker_compose, 'services.'.$this->container_name.'.cpu_shares'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpus'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); $docker_compose['services'][$this->container_name]['deploy'] = [ 'mode' => 'replicated', @@ -1653,7 +1671,7 @@ private function generate_compose_file() } } if ($this->application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.'.$this->container_name.'.healthcheck'); + data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); } if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; @@ -1731,9 +1749,9 @@ private function generate_local_persistent_volumes() $volume_name = $persistentStorage->name; } if ($this->pull_request_id !== 0) { - $volume_name = $volume_name.'-pr-'.$this->pull_request_id; + $volume_name = $volume_name . '-pr-' . $this->pull_request_id; } - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } return $local_persistent_volumes; @@ -1749,7 +1767,7 @@ private function generate_local_persistent_volumes_only_volume_names() $name = $persistentStorage->name; if ($this->pull_request_id !== 0) { - $name = $name.'-pr-'.$this->pull_request_id; + $name = $name . '-pr-' . $this->pull_request_id; } $local_persistent_volumes_names[$name] = [ @@ -1763,7 +1781,7 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_healthcheck_commands() { - if (! $this->application->health_check_port) { + if (!$this->application->health_check_port) { $health_check_port = $this->application->ports_exposes_array[0]; } else { $health_check_port = $this->application->health_check_port; @@ -1986,7 +2004,7 @@ private function stop_running_container(bool $force = false) $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); if ($this->pull_request_id === 0) { $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id; + return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id; }); } $containers->each(function ($container) { @@ -2118,8 +2136,8 @@ private function run_pre_deployment_command() foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container.'-'.$this->application->uuid)) { - $cmd = "sh -c '".str_replace("'", "'\''", $this->application->pre_deployment_command)."'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ @@ -2144,8 +2162,8 @@ private function run_post_deployment_command() $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container.'-'.$this->application->uuid)) { - $cmd = "sh -c '".str_replace("'", "'\''", $this->application->post_deployment_command)."'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; try { $this->execute_remote_command( @@ -2184,7 +2202,7 @@ private function next(string $status) return; } if ($status === ApplicationDeploymentStatus::FINISHED->value) { - if (! $this->only_this_server) { + if (!$this->only_this_server) { $this->deploy_to_additional_destinations(); } $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); From a521d8549a227da5a6adfe7c013eaa4dc2c78658 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Thu, 13 Jun 2024 11:15:09 +0000 Subject: [PATCH 33/71] Fix styling --- app/Jobs/ApplicationDeploymentJob.php | 117 +++++++++++++------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 423487cb1..449734c7a 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -189,8 +189,8 @@ public function __construct(int $application_deployment_queue_id) $this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0; $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); - $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); - $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; + $this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/'); + $this->configuration_dir = application_configuration_dir()."/{$this->application->uuid}"; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); @@ -218,7 +218,7 @@ public function handle(): void $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - if (!$this->server->isFunctional()) { + if (! $this->server->isFunctional()) { $this->application_deployment_queue->addLogEntry('Server is not functional.'); $this->fail('Server is not functional.'); @@ -228,7 +228,7 @@ public function handle(): void // Generate custom host<->ip mapping $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); - if (!is_null($allContainers)) { + if (! is_null($allContainers)) { $allContainers = format_docker_command_output_to_json($allContainers); $ips = collect([]); if (count($allContainers) > 0) { @@ -398,14 +398,14 @@ private function deploy_docker_compose_buildpack() } if (data_get($this->application, 'docker_compose_custom_start_command')) { $this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command; - if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) { - $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); + if (! str($this->docker_compose_custom_start_command)->contains('--project-directory')) { + $this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); } } if (data_get($this->application, 'docker_compose_custom_build_command')) { $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; - if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) { - $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value(); + if (! str($this->docker_compose_custom_build_command)->contains('--project-directory')) { + $this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory '.$this->workdir)->value(); } } if ($this->pull_request_id === 0) { @@ -426,7 +426,7 @@ private function deploy_docker_compose_buildpack() } else { $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $this->save_environment_variables(); - if (!is_null($this->env_filename)) { + if (! is_null($this->env_filename)) { $services = collect($composeFile['services']); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -537,7 +537,7 @@ private function deploy_dockerfile_buildpack() $this->check_git_if_build_needed(); $this->generate_image_names(); $this->clone_repository(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -561,7 +561,7 @@ private function deploy_nixpacks_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -586,7 +586,7 @@ private function deploy_static_buildpack() $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->generate_image_names(); - if (!$this->force_rebuild) { + if (! $this->force_rebuild) { $this->check_image_locally_or_remotely(); if ($this->should_skip_build()) { return; @@ -665,7 +665,7 @@ private function push_to_docker_registry() return; } - ray('push_to_docker_registry noww: ' . $this->production_image_name); + ray('push_to_docker_registry noww: '.$this->production_image_name); try { instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); $this->application_deployment_queue->addLogEntry('----------------------------------------'); @@ -756,7 +756,7 @@ private function should_skip_build() return true; } - if (!$this->application->isConfigurationChanged()) { + if (! $this->application->isConfigurationChanged()) { $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); $this->generate_compose_file(); $this->push_to_docker_registry(); @@ -812,7 +812,7 @@ private function save_environment_variables() $this->env_filename = ".env-pr-$this->pull_request_id"; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (!is_null($this->commit)) { + if (! is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -834,12 +834,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\'' . $real_value . '\''; + $real_value = '\''.$real_value.'\''; } else { $real_value = escapeEnvVariables($env->real_value); } } - $envs->push($env->key . '=' . $real_value); + $envs->push($env->key.'='.$real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { @@ -853,7 +853,7 @@ private function save_environment_variables() $this->env_filename = '.env'; // Add SOURCE_COMMIT if not exists if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { - if (!is_null($this->commit)) { + if (! is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { $envs->push('SOURCE_COMMIT=unknown'); @@ -875,12 +875,12 @@ private function save_environment_variables() $real_value = $env->real_value; } else { if ($env->is_literal || $env->is_multiline) { - $real_value = '\'' . $real_value . '\''; + $real_value = '\''.$real_value.'\''; } else { $real_value = escapeEnvVariables($env->real_value); } } - $envs->push($env->key . '=' . $real_value); + $envs->push($env->key.'='.$real_value); } // Add PORT if not exists, use the first port as default if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { @@ -955,20 +955,21 @@ private function laravel_finetunes() $nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); } - if (!$nixpacks_php_fallback_path) { + if (! $nixpacks_php_fallback_path) { $nixpacks_php_fallback_path = new EnvironmentVariable(); $nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH'; $nixpacks_php_fallback_path->value = '/index.php'; $nixpacks_php_fallback_path->application_id = $this->application->id; $nixpacks_php_fallback_path->save(); } - if (!$nixpacks_php_root_dir) { + if (! $nixpacks_php_root_dir) { $nixpacks_php_root_dir = new EnvironmentVariable(); $nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR'; $nixpacks_php_root_dir->value = '/app/public'; $nixpacks_php_root_dir->application_id = $this->application->id; $nixpacks_php_root_dir->save(); } + return [$nixpacks_php_fallback_path, $nixpacks_php_root_dir]; } @@ -1243,7 +1244,7 @@ private function deploy_to_additional_destinations() destination: $destination, no_questions_asked: true, ); - $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [ + $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: ".route('project.application.deployment.show', [ 'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'application_uuid' => data_get($this->application, 'uuid'), 'deployment_uuid' => $deployment_uuid, @@ -1305,7 +1306,7 @@ private function check_git_if_build_needed() ], ); } - if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { + if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->application_deployment_queue->commit = $this->commit; $this->application_deployment_queue->save(); @@ -1391,10 +1392,10 @@ private function generate_nixpacks_confs() if (count($aptPkgs) === 0) { data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); } else { - if (!in_array('curl', $aptPkgs)) { + if (! in_array('curl', $aptPkgs)) { $aptPkgs[] = 'curl'; } - if (!in_array('wget', $aptPkgs)) { + if (! in_array('wget', $aptPkgs)) { $aptPkgs[] = 'wget'; } data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); @@ -1435,13 +1436,13 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } @@ -1456,13 +1457,13 @@ private function generate_env_variables() $this->env_args->put('SOURCE_COMMIT', $this->commit); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } } else { foreach ($this->application->build_environment_variables_preview as $env) { - if (!is_null($env->real_value)) { + if (! is_null($env->real_value)) { $this->env_args->put($env->key, $env->real_value); } } @@ -1486,7 +1487,7 @@ private function generate_compose_file() $this->application->parseContainerLabels(); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = $labels->filter(function ($value, $key) { - return !Str::startsWith($value, 'coolify.'); + return ! Str::startsWith($value, 'coolify.'); }); $found_caddy_labels = $labels->filter(function ($value, $key) { return Str::startsWith($value, 'caddy_'); @@ -1577,7 +1578,7 @@ private function generate_compose_file() // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; // } // } - if (!is_null($this->env_filename)) { + if (! is_null($this->env_filename)) { $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; } $docker_compose['services'][$this->container_name]['healthcheck'] = [ @@ -1585,27 +1586,27 @@ private function generate_compose_file() 'CMD-SHELL', $this->generate_healthcheck_commands(), ], - 'interval' => $this->application->health_check_interval . 's', - 'timeout' => $this->application->health_check_timeout . 's', + 'interval' => $this->application->health_check_interval.'s', + 'timeout' => $this->application->health_check_timeout.'s', 'retries' => $this->application->health_check_retries, - 'start_period' => $this->application->health_check_start_period . 's', + 'start_period' => $this->application->health_check_start_period.'s', ]; - if (!is_null($this->application->limits_cpuset)) { - data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset); + if (! is_null($this->application->limits_cpuset)) { + data_set($docker_compose, 'services.'.$this->container_name.'.cpuset', $this->application->limits_cpuset); } if ($this->server->isSwarm()) { - data_forget($docker_compose, 'services.' . $this->container_name . '.container_name'); - data_forget($docker_compose, 'services.' . $this->container_name . '.expose'); - data_forget($docker_compose, 'services.' . $this->container_name . '.restart'); + data_forget($docker_compose, 'services.'.$this->container_name.'.container_name'); + data_forget($docker_compose, 'services.'.$this->container_name.'.expose'); + data_forget($docker_compose, 'services.'.$this->container_name.'.restart'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit'); - data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness'); - data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpus'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset'); - data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_limit'); + data_forget($docker_compose, 'services.'.$this->container_name.'.memswap_limit'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_swappiness'); + data_forget($docker_compose, 'services.'.$this->container_name.'.mem_reservation'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpus'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpuset'); + data_forget($docker_compose, 'services.'.$this->container_name.'.cpu_shares'); $docker_compose['services'][$this->container_name]['deploy'] = [ 'mode' => 'replicated', @@ -1671,7 +1672,7 @@ private function generate_compose_file() } } if ($this->application->isHealthcheckDisabled()) { - data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); + data_forget($docker_compose, 'services.'.$this->container_name.'.healthcheck'); } if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; @@ -1749,9 +1750,9 @@ private function generate_local_persistent_volumes() $volume_name = $persistentStorage->name; } if ($this->pull_request_id !== 0) { - $volume_name = $volume_name . '-pr-' . $this->pull_request_id; + $volume_name = $volume_name.'-pr-'.$this->pull_request_id; } - $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; } return $local_persistent_volumes; @@ -1767,7 +1768,7 @@ private function generate_local_persistent_volumes_only_volume_names() $name = $persistentStorage->name; if ($this->pull_request_id !== 0) { - $name = $name . '-pr-' . $this->pull_request_id; + $name = $name.'-pr-'.$this->pull_request_id; } $local_persistent_volumes_names[$name] = [ @@ -1781,7 +1782,7 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_healthcheck_commands() { - if (!$this->application->health_check_port) { + if (! $this->application->health_check_port) { $health_check_port = $this->application->ports_exposes_array[0]; } else { $health_check_port = $this->application->health_check_port; @@ -2004,7 +2005,7 @@ private function stop_running_container(bool $force = false) $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); if ($this->pull_request_id === 0) { $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id; + return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id; }); } $containers->each(function ($container) { @@ -2136,8 +2137,8 @@ private function run_pre_deployment_command() foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container.'-'.$this->application->uuid)) { + $cmd = "sh -c '".str_replace("'", "'\''", $this->application->pre_deployment_command)."'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ @@ -2162,8 +2163,8 @@ private function run_post_deployment_command() $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); - if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container.'-'.$this->application->uuid)) { + $cmd = "sh -c '".str_replace("'", "'\''", $this->application->post_deployment_command)."'"; $exec = "docker exec {$containerName} {$cmd}"; try { $this->execute_remote_command( @@ -2202,7 +2203,7 @@ private function next(string $status) return; } if ($status === ApplicationDeploymentStatus::FINISHED->value) { - if (!$this->only_this_server) { + if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); } $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); From e4ee149085169b4e0991c4f6d6a3947f53156db3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 13:37:19 +0200 Subject: [PATCH 34/71] refactor: Remove debug code for saving environment variables --- app/Livewire/Project/Shared/EnvironmentVariable/All.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 4c06bfe23..d67dae19e 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -112,7 +112,6 @@ public function saveVariables($isPreview) $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete(); } else { $variables = parseEnvFormatToArray($this->variables); - ray($variables, $this->variables); $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); } foreach ($variables as $key => $variable) { From 30ac392af48b30ed3a9fdf034328078064ffa87d Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 13 Jun 2024 13:56:19 +0200 Subject: [PATCH 35/71] Update lang/de.json Co-authored-by: Marco <59606979+marcomaiermm@users.noreply.github.com> --- lang/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/de.json b/lang/de.json index 8c9b41de1..f2050283f 100644 --- a/lang/de.json +++ b/lang/de.json @@ -26,5 +26,5 @@ "input.code": "Einmalcode", "input.recovery_code": "Wiederherstellungscode", "button.save": "Speichern", - "repository.url": "Beispiele
Für öffentliche Reposetories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." + "repository.url": "Beispiele
Für öffentliche Repositories benutze https://....
Für private Repositories benutze git@....

https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt.
https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt.
https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt." } From a47816f45df635fcc81bedc731dbcf920b9480d5 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 13 Jun 2024 13:56:27 +0200 Subject: [PATCH 36/71] Update lang/de.json Co-authored-by: Marco <59606979+marcomaiermm@users.noreply.github.com> --- lang/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/de.json b/lang/de.json index f2050283f..29fec629f 100644 --- a/lang/de.json +++ b/lang/de.json @@ -18,7 +18,7 @@ "auth.failed.callback": "Fehlerhafte Verarbeitung der Antwort des Anmeldeanbieters.", "auth.failed.password": "Das angegebene Passwort ist inkorrekt.", "auth.failed.email": "Wir können keinen Benutzer mit dieser E-Mail Adresse finden.", - "auth.throttle": "Zu viele anmeldeversuche. Bitte versuchen es in :seconds Sekunden erneut.", + "auth.throttle": "Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.", "input.name": "Name", "input.email": "E-Mail", "input.password": "Passwort", From bfe2fb6da8965bd2080c154ed30742cd0981f4fe Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jun 2024 14:48:23 +0200 Subject: [PATCH 37/71] refactor: Update Docker build commands for better performance and flexibility --- app/Jobs/ApplicationDeploymentJob.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 423487cb1..395f58e19 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1389,6 +1389,7 @@ private function generate_nixpacks_confs() $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); $aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []); if (count($aptPkgs) === 0) { + $aptPkgs = ['curl', 'wget']; data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); } else { if (!in_array('curl', $aptPkgs)) { @@ -1861,12 +1862,14 @@ private function build_image() $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); + $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ @@ -1959,12 +1962,13 @@ private function build_image() $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); + $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ @@ -2222,10 +2226,14 @@ public function failed(Throwable $exception): void ray($code); if ($code !== 69420) { // 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one - $this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr'); - $this->execute_remote_command( - ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true] - ); + if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) { + // do not remove already running container + } else { + $this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr'); + $this->execute_remote_command( + ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true] + ); + } } } } From 49d91c498eef858149c92317ce0ba66d7d9c7f02 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 14 Jun 2024 09:50:16 +0200 Subject: [PATCH 38/71] refactor: Update image sizes and add new logos to README.md --- README.md | 9 +++++---- other/logos/fractal.png | Bin 0 -> 5643 bytes 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 other/logos/fractal.png diff --git a/README.md b/README.md index 2f61468a1..d22ec90d1 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,14 @@ # Donations Special thanks to our biggest sponsors! cccareers logo -hetzner logo -logto logo +hetzner logo +logto logo bc direct logo -quantcdn logo +quantcdn logo arcjet logo supaguide logo -tigris logo +tigris logo +fractal logo ## Github Sponsors ($40+) SerpAPI diff --git a/other/logos/fractal.png b/other/logos/fractal.png new file mode 100644 index 0000000000000000000000000000000000000000..c4d39c1f1dbcb8861479cf169ceed9e796c6e351 GIT binary patch literal 5643 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 literal 0 HcmV?d00001 From b17be37aee22ae807da978d77cd98dafa4e40738 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 14 Jun 2024 14:09:56 +0200 Subject: [PATCH 39/71] fix: db proxy status shown better in the UI --- app/Actions/Database/StopDatabaseProxy.php | 2 ++ app/Livewire/Project/Database/Heading.php | 1 - .../Project/Database/Postgresql/General.php | 27 ++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 984225435..1b262c898 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -2,6 +2,7 @@ namespace App\Actions\Database; +use App\Events\DatabaseStatusChanged; use App\Models\ServiceDatabase; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; @@ -28,5 +29,6 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St instant_remote_process(["docker rm -f {$uuid}-proxy"], $server); $database->is_public = false; $database->save(); + DatabaseStatusChanged::dispatch(); } } diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index 61dafa76f..ae88ac12b 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -12,7 +12,6 @@ use App\Actions\Database\StartRedis; use App\Actions\Database\StopDatabase; use App\Actions\Docker\GetContainersStatus; -use App\Jobs\ContainerStatusJob; use Livewire\Component; class Heading extends Component diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 38cac2e5c..a950e5ef9 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -25,7 +25,17 @@ class General extends Component public ?string $db_url_public = null; - protected $listeners = ['refresh', 'save_init_script', 'delete_init_script']; + public function getListeners() + { + $userId = auth()->user()->id; + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped', + "refresh", + "save_init_script", + "delete_init_script", + ]; + } protected $rules = [ 'database.name' => 'required', @@ -68,11 +78,14 @@ public function mount() } $this->server = data_get($this->database, 'destination.server'); } - + public function database_stopped() + { + $this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.'); + } public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -89,14 +102,14 @@ public function instantSaveAdvanced() public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -112,7 +125,7 @@ public function instantSave() } $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } @@ -157,7 +170,7 @@ public function save_new_init_script() return; } - if (! isset($this->database->init_scripts)) { + if (!isset($this->database->init_scripts)) { $this->database->init_scripts = []; } $this->database->init_scripts = array_merge($this->database->init_scripts, [ From f10f3456d7bfc6f6d4a3ed7b0d8cc3eeaa9a4a97 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Fri, 14 Jun 2024 12:10:40 +0000 Subject: [PATCH 40/71] Fix styling --- .../Project/Database/Postgresql/General.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index a950e5ef9..1c5d39055 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -31,9 +31,9 @@ public function getListeners() return [ "echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped', - "refresh", - "save_init_script", - "delete_init_script", + 'refresh', + 'save_init_script', + 'delete_init_script', ]; } @@ -78,14 +78,16 @@ public function mount() } $this->server = data_get($this->database, 'destination.server'); } + public function database_stopped() { $this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.'); } + public function instantSaveAdvanced() { try { - if (!$this->server->isLogDrainEnabled()) { + if (! $this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -102,14 +104,14 @@ public function instantSaveAdvanced() public function instantSave() { try { - if ($this->database->is_public && !$this->database->public_port) { + if ($this->database->is_public && ! $this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (!str($this->database->status)->startsWith('running')) { + if (! str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -125,7 +127,7 @@ public function instantSave() } $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = !$this->database->is_public; + $this->database->is_public = ! $this->database->is_public; return handleError($e, $this); } @@ -170,7 +172,7 @@ public function save_new_init_script() return; } - if (!isset($this->database->init_scripts)) { + if (! isset($this->database->init_scripts)) { $this->database->init_scripts = []; } $this->database->init_scripts = array_merge($this->database->init_scripts, [ From 1ecd0307edc96e7d04c2c4cc44d2e551470c563b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 14 Jun 2024 14:23:40 +0200 Subject: [PATCH 41/71] feat: COOLIFY_CONTAINER_NAME predefined variable --- app/Jobs/ApplicationDeploymentJob.php | 6 ++++++ app/Models/Service.php | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index fb577b895..8f89da2d2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -828,6 +828,9 @@ private function save_environment_variables() if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { $envs->push("COOLIFY_BRANCH={$local_branch}"); } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + } foreach ($sorted_environment_variables_preview as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { @@ -869,6 +872,9 @@ private function save_environment_variables() if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { $envs->push("COOLIFY_BRANCH={$local_branch}"); } + if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + } foreach ($sorted_environment_variables as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { diff --git a/app/Models/Service.php b/app/Models/Service.php index 7851eb58a..6e8ba25a8 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; +use Symfony\Component\Yaml\Yaml; class Service extends BaseModel { @@ -837,6 +838,13 @@ public function saveComposeConfigs() $commands[] = "mkdir -p $workdir"; $commands[] = "cd $workdir"; + $json = Yaml::parse($this->docker_compose); + foreach($json['services'] as $service => $config) { + $envs = collect($config['environment']); + $envs->push("COOLIFY_CONTAINER_NAME=$service-{$this->uuid}"); + data_set($json, "services.$service.environment", $envs->toArray()); + } + $this->docker_compose = Yaml::dump($json); $docker_compose_base64 = base64_encode($this->docker_compose); $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; $envs = $this->environment_variables()->get(); From 2b6fc16637f488048412f402327a764c886d9911 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Fri, 14 Jun 2024 12:24:28 +0000 Subject: [PATCH 42/71] Fix styling --- app/Models/Service.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Service.php b/app/Models/Service.php index 6e8ba25a8..8adca3424 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -839,7 +839,7 @@ public function saveComposeConfigs() $commands[] = "cd $workdir"; $json = Yaml::parse($this->docker_compose); - foreach($json['services'] as $service => $config) { + foreach ($json['services'] as $service => $config) { $envs = collect($config['environment']); $envs->push("COOLIFY_CONTAINER_NAME=$service-{$this->uuid}"); data_set($json, "services.$service.environment", $envs->toArray()); From f6f44d8e8f5c36649af3327f09d617fe862797da Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 14 Jun 2024 14:48:12 +0200 Subject: [PATCH 43/71] fix: show commit message on webhooks + prs --- .../application/deployment/index.blade.php | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 4282700f8..241530fe0 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -46,7 +46,7 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> {{ $deployment->status }} @if (data_get($deployment, 'is_webhook') || data_get($deployment, 'pull_request_id')) -
+
@if (data_get($deployment, 'is_webhook')) Webhook @endif @@ -55,12 +55,19 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | @endif Pull Request #{{ data_get($deployment, 'pull_request_id') }} - (SHA - @if (data_get($deployment, 'commit')) - {{ data_get($deployment, 'commit') }}) - @else - HEAD) - @endif + @endif + @if (data_get($deployment, 'commit')) +
+
+ @if ($deployment->commitMessage()) + ({{ data_get_str($deployment, 'commit')->limit(7) }} - + {{ $deployment->commitMessage() }}) + @else + {{ data_get_str($deployment, 'commit')->limit(7) }} + @endif +
+
@endif
@else @@ -98,7 +105,7 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> Finished 0s in 0s @else - Running for 0s + Running for 0s @endif
From af38d0cc078d76ee83e5ecca9ae69a723ca2dd3c Mon Sep 17 00:00:00 2001 From: Alexzvn Date: Sat, 15 Jun 2024 00:01:58 +0000 Subject: [PATCH 44/71] fix: application custom labels reset after saving --- app/Livewire/Project/Application/General.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 60cdee48e..06ff7b1de 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -347,7 +347,9 @@ public function set_redirect() public function submit($showToaster = true) { try { - $this->set_redirect(); + if ($this->application->isDirty('redirect')) { + $this->set_redirect(); + } $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { From 1ae6106782ec0451ca15ac3659c77927fd4beca3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 17 Jun 2024 10:20:02 +0200 Subject: [PATCH 45/71] refactor: Update README.md with new logos and fix styling --- README.md | 4 +++- other/logos/advin.png | Bin 0 -> 33880 bytes other/logos/codext.jpg | Bin 0 -> 105902 bytes other/logos/fractal.svg | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 other/logos/advin.png create mode 100644 other/logos/codext.jpg create mode 100644 other/logos/fractal.svg diff --git a/README.md b/README.md index d22ec90d1..34c40acf5 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ # Donations arcjet logo supaguide logo tigris logo -fractal logo +fractal logo +advin logo ## Github Sponsors ($40+) SerpAPI @@ -52,6 +53,7 @@ ## Github Sponsors ($40+) FlintCompany American Cloud CryptoJobsList +Codext Thompson Edolo UXWizz Younes Barrad diff --git a/other/logos/advin.png b/other/logos/advin.png new file mode 100644 index 0000000000000000000000000000000000000000..155408b9cb1914a35c86e99d8b3a85b06def62d6 GIT binary patch literal 33880 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 z z(ma%Hl` z$p#GhwU88BNU}UOhMZOWMsa{CtylSyo<(=EwHviY$*bQLYubbuj7U!|MOEW0yV=P9 zvSkZ%UhB~Ota*7g`CQZXi*Eguor_kAV>=ENBDj{SoRGimTbUIw;vCLyNl?Va-cP5n z_Y#!`Jr&1jd@uuN<=Pyy-bQbGe7D@y3@t)PW!)AODBb(^(BS*oOfOgs)DKx`h)>(L z%&bx$!0tS9IltG_6T}*7N*C z`55LJ4G3)D`GV`jgXmh|^7+AQ?{^AW^@n;qgeiOOn2pJv{43qpy1?Ctj&$SS7U}+K zY;T92LQ6uIc((g=Ks;Y*)F`L#RNk4#7&ms?-ZeWV1}*Mv9TvkJ2&Fh zM0m;*NuL(u5EL_-xp%>!zYlA$B?HE?9&dY0s(wa5gF%wNei*O9Ua+!%3aXp181O)# z%=_=X644V)X!7r=x)K{t4C5NtL;Woc4W*z*9DAi63!a7WCioLRv?AK^-J?RhBlWk! zVGLVl@8@}C7Gd~jJVfoT*ur4A4?jGU&URAPu(prsQEz|Nee)6Eao~

*p?!IAt}F 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

    b z2;`(;aY<#Zi3Y=Yy|29nKA=B&l`V$UWbmmsI7cWrY+9I%vUxEHicOe zt=_ulG8AAhMvn}j3V#RB1+M}pzSMiJD_3e{cNZ03n$EG} zuN-@8Ka44&O($l_;um>RNlPY6W!#-lOSbdGIKm45t)%5s843b`N2*T;@@H_SJJ?X@ z2YJF~vAe{w!6~qLem*VmN$)(D7nv#UXDih1bC6m+Y$j+UCs9#n68C$-q??{^&I5Mp zoNSR&)Nm#^w48j+t!%M;u6~=czN{Xqdo&JQ?q|Nk^rxR7$DZ!p8Ji1h{ItLpOItWBoncm8#U^}ZVDS3H+`SM8p1Y=LS(w;W}q0Bz%QE6rZo6Sp&v?r>Wt=Kqx&L}6 z4_%Dxa+wT=j(sTSGLcH7(n)Yo92)mG4NG0kapC>Dojfqp)?{yvX?1`^6V*(=5{z7C zYTEX2se4zz@)Of`)i^kpBRfd4;flYvqH--O2Q%%pYq4+3A}Kpt?U+`>;h3Jem}pde zRMKC$awq46-VDFovTDjPiFbwzYvZ~wU%%m&6fX8O$(~Tt@rP|oiTgqB7@U#4#5MY_ z$U@9{=QI@~Q1!!HvQ1GhmVw2!sRUMOT`yKh&gjLJHLa!!2D9+5ubcL;yD}f_J~pQl=4e}(RDOe8#BW+=1W*0OCc0P#dY$4nQ%b% z`e<>a#F}iTmg{+j`e`vhF>p1)pPKhO1h%{AosJp(lV_@)&9DEqlPf4`glYZnGivL z0GSFvJ(*oxFuFa{`*JmYdt7|vS8L!XEZLw(00S1lgl9N>cur=fdu(dY$F9iGROq?< zDKWXd^gkTD@S1gAYtOT54~kNO?b;JzQy%@kYSB1y8}psvnBHpM<{L^){>OO%GJ+hh z^QY%^-Yu0I2}^v4&Kqg*OXf&#sf@pF2Rcax?B&is0ilMrBU#q36;rqzV7~23-!Xt# z+hAl62Cz#!cx&ambm1#RFbnTr&#~3FzB1XP|<81IG{0!VXO=vKhxx|A9PQY$dE&ZM5H! zHT`uXGLU?f0icT-{Bqazk~_E+=rLpRnK7^}>9zY)Qa>HU9qv}m=*Rd2FwLp40yI8^ zDn4;`l^t@I^2_56guzJhjn?E9cmhM$ChU@TRrde2IvQ}ZqJH-EbU}KFSjbo?hLz8n3H`J5;VZLH(?HlSCRvq2Jxo{?gHFS-L z{p+&r!jC=8=~TJP6PUI>CsgvkVT&=VD8HdG#fo6s*6Ca&xfjEdz3>0MwUrzB4=Udo hNAN$lJpX^cJyT>c$v7H>8~kVb>MA;rDkaM|{|i`_jBEe^ literal 0 HcmV?d00001 diff --git a/other/logos/codext.jpg b/other/logos/codext.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8abf63972ba8b385ce500c65b3ab8ce8d75006c3 GIT binary patch literal 105902 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∨ zubbMKi%yRgc^quU6Rn8OaPP|FD=g5nvYn8PXeNNg=%Obi-(xQME|ISTZdJ*?4rj9x z|0S(LBWp~DlYNX6;b8W-lYJ4FG+)q}O4r?EAoNe@*|M6=U0e7D@;hvu?gDx*T46bcDnD&!O+HSXpz|q^*SY1Lc+>AL@D_`lqguxq@m&5uU`-242c(d zT$Vw8j1*vqKhY9_(r(5YvbWrAk0ON|zyI(q6{?Xfj?+!jNl6*(n@bijNH9(Ixf_i0 zeLRXf{qDogbLZj&*3e+Si=5mgHchTo)K7h87G~~9nwy_LwcUHTDlE9Gty+4znvrc? zLI)q1&gMXRf;b~uRToew6U0Jk0v#ydD!3*b={R=&GCBMnC_6{!ln}IRYhL^0<72{f zw{UlHIh6@u2L_`16Rwz1MyIApdKA2QV~W*@L5jQi6b!%M7@MG%1Z2WlC5yep&(vrE zS(dn$PZN;sHGR7ZHHpihXAz`6hRwKNw~tg?4uT-_6k9r0ICZg78HUk@I%3RcSWX~# zqQaWR@Gd(7+)pGV#=8!j=)QJW8LT-{^EzynZQO(*$Cf= z)Ipl~P5IKB6{#=3r4#EBXFiB zm8h)=NySI;Ne#Q+IP^*MCCg}ZqgS{a2CS~NYE57h8$SWRqpT>`KG;9gpr4(Xhu=-$myQLV8Y zdU;a9w2)_oH=LK2L9!QM+Dz|N%D_>5A0ykGLG6$*(s z8ErXpN#B57;9$3KWj2>sBF$ztd0yxtW0f|ifVf1X6C}UjY5BK zNDCj7#kUr6<``R1hRby50vBh&fouj2rdsKg;0H;{{8}0o6h-R(@oj*|eO2{$X4J$2 zW$SDfCSd!B_iB3smq&xXR8=o90xbw%lS`}B16ciAR#LmVkqIPiYw zN~lpf3zMN=M-MYX9$x5?8v=xZ|6QW${)0qssVf?YUE98hGwA>T5d{>Z0NDFi^qJL9 zcHj12sU0Zm`)7u9Gnr*I@jdxi-nE8-wb7LiU${2AGKlbpmRRhqx7$zcC<0xOU5zB* zyno*_65i25-6QOfUFkCNckRHtQ$=po1swSkny>E2Ss3}YJrenA?x2CT;e=39jm6m~ zMSxBL@G5?jLiH+>Vb*{*Y?Rq^UEo;~imcVfF)-KvCK<$Yy>jMVCyE-wgDh zawd>>Cb1|)SzO$$_kI$_F)2>9U5Y`@i_6GQSd{H;==@MlER-h`91NpsWP?IM;t6A_H*hCOJ?6nhs}kjD3+PdaekNOfz`?s^U9k;O>G1>A*idwr^qUlui9 zy6!1uW1Y@z89CDDd~&1}(hqLi=WIim_8N6LO{Jrdk3G*E+04Vxu#!0DKPCjWw;Y$F zcH3A-pQp8)qUrBET}G{X5wHjJ)x!=>9*} zM6)JHAKJz|ANojBfH+6t@WFNS7@)WWlAc$Hu0PrKCoj|#X(U1xkfs5VP|V7r%qQB& zy|%yk2bCsoz=7y8=RX#yVQq)$jCj)#f`P43b?HWbNyEBGS#@2`cI2-nYIZTTSjC3< zoxOIifCyKnoNVigam4m!j<)|oAmcF0E z!=4}oINNuKY|L_mTgks;dr?)%Xy-c<`-YJ8vxCoAElC+-Plk%AnU50Q_3#JZ+ZIaA zx?*l5?ub#`eSN%Xh+|Q$^-~8s2Ed%KnaiL+Ap^PXjF)3h5p6dSQv$&~1!+iHs1;Z; ze+1Vrrk9<5KtvxIiO5Cj%0D@IAwODJ26>}H(6j8l!BbtfuDny)W| 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

    ?m@sXt>NkUObDp^xeku6)OELq1I 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 ZHuVxeA*{U%1__szosX>SpwFL{{|_0K(gXki literal 0 HcmV?d00001 diff --git a/other/logos/fractal.svg b/other/logos/fractal.svg new file mode 100644 index 000000000..cd2ee4134 --- /dev/null +++ b/other/logos/fractal.svg @@ -0,0 +1,40 @@ + + + + + + Networks + Fractal + + + + + + + \ No newline at end of file From 1d0a1ab16a83038285b324195262b35a24d44bcc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 17 Jun 2024 14:21:27 +0200 Subject: [PATCH 46/71] feat: charts --- app/Actions/Server/StartSentinel.php | 2 +- app/Jobs/PullSentinelImageJob.php | 7 +- app/Livewire/Charts/Server.php | 33 ++++++++ app/Models/Server.php | 1 - app/View/Components/ApexCharts.php | 30 +++++++ .../views/components/apex-charts.blade.php | 78 +++++++++++++++++++ resources/views/layouts/base.blade.php | 1 + .../views/livewire/charts/server.blade.php | 4 + resources/views/livewire/dashboard.blade.php | 7 +- .../views/livewire/server/show.blade.php | 5 ++ 10 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 app/Livewire/Charts/Server.php create mode 100644 app/View/Components/ApexCharts.php create mode 100644 resources/views/components/apex-charts.blade.php create mode 100644 resources/views/livewire/charts/server.blade.php diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php index eea429c79..342e72b1b 100644 --- a/app/Actions/Server/StartSentinel.php +++ b/app/Actions/Server/StartSentinel.php @@ -15,7 +15,7 @@ public function handle(Server $server, $version = 'latest', bool $restart = fals instant_remote_process(['docker rm -f coolify-sentinel'], $server, false); } instant_remote_process([ - "docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", + "docker run --rm --pull always -d -e \"SCHEDULER=true\" -e \"METRICS_HISTORY=10\" -e \"REFRESH_RATE=5\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", 'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', 'chmod -R 700 /data/coolify/metrics /data/coolify/logs', ], $server, false); diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index 1dd4b1dd3..c9a27e5f1 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -36,7 +36,10 @@ public function handle(): void { try { $version = get_latest_sentinel_version(); - if (! $version) { + if (isDev()) { + $version = "0.0.5"; + } + if (!$version) { ray('Failed to get latest Sentinel version'); return; @@ -52,7 +55,7 @@ public function handle(): void } ray('Sentinel image is up to date'); } catch (\Throwable $e) { - send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); + send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage()); ray($e->getMessage()); throw $e; } diff --git a/app/Livewire/Charts/Server.php b/app/Livewire/Charts/Server.php new file mode 100644 index 000000000..8f8b5cf0e --- /dev/null +++ b/app/Livewire/Charts/Server.php @@ -0,0 +1,33 @@ +loadData(); + } + public function loadData() + { + $metrics = $this->server->getMetrics(); + $metrics = collect($metrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $this->dispatch("refreshChartData-{$this->chartId}", [ + 'seriesData' => $metrics, + ]); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index b1419dc0e..915fc60ce 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -487,7 +487,6 @@ public function getMetrics() $parsedCollection = collect($cpu)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { [$time, $value] = explode(',', trim($line)); - return [(int) $time, (float) $value]; }); })->toArray(); diff --git a/app/View/Components/ApexCharts.php b/app/View/Components/ApexCharts.php new file mode 100644 index 000000000..3f80a41a5 --- /dev/null +++ b/app/View/Components/ApexCharts.php @@ -0,0 +1,30 @@ +chartId = $chartId; + $this->seriesData = $seriesData; + $this->categories = $categories; + $this->seriesName = $seriesName ?? 'Series'; + } + + /** + * Get the view / contents that represent the component. + */ + public function render(): View|Closure|string + { + return view('components.apex-charts'); + } +} diff --git a/resources/views/components/apex-charts.blade.php b/resources/views/components/apex-charts.blade.php new file mode 100644 index 000000000..029e16df7 --- /dev/null +++ b/resources/views/components/apex-charts.blade.php @@ -0,0 +1,78 @@ +

    + + diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index c8d3a33cc..c1851e2f9 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -38,6 +38,7 @@ + @endauth @section('body') diff --git a/resources/views/livewire/charts/server.blade.php b/resources/views/livewire/charts/server.blade.php new file mode 100644 index 000000000..0047a6c12 --- /dev/null +++ b/resources/views/livewire/charts/server.blade.php @@ -0,0 +1,4 @@ +
    +

    CPU Usage

    + +
    diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index 37f010397..d5e645a10 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -39,9 +39,9 @@ Add Resource - Settings - + href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}"> + Settings + @@ -161,7 +161,6 @@ @endif - diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php new file mode 100644 index 000000000..a39679fd8 --- /dev/null +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -0,0 +1,127 @@ +
    +

    CPU

    + + + + + + + + + +
    + + + +
    diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php new file mode 100644 index 000000000..a112744ec --- /dev/null +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -0,0 +1,128 @@ +
    +

    Memory

    + + + + + + + + + +
    + + + +
    diff --git a/resources/views/livewire/charts/server.blade.php b/resources/views/livewire/charts/server.blade.php deleted file mode 100644 index 0047a6c12..000000000 --- a/resources/views/livewire/charts/server.blade.php +++ /dev/null @@ -1,4 +0,0 @@ -
    -

    CPU Usage

    - -
    diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index 6efe08c52..9f061ba54 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -144,6 +144,26 @@ class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-1 +
    +

    Metrics

    + @if ($server->isMetricsEnabled()) + Restart Collector + @endif +
    +
    + +
    +
    +
    + + + +
    +
    @endif diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index 2a2d582f8..d3a3bb8c6 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -5,9 +5,26 @@ - @if (isDev()) + @if ($server->isFunctional() && $server->isMetricsEnabled())
    - + + +
    @endif From ce15f8f1ddca08c583046141c4a27638a9932814 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Tue, 18 Jun 2024 14:43:18 +0000 Subject: [PATCH 55/71] Fix styling --- app/Jobs/PullSentinelImageJob.php | 5 ++- app/Jobs/ServerStatusJob.php | 6 +-- app/Livewire/Charts/ServerCpu.php | 1 + app/Livewire/Charts/ServerMemory.php | 6 ++- app/Livewire/Server/Form.php | 4 +- app/Models/Server.php | 38 ++++++++++--------- bootstrap/helpers/shared.php | 5 ++- .../2024_06_18_105948_move_server_metrics.php | 1 - 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index 20fe19e99..1dd4b1dd3 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -36,7 +36,7 @@ public function handle(): void { try { $version = get_latest_sentinel_version(); - if (!$version) { + if (! $version) { ray('Failed to get latest Sentinel version'); return; @@ -47,11 +47,12 @@ public function handle(): void } if (version_compare($local_version, $version, '<')) { StartSentinel::run($this->server, $version, true); + return; } ray('Sentinel image is up to date'); } catch (\Throwable $e) { - send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage()); + send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); ray($e->getMessage()); throw $e; } diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index ce8864304..544f0ef3b 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -41,7 +41,7 @@ public function uniqueId(): int public function handle() { - if (!$this->server->isServerReady($this->tries)) { + if (! $this->server->isServerReady($this->tries)) { throw new \RuntimeException('Server is not ready.'); } try { @@ -53,7 +53,7 @@ public function handle() } } } catch (\Throwable $e) { - send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage()); + send_internal_notification('ServerStatusJob failed with: '.$e->getMessage()); ray($e->getMessage()); return handleError($e); @@ -103,7 +103,7 @@ private function remove_unnecessary_coolify_yaml() { // This will remote the coolify.yaml file from the server as it is not needed on cloud servers if (isCloud() && $this->server->id !== 0) { - $file = $this->server->proxyPath() . '/dynamic/coolify.yaml'; + $file = $this->server->proxyPath().'/dynamic/coolify.yaml'; return instant_remote_process([ "rm -f $file", diff --git a/app/Livewire/Charts/ServerCpu.php b/app/Livewire/Charts/ServerCpu.php index fda2c5227..454f59193 100644 --- a/app/Livewire/Charts/ServerCpu.php +++ b/app/Livewire/Charts/ServerCpu.php @@ -41,6 +41,7 @@ public function loadData() return handleError($e, $this); } } + public function setInterval() { $this->loadData(); diff --git a/app/Livewire/Charts/ServerMemory.php b/app/Livewire/Charts/ServerMemory.php index 366c87838..f3f992e97 100644 --- a/app/Livewire/Charts/ServerMemory.php +++ b/app/Livewire/Charts/ServerMemory.php @@ -14,12 +14,14 @@ class ServerMemory extends Component public $data; public $categories; + public $interval = 5; public function render() { return view('livewire.charts.server-memory'); } + public function mount() { $this->loadData(); @@ -39,7 +41,9 @@ public function loadData() return handleError($e, $this); } } - public function setInterval() { + + public function setInterval() + { $this->loadData(); } } diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 7622a7b96..87c0d09d1 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -104,7 +104,9 @@ public function instantSave() return handleError($e, $this); } } - public function restartSentinel() { + + public function restartSentinel() + { try { $version = get_latest_sentinel_version(); StartSentinel::run($this->server, $version, true); diff --git a/app/Models/Server.php b/app/Models/Server.php index 4337ce09e..744b383b5 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -131,7 +131,7 @@ public function addInitialNetwork() public function setupDefault404Redirect() { - $dynamic_conf_path = $this->proxyPath() . '/dynamic'; + $dynamic_conf_path = $this->proxyPath().'/dynamic'; $proxy_type = $this->proxyType(); $redirect_url = $this->proxy->redirect_url; if ($proxy_type === 'TRAEFIK_V2') { @@ -145,8 +145,8 @@ public function setupDefault404Redirect() respond 404 }'; $conf = - "# This file is automatically generated by Coolify.\n" . - "# Do not edit it manually (only if you know what are you doing).\n\n" . + "# This file is automatically generated by Coolify.\n". + "# Do not edit it manually (only if you know what are you doing).\n\n". $conf; $base64 = base64_encode($conf); instant_remote_process([ @@ -205,8 +205,8 @@ public function setupDefault404Redirect() ]; $conf = Yaml::dump($dynamic_conf, 12, 2); $conf = - "# This file is automatically generated by Coolify.\n" . - "# Do not edit it manually (only if you know what are you doing).\n\n" . + "# This file is automatically generated by Coolify.\n". + "# Do not edit it manually (only if you know what are you doing).\n\n". $conf; $base64 = base64_encode($conf); @@ -215,8 +215,8 @@ public function setupDefault404Redirect() redir $redirect_url }"; $conf = - "# This file is automatically generated by Coolify.\n" . - "# Do not edit it manually (only if you know what are you doing).\n\n" . + "# This file is automatically generated by Coolify.\n". + "# Do not edit it manually (only if you know what are you doing).\n\n". $conf; $base64 = base64_encode($conf); } @@ -237,7 +237,7 @@ public function setupDefault404Redirect() public function setupDynamicProxyConfiguration() { $settings = InstanceSettings::get(); - $dynamic_config_path = $this->proxyPath() . '/dynamic'; + $dynamic_config_path = $this->proxyPath().'/dynamic'; if ($this->proxyType() === 'TRAEFIK_V2') { $file = "$dynamic_config_path/coolify.yaml"; if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) { @@ -330,8 +330,8 @@ public function setupDynamicProxyConfiguration() } $yaml = Yaml::dump($traefik_dynamic_conf, 12, 2); $yaml = - "# This file is automatically generated by Coolify.\n" . - "# Do not edit it manually (only if you know what are you doing).\n\n" . + "# This file is automatically generated by Coolify.\n". + "# Do not edit it manually (only if you know what are you doing).\n\n". $yaml; $base64 = base64_encode($yaml); @@ -389,9 +389,9 @@ public function proxyPath() if ($proxyType === ProxyTypes::TRAEFIK_V2->value) { $proxy_path = $proxy_path; } elseif ($proxyType === ProxyTypes::CADDY->value) { - $proxy_path = $proxy_path . '/caddy'; + $proxy_path = $proxy_path.'/caddy'; } elseif ($proxyType === ProxyTypes::NGINX->value) { - $proxy_path = $proxy_path . '/nginx'; + $proxy_path = $proxy_path.'/nginx'; } return $proxy_path; @@ -466,6 +466,7 @@ public function isMetricsEnabled() { return $this->settings->is_metrics_enabled; } + public function checkSentinel() { ray("Checking sentinel on server: {$this->name}"); @@ -507,6 +508,7 @@ public function getCpuMetrics(int $mins = 5) return $parsedCollection; } } + public function getMemoryMetrics(int $mins = 5) { if ($this->isMetricsEnabled()) { @@ -646,7 +648,7 @@ public function getContainers(): Collection $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false); } else { $containers = instant_remote_process(['docker container ls -q'], $this, false); - if (!$containers) { + if (! $containers) { return collect([]); } $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false); @@ -666,7 +668,7 @@ public function loadUnmanagedContainers(): Collection $containers = format_docker_command_output_to_json($containers); $containers = $containers->map(function ($container) { $labels = data_get($container, 'Labels'); - if (!str($labels)->contains('coolify.managed')) { + if (! str($labels)->contains('coolify.managed')) { return $container; } @@ -738,7 +740,7 @@ public function dockerComposeBasedPreviewDeployments() return $this->previews()->filter(function ($preview) { $applicationId = data_get($preview, 'application_id'); $application = Application::find($applicationId); - if (!$application) { + if (! $application) { return false; } @@ -822,9 +824,9 @@ public function isProxyShouldRun() public function isFunctional() { - $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && !$this->settings->force_disabled; + $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && ! $this->settings->force_disabled; ['private_key_filename' => $private_key_filename, 'mux_filename' => $mux_filename] = server_ssh_configuration($this); - if (!$isFunctional) { + if (! $isFunctional) { Storage::disk('ssh-keys')->delete($private_key_filename); Storage::disk('ssh-mux')->delete($mux_filename); } @@ -883,7 +885,7 @@ public function validateConnection() config()->set('coolify.mux_enabled', false); $server = Server::find($this->id); - if (!$server) { + if (! $server) { return ['uptime' => false, 'error' => 'Server not found.']; } if ($server->skipServer()) { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 47ed4b9a4..3129fef90 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -163,6 +163,7 @@ function get_latest_sentinel_version(): string try { $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); $versions = $response->json(); + return data_get($versions, 'coolify.sentinel.version'); } catch (\Throwable $e) { //throw $e; @@ -2285,7 +2286,9 @@ function isAnyDeploymentInprogress() exit(0); } -function generateSentinelToken() { +function generateSentinelToken() +{ $token = Str::random(64); + return $token; } diff --git a/database/migrations/2024_06_18_105948_move_server_metrics.php b/database/migrations/2024_06_18_105948_move_server_metrics.php index c8cbb3f6f..26a1d1684 100644 --- a/database/migrations/2024_06_18_105948_move_server_metrics.php +++ b/database/migrations/2024_06_18_105948_move_server_metrics.php @@ -1,6 +1,5 @@ Date: Tue, 18 Jun 2024 16:52:05 +0200 Subject: [PATCH 56/71] chore: Update sentinel version to 0.0.8 --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index b5923d7b0..b983658d4 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "4.0.0-beta.298" }, "sentinel": { - "version": "0.0.4" + "version": "0.0.8" } } } From a43de75b42af4654714012ac8b10e6ea5998ecc8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 08:58:57 +0200 Subject: [PATCH 57/71] fix: metrics parsing --- app/Models/Server.php | 46 ++++++++++++++++--- .../livewire/charts/server-cpu.blade.php | 7 ++- .../livewire/charts/server-memory.blade.php | 8 ++-- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 744b383b5..1dedf8045 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -5,8 +5,6 @@ use App\Actions\Server\InstallDocker; use App\Enums\ProxyTypes; use App\Jobs\PullSentinelImageJob; -use App\Notifications\Server\Revived; -use App\Notifications\Server\Unreachable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Collection; @@ -503,9 +501,27 @@ public function getCpuMetrics(int $mins = 5) return [(int) $time, (float) $value]; }); - })->toArray(); + }); + if ($mins === 30 || $mins === 60) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 5 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } + if ($mins === 720) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 10 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } + if ($mins === 10080) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 20 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } - return $parsedCollection; + return $parsedCollection->toArray(); } } @@ -529,9 +545,27 @@ public function getMemoryMetrics(int $mins = 5) return [(int) $time, (float) $usedPercent]; }); - })->toArray(); + }); + if ($mins === 30 || $mins === 60) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 5 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } + if ($mins === 720) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 10 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } + if ($mins === 10080) { + $parsedCollection = $parsedCollection->filter(function ($item, $key) { + return $key % 20 === 0; + }); + $parsedCollection = $parsedCollection->values(); + } - return $parsedCollection; + return $parsedCollection->toArray(); } } diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index a39679fd8..ff85f94f1 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -14,14 +14,13 @@ From d1a5f97f5940c1949587c03c8a0bc7e4fa2b38b2 Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Wed, 19 Jun 2024 06:59:46 +0000 Subject: [PATCH 58/71] Fix styling --- app/Data/ServerMetadata.php | 3 +-- app/Events/ProxyStarted.php | 5 +---- app/Exceptions/ProcessException.php | 4 +--- app/Jobs/ApplicationPullRequestUpdateJob.php | 3 +-- app/Jobs/CheckLogDrainContainerJob.php | 4 +--- app/Jobs/CheckResaleLicenseJob.php | 4 +--- app/Jobs/CleanupHelperContainersJob.php | 4 +--- app/Jobs/CleanupInstanceStuffsJob.php | 5 +---- app/Jobs/ContainerStatusJob.php | 4 +--- app/Jobs/CoolifyTask.php | 3 +-- app/Jobs/DatabaseBackupStatusJob.php | 4 +--- app/Jobs/DeleteResourceJob.php | 4 +--- app/Jobs/DockerCleanupJob.php | 4 +--- app/Jobs/GithubAppPermissionJob.php | 4 +--- app/Jobs/InstanceAutoUpdateJob.php | 4 +--- app/Jobs/PullCoolifyImageJob.php | 4 +--- app/Jobs/PullHelperImageJob.php | 4 +--- app/Jobs/PullSentinelImageJob.php | 4 +--- app/Jobs/PullTemplatesFromCDN.php | 4 +--- app/Jobs/PullVersionsFromCDN.php | 4 +--- app/Jobs/SendConfirmationForWaitlistJob.php | 4 +--- app/Jobs/SendMessageToDiscordJob.php | 3 +-- app/Jobs/SendMessageToTelegramJob.php | 3 +-- app/Jobs/ServerFilesFromServerJob.php | 4 +--- app/Jobs/ServerLimitCheckJob.php | 4 +--- app/Jobs/ServerStatusJob.php | 4 +--- app/Jobs/ServerStorageSaveJob.php | 4 +--- app/Jobs/SubscriptionInvoiceFailedJob.php | 4 +--- app/Jobs/SubscriptionTrialEndedJob.php | 3 +-- app/Jobs/SubscriptionTrialEndsSoonJob.php | 3 +-- app/Listeners/MaintenanceModeDisabledNotification.php | 4 +--- app/Listeners/ProxyStartedNotification.php | 4 +--- app/Models/Kubernetes.php | 4 +--- app/Notifications/Container/ContainerRestarted.php | 4 +--- app/Notifications/Container/ContainerStopped.php | 4 +--- app/Notifications/Database/DailyBackup.php | 4 +--- app/Notifications/Internal/GeneralNotification.php | 4 +--- app/Notifications/Server/DockerCleanup.php | 4 +--- app/Notifications/Server/ForceDisabled.php | 4 +--- app/Notifications/Server/ForceEnabled.php | 4 +--- app/Notifications/Server/HighDiskUsage.php | 4 +--- app/Notifications/Server/Unreachable.php | 5 +---- app/Notifications/Test.php | 4 +--- app/Notifications/TransactionalEmails/InvitationLink.php | 4 +--- app/Notifications/TransactionalEmails/Test.php | 4 +--- app/Providers/AppServiceProvider.php | 4 +--- app/View/Components/Forms/Input.php | 3 +-- app/View/Components/ResourceView.php | 4 +--- app/View/Components/Status/Index.php | 3 +-- database/seeders/EnvironmentSeeder.php | 4 +--- 50 files changed, 50 insertions(+), 144 deletions(-) diff --git a/app/Data/ServerMetadata.php b/app/Data/ServerMetadata.php index b96efa622..d95944b15 100644 --- a/app/Data/ServerMetadata.php +++ b/app/Data/ServerMetadata.php @@ -11,6 +11,5 @@ class ServerMetadata extends Data public function __construct( public ?ProxyTypes $type, public ?ProxyStatus $status - ) { - } + ) {} } diff --git a/app/Events/ProxyStarted.php b/app/Events/ProxyStarted.php index ed62eccb1..64d562e0a 100644 --- a/app/Events/ProxyStarted.php +++ b/app/Events/ProxyStarted.php @@ -10,8 +10,5 @@ class ProxyStarted { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public $data) - { - - } + public function __construct(public $data) {} } diff --git a/app/Exceptions/ProcessException.php b/app/Exceptions/ProcessException.php index 728a0d81b..47eaa6fd8 100644 --- a/app/Exceptions/ProcessException.php +++ b/app/Exceptions/ProcessException.php @@ -4,6 +4,4 @@ use Exception; -class ProcessException extends Exception -{ -} +class ProcessException extends Exception {} diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index d400642dd..6120d1cba 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -25,8 +25,7 @@ public function __construct( public ApplicationPreview $preview, public ProcessStatus $status, public ?string $deployment_uuid = null - ) { - } + ) {} public function handle() { diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php index 312200f66..16ef85192 100644 --- a/app/Jobs/CheckLogDrainContainerJob.php +++ b/app/Jobs/CheckLogDrainContainerJob.php @@ -19,9 +19,7 @@ class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php index 8f2039ef2..b55ae9967 100644 --- a/app/Jobs/CheckResaleLicenseJob.php +++ b/app/Jobs/CheckResaleLicenseJob.php @@ -14,9 +14,7 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php index 418c7a0f4..7b064a464 100644 --- a/app/Jobs/CleanupHelperContainersJob.php +++ b/app/Jobs/CleanupHelperContainersJob.php @@ -15,9 +15,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php index b846ad2bc..d9de3f6fe 100644 --- a/app/Jobs/CleanupInstanceStuffsJob.php +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -16,10 +16,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct() - { - - } + public function __construct() {} // public function uniqueId(): string // { diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index c50d17d4c..e919855d5 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -23,9 +23,7 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php index e5f4dfd5e..5418daa22 100755 --- a/app/Jobs/CoolifyTask.php +++ b/app/Jobs/CoolifyTask.php @@ -23,8 +23,7 @@ public function __construct( public bool $ignore_errors = false, public $call_event_on_finish = null, public $call_event_data = null - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/DatabaseBackupStatusJob.php b/app/Jobs/DatabaseBackupStatusJob.php index cf240e0d7..d3b0e99cf 100644 --- a/app/Jobs/DatabaseBackupStatusJob.php +++ b/app/Jobs/DatabaseBackupStatusJob.php @@ -18,9 +18,7 @@ class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue public $tries = 1; - public function __construct() - { - } + public function __construct() {} public function handle() { diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 6d4720f6b..8710fda88 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -28,9 +28,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) - { - } + public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {} public function handle() { diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index e3ac193dc..e637fb6d4 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public ?int $usageBefore = null; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/GithubAppPermissionJob.php b/app/Jobs/GithubAppPermissionJob.php index bab8f3a25..3188d35d6 100644 --- a/app/Jobs/GithubAppPermissionJob.php +++ b/app/Jobs/GithubAppPermissionJob.php @@ -23,9 +23,7 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public GithubApp $github_app) - { - } + public function __construct(public GithubApp $github_app) {} public function middleware(): array { diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php index bce60bbc8..1bbfcf8cb 100644 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ b/app/Jobs/InstanceAutoUpdateJob.php @@ -19,9 +19,7 @@ class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, Should public $tries = 1; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullCoolifyImageJob.php b/app/Jobs/PullCoolifyImageJob.php index ccaa785dc..2bcbfc4df 100644 --- a/app/Jobs/PullCoolifyImageJob.php +++ b/app/Jobs/PullCoolifyImageJob.php @@ -19,9 +19,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 1000; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index d3bda2ea1..30a1b8026 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -27,9 +27,7 @@ public function uniqueId(): string return $this->server->uuid; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index 1dd4b1dd3..30b36d99f 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -28,9 +28,7 @@ public function uniqueId(): string return $this->server->uuid; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php index 948060033..396ff29f4 100644 --- a/app/Jobs/PullTemplatesFromCDN.php +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -17,9 +17,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/PullVersionsFromCDN.php b/app/Jobs/PullVersionsFromCDN.php index 1ad4989de..79ebad7a8 100644 --- a/app/Jobs/PullVersionsFromCDN.php +++ b/app/Jobs/PullVersionsFromCDN.php @@ -17,9 +17,7 @@ class PullVersionsFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() - { - } + public function __construct() {} public function handle(): void { diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php index 4d5618df0..73e8658ee 100755 --- a/app/Jobs/SendConfirmationForWaitlistJob.php +++ b/app/Jobs/SendConfirmationForWaitlistJob.php @@ -14,9 +14,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public string $email, public string $uuid) - { - } + public function __construct(public string $email, public string $uuid) {} public function handle() { diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php index 90f2e0b30..f38cf823c 100644 --- a/app/Jobs/SendMessageToDiscordJob.php +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -31,8 +31,7 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public string $text, public string $webhookUrl - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index b81bbc50b..bf52b782f 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -33,8 +33,7 @@ public function __construct( public string $token, public string $chatId, public ?string $topicId = null, - ) { - } + ) {} /** * Execute the job. diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php index 2476c12dd..769dfc004 100644 --- a/app/Jobs/ServerFilesFromServerJob.php +++ b/app/Jobs/ServerFilesFromServerJob.php @@ -16,9 +16,7 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) - { - } + public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {} public function handle() { diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php index 3eaf88ba7..24292025b 100644 --- a/app/Jobs/ServerLimitCheckJob.php +++ b/app/Jobs/ServerLimitCheckJob.php @@ -24,9 +24,7 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public Team $team) - { - } + public function __construct(public Team $team) {} public function middleware(): array { diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index 544f0ef3b..938f3fe40 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -25,9 +25,7 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { diff --git a/app/Jobs/ServerStorageSaveJob.php b/app/Jobs/ServerStorageSaveJob.php index c94a3edc5..526cd5375 100644 --- a/app/Jobs/ServerStorageSaveJob.php +++ b/app/Jobs/ServerStorageSaveJob.php @@ -14,9 +14,7 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public LocalFileVolume $localFileVolume) - { - } + public function __construct(public LocalFileVolume $localFileVolume) {} public function handle() { diff --git a/app/Jobs/SubscriptionInvoiceFailedJob.php b/app/Jobs/SubscriptionInvoiceFailedJob.php index e4cd219c8..64a75671f 100755 --- a/app/Jobs/SubscriptionInvoiceFailedJob.php +++ b/app/Jobs/SubscriptionInvoiceFailedJob.php @@ -15,9 +15,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(protected Team $team) - { - } + public function __construct(protected Team $team) {} public function handle() { diff --git a/app/Jobs/SubscriptionTrialEndedJob.php b/app/Jobs/SubscriptionTrialEndedJob.php index ee260d8d9..dd2250dd7 100755 --- a/app/Jobs/SubscriptionTrialEndedJob.php +++ b/app/Jobs/SubscriptionTrialEndedJob.php @@ -17,8 +17,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Team $team - ) { - } + ) {} public function handle(): void { diff --git a/app/Jobs/SubscriptionTrialEndsSoonJob.php b/app/Jobs/SubscriptionTrialEndsSoonJob.php index fba668108..80e232a3e 100755 --- a/app/Jobs/SubscriptionTrialEndsSoonJob.php +++ b/app/Jobs/SubscriptionTrialEndsSoonJob.php @@ -17,8 +17,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public Team $team - ) { - } + ) {} public function handle(): void { diff --git a/app/Listeners/MaintenanceModeDisabledNotification.php b/app/Listeners/MaintenanceModeDisabledNotification.php index 9f676ca99..ded53ccee 100644 --- a/app/Listeners/MaintenanceModeDisabledNotification.php +++ b/app/Listeners/MaintenanceModeDisabledNotification.php @@ -9,9 +9,7 @@ class MaintenanceModeDisabledNotification { - public function __construct() - { - } + public function __construct() {} public function handle(EventsMaintenanceModeDisabled $event): void { diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index 64271cc52..d0541b162 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -9,9 +9,7 @@ class ProxyStartedNotification { public Server $server; - public function __construct() - { - } + public function __construct() {} public function handle(ProxyStarted $event): void { diff --git a/app/Models/Kubernetes.php b/app/Models/Kubernetes.php index 2ad7a2110..174cb5bc8 100644 --- a/app/Models/Kubernetes.php +++ b/app/Models/Kubernetes.php @@ -2,6 +2,4 @@ namespace App\Models; -class Kubernetes extends BaseModel -{ -} +class Kubernetes extends BaseModel {} diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index a55f16a83..86c1e6e69 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -14,9 +14,7 @@ class ContainerRestarted extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) - { - } + public function __construct(public string $name, public Server $server, public ?string $url = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index d9dc57b98..75b4872cb 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -14,9 +14,7 @@ class ContainerStopped extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $name, public Server $server, public ?string $url = null) - { - } + public function __construct(public string $name, public Server $server, public ?string $url = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php index c74676eb7..90abee8a6 100644 --- a/app/Notifications/Database/DailyBackup.php +++ b/app/Notifications/Database/DailyBackup.php @@ -16,9 +16,7 @@ class DailyBackup extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public $databases) - { - } + public function __construct(public $databases) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index 6acd770f6..1d4d648c8 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -14,9 +14,7 @@ class GeneralNotification extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public string $message) - { - } + public function __construct(public string $message) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index 0e445f035..f8195ec1d 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -15,9 +15,7 @@ class DockerCleanup extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public string $message) - { - } + public function __construct(public Server $server, public string $message) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index 960a7c79f..9a76558e2 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -17,9 +17,7 @@ class ForceDisabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 6a4b5d74b..a43e30376 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -17,9 +17,7 @@ class ForceEnabled extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 5f63ef8f1..a6e248170 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -17,9 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) - { - } + public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 2dcfe28b8..ebbd6af77 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -17,10 +17,7 @@ class Unreachable extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server) - { - - } + public function __construct(public Server $server) {} public function via(object $notifiable): array { diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 925859aba..f873a95d3 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -13,9 +13,7 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) - { - } + public function __construct(public ?string $emails = null) {} public function via(object $notifiable): array { diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index a251b47ea..49d2ad487 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -22,9 +22,7 @@ public function via(): array return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) - { - } + public function __construct(public User $user) {} public function toMail(): MailMessage { diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index ed30c1883..a417e1ee5 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -14,9 +14,7 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public string $emails) - { - } + public function __construct(public string $emails) {} public function via(): array { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1bce22c12..6822dec13 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,9 +9,7 @@ class AppServiceProvider extends ServiceProvider { - public function register(): void - { - } + public function register(): void {} public function boot(): void { diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index 36c07dae1..35448d5e5 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -22,8 +22,7 @@ public function __construct( public bool $allowToPeak = true, public bool $isMultiline = false, public string $defaultClass = 'input', - ) { - } + ) {} public function render(): View|Closure|string { diff --git a/app/View/Components/ResourceView.php b/app/View/Components/ResourceView.php index 5a11b159d..d1107465b 100644 --- a/app/View/Components/ResourceView.php +++ b/app/View/Components/ResourceView.php @@ -16,9 +16,7 @@ public function __construct( public ?string $logo = null, public ?string $documentation = null, public bool $upgrade = false, - ) { - - } + ) {} /** * Get the view / contents that represent the component. diff --git a/app/View/Components/Status/Index.php b/app/View/Components/Status/Index.php index f8436a102..ada9eb682 100644 --- a/app/View/Components/Status/Index.php +++ b/app/View/Components/Status/Index.php @@ -14,8 +14,7 @@ class Index extends Component public function __construct( public $resource = null, public bool $showRefreshButton = true, - ) { - } + ) {} /** * Get the view / contents that represent the component. diff --git a/database/seeders/EnvironmentSeeder.php b/database/seeders/EnvironmentSeeder.php index 0e980f22b..1c6d562a9 100644 --- a/database/seeders/EnvironmentSeeder.php +++ b/database/seeders/EnvironmentSeeder.php @@ -9,7 +9,5 @@ class EnvironmentSeeder extends Seeder /** * Run the database seeds. */ - public function run(): void - { - } + public function run(): void {} } From 36f251e7102b27d93e9c0d8ee3917a3acfb26e0e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 09:30:56 +0200 Subject: [PATCH 59/71] fix: charts --- app/Livewire/Charts/ServerCpu.php | 17 ++++++++- app/Livewire/Charts/ServerMemory.php | 17 ++++++++- app/Models/Server.php | 38 +------------------ .../livewire/charts/server-cpu.blade.php | 6 +-- .../livewire/charts/server-memory.blade.php | 6 +-- 5 files changed, 40 insertions(+), 44 deletions(-) diff --git a/app/Livewire/Charts/ServerCpu.php b/app/Livewire/Charts/ServerCpu.php index 454f59193..50a7e115e 100644 --- a/app/Livewire/Charts/ServerCpu.php +++ b/app/Livewire/Charts/ServerCpu.php @@ -15,7 +15,9 @@ class ServerCpu extends Component public $categories; - public $interval = 5; + public int $interval = 5; + + public bool $poll = true; public function render() { @@ -27,6 +29,16 @@ public function mount() $this->loadData(); } + public function pollData() + { + if ($this->poll || $this->interval <= 10) { + $this->loadData(); + if ($this->interval > 10) { + $this->poll = false; + } + } + } + public function loadData() { try { @@ -44,6 +56,9 @@ public function loadData() public function setInterval() { + if ($this->interval <= 10) { + $this->poll = true; + } $this->loadData(); } } diff --git a/app/Livewire/Charts/ServerMemory.php b/app/Livewire/Charts/ServerMemory.php index f3f992e97..15a2345f5 100644 --- a/app/Livewire/Charts/ServerMemory.php +++ b/app/Livewire/Charts/ServerMemory.php @@ -15,13 +15,25 @@ class ServerMemory extends Component public $categories; - public $interval = 5; + public int $interval = 5; + + public bool $poll = true; public function render() { return view('livewire.charts.server-memory'); } + public function pollData() + { + if ($this->poll || $this->interval <= 10) { + $this->loadData(); + if ($this->interval > 10) { + $this->poll = false; + } + } + } + public function mount() { $this->loadData(); @@ -44,6 +56,9 @@ public function loadData() public function setInterval() { + if ($this->interval <= 10) { + $this->poll = true; + } $this->loadData(); } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 1dedf8045..3d40042bb 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -498,28 +498,11 @@ public function getCpuMetrics(int $mins = 5) $parsedCollection = collect($cpu)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { [$time, $value] = explode(',', trim($line)); + $value = number_format($value, 0); return [(int) $time, (float) $value]; }); }); - if ($mins === 30 || $mins === 60) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 5 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } - if ($mins === 720) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 10 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } - if ($mins === 10080) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 20 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } return $parsedCollection->toArray(); } @@ -542,28 +525,11 @@ public function getMemoryMetrics(int $mins = 5) $parsedCollection = collect($memory)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { [$time, $used, $free, $usedPercent] = explode(',', trim($line)); + $usedPercent = number_format($usedPercent, 0); return [(int) $time, (float) $usedPercent]; }); }); - if ($mins === 30 || $mins === 60) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 5 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } - if ($mins === 720) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 10 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } - if ($mins === 10080) { - $parsedCollection = $parsedCollection->filter(function ($item, $key) { - return $key % 20 === 0; - }); - $parsedCollection = $parsedCollection->values(); - } return $parsedCollection->toArray(); } diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index ff85f94f1..a6eefdf15 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -1,8 +1,8 @@ -
    +

    CPU

    - - + + diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php index 1b29e20b6..8003a6eab 100644 --- a/resources/views/livewire/charts/server-memory.blade.php +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -1,8 +1,8 @@ -
    +

    Memory

    - - + + From 07e55b26368068bd76477b951d61dbb8326bac61 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 09:42:44 +0200 Subject: [PATCH 60/71] chore: Update chart styling and loading text --- .../livewire/charts/server-cpu.blade.php | 26 ++++--------------- .../livewire/charts/server-memory.blade.php | 21 +++++---------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index a6eefdf15..eaa2d3c08 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -57,31 +57,10 @@ colors: [baseColor], xaxis: { type: 'datetime', - labels: { - show: true, - style: { - colors: textColor, - } - } - }, - yaxis: { - show: true, - labels: { - show: true, - style: { - colors: textColor, - } - } }, series: [{ data: '{!! $data !!}' }], - noData: { - text: 'Loading...', - style: { - color: textColor, - } - }, tooltip: { enabled: false, }, @@ -100,20 +79,25 @@ }], colors: [baseColor], xaxis: { + type: 'datetime', labels: { + show: true, style: { colors: textColor, } } }, yaxis: { + show: true, labels: { + show: true, style: { colors: textColor, } } }, noData: { + text: 'Loading...', style: { color: textColor, } diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php index 8003a6eab..40404fdae 100644 --- a/resources/views/livewire/charts/server-memory.blade.php +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -64,24 +64,9 @@ } } }, - yaxis: { - show: true, - labels: { - show: true, - style: { - colors: textColor, - } - } - }, series: [{ data: '{!! $data !!}' }], - noData: { - text: 'Loading...', - style: { - color: textColor, - } - }, tooltip: { enabled: false, }, @@ -100,20 +85,26 @@ }], colors: [baseColor], xaxis: { + type: 'datetime', labels: { + show: true, style: { colors: textColor, } } }, yaxis: { + min: 0, + show: true, labels: { + show: true, style: { colors: textColor, } } }, noData: { + text: 'Loading...', style: { color: textColor, } From fa99cbce4aec0f9432845cb2def17c9657013253 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 10:33:34 +0200 Subject: [PATCH 61/71] do not load charts on mount --- app/Livewire/Charts/ServerCpu.php | 5 ----- app/Livewire/Charts/ServerMemory.php | 5 ----- resources/views/livewire/charts/server-cpu.blade.php | 2 +- resources/views/livewire/charts/server-memory.blade.php | 2 +- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app/Livewire/Charts/ServerCpu.php b/app/Livewire/Charts/ServerCpu.php index 50a7e115e..5f3283009 100644 --- a/app/Livewire/Charts/ServerCpu.php +++ b/app/Livewire/Charts/ServerCpu.php @@ -24,11 +24,6 @@ public function render() return view('livewire.charts.server-cpu'); } - public function mount() - { - $this->loadData(); - } - public function pollData() { if ($this->poll || $this->interval <= 10) { diff --git a/app/Livewire/Charts/ServerMemory.php b/app/Livewire/Charts/ServerMemory.php index 15a2345f5..911f267f6 100644 --- a/app/Livewire/Charts/ServerMemory.php +++ b/app/Livewire/Charts/ServerMemory.php @@ -34,11 +34,6 @@ public function pollData() } } - public function mount() - { - $this->loadData(); - } - public function loadData() { try { diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index eaa2d3c08..8ecb65b4f 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -1,4 +1,4 @@ -
    +

    CPU

    diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php index 40404fdae..522fa6bb6 100644 --- a/resources/views/livewire/charts/server-memory.blade.php +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -1,4 +1,4 @@ -
    +

    Memory

    From 49ad8b7cc1b20545bb82aa8e64194a11a37b6925 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 10:37:01 +0200 Subject: [PATCH 62/71] loadign charts --- resources/views/livewire/charts/server-cpu.blade.php | 8 +++++++- resources/views/livewire/charts/server-memory.blade.php | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index 8ecb65b4f..fe7131ade 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -59,8 +59,14 @@ type: 'datetime', }, series: [{ - data: '{!! $data !!}' + data: [] }], + noData: { + text: 'Loading...', + style: { + color: textColor, + } + }, tooltip: { enabled: false, }, diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php index 522fa6bb6..737526b0d 100644 --- a/resources/views/livewire/charts/server-memory.blade.php +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -65,8 +65,14 @@ } }, series: [{ - data: '{!! $data !!}' + data: [] }], + noData: { + text: 'Loading...', + style: { + color: textColor, + } + }, tooltip: { enabled: false, }, From 5221aa0ab739be7006064b0807c7858bd85d5011 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 13:29:01 +0200 Subject: [PATCH 63/71] chore: Update sentinel version to 0.0.9 --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index b983658d4..b681ca8f5 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "4.0.0-beta.298" }, "sentinel": { - "version": "0.0.8" + "version": "0.0.9" } } } From e956642982aff7a487d33fe302664a392bcc70d8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 19 Jun 2024 16:09:00 +0200 Subject: [PATCH 64/71] chore: Update Spanish translation for failed authentication messages --- lang/es.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lang/es.json b/lang/es.json index 866f5ae88..0d8c0c940 100644 --- a/lang/es.json +++ b/lang/es.json @@ -15,10 +15,10 @@ "auth.registration_disabled": "El registro está desactivado. Por favor contacta con el administrador.", "auth.reset_password": "Cambiar contraseña", "auth.failed": "Las credenciales no coinciden con nuestro registro..", - "auth.failed.callback": "Fallo el proceso de inicio de sesión con el proveedor.", + "auth.failed.callback": "Falló el proceso de inicio de sesión con el proveedor.", "auth.failed.password": "La contraseña es incorrecta.", "auth.failed.email": "No encontramos un usuario con ese correo.", - "auth.throttle": "Demasiados intentos. Por favor Inténtalo en :seconds segundos.", + "auth.throttle": "Demasiados intentos. Por favor inténtalo en :seconds segundos.", "input.name": "Nombre", "input.email": "Correo", "input.password": "Contraseña", @@ -26,5 +26,5 @@ "input.code": "Código de único uso", "input.recovery_code": "Código de recuperación", "button.save": "Guardar", - "repository.url": "Examples
    Para repositorios publicos, usar https://....
    Para repositorios privados, usar git@....

    https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
    https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
    https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
    https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." -} + "repository.url": "Examples
    Para repositorios públicos, usar https://....
    Para repositorios privados, usar git@....

    https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada.
    https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada.
    https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada.
    https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada." +} \ No newline at end of file From 0eccbf64f4259d5249f1cafeb309adcc18b5d395 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 10:44:31 +0200 Subject: [PATCH 65/71] remove sentinel from coolify versions --- bootstrap/helpers/shared.php | 6 +++++- versions.json | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 3129fef90..9a2867353 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -79,6 +79,10 @@ function backup_dir(): string { return base_configuration_dir().'/backups'; } +function metrics_dir(): string +{ + return base_configuration_dir().'/metrics'; +} function generate_readme_file(string $name, string $updated_at): string { @@ -161,7 +165,7 @@ function get_latest_sentinel_version(): string return '0.0.8'; } try { - $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); + $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); $versions = $response->json(); return data_get($versions, 'coolify.sentinel.version'); diff --git a/versions.json b/versions.json index b681ca8f5..3b8256b6b 100644 --- a/versions.json +++ b/versions.json @@ -2,9 +2,6 @@ "coolify": { "v4": { "version": "4.0.0-beta.298" - }, - "sentinel": { - "version": "0.0.9" } } } From 439bee1203966b0f40d8bd9bdb35a4715ec01df9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 10:50:49 +0200 Subject: [PATCH 66/71] refactor: update shared.php to use correct key for retrieving sentinel version --- bootstrap/helpers/shared.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 9a2867353..2feda420f 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -168,7 +168,7 @@ function get_latest_sentinel_version(): string $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); $versions = $response->json(); - return data_get($versions, 'coolify.sentinel.version'); + return data_get($versions, 'sentinel.version'); } catch (\Throwable $e) { //throw $e; ray($e->getMessage()); From c81ad5cd03a86be77b4a0702bb1ec28d4acedfe2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 13:17:06 +0200 Subject: [PATCH 67/71] feat: container metrics --- app/Actions/Server/StartSentinel.php | 2 +- app/Console/Kernel.php | 2 +- app/Jobs/DockerCleanupJob.php | 4 +- app/Jobs/PullSentinelImageJob.php | 6 +- app/Jobs/ServerStatusJob.php | 6 +- app/Livewire/Project/Shared/Metrics.php | 64 +++++ app/Livewire/Server/Form.php | 29 ++- app/Models/Application.php | 29 +++ app/Models/Server.php | 38 ++- app/Models/StandaloneClickhouse.php | 29 +++ app/Models/StandaloneDragonfly.php | 29 +++ app/Models/StandaloneKeydb.php | 29 +++ app/Models/StandaloneMariadb.php | 29 +++ app/Models/StandaloneMongodb.php | 29 +++ app/Models/StandaloneMysql.php | 29 +++ app/Models/StandalonePostgresql.php | 29 +++ app/Models/StandaloneRedis.php | 29 +++ bootstrap/helpers/shared.php | 3 - ...4_06_20_102551_add_server_api_sentinel.php | 28 ++ resources/views/layouts/base.blade.php | 14 + .../livewire/charts/server-cpu.blade.php | 2 +- .../livewire/charts/server-memory.blade.php | 2 +- .../application/configuration.blade.php | 6 + .../project/database/configuration.blade.php | 6 + .../livewire/project/shared/metrics.blade.php | 243 ++++++++++++++++++ .../views/livewire/server/form.blade.php | 11 +- .../views/livewire/server/show.blade.php | 16 -- 27 files changed, 704 insertions(+), 39 deletions(-) create mode 100644 app/Livewire/Project/Shared/Metrics.php create mode 100644 database/migrations/2024_06_20_102551_add_server_api_sentinel.php create mode 100644 resources/views/livewire/project/shared/metrics.blade.php diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php index a2afea3bb..b79bc8f67 100644 --- a/app/Actions/Server/StartSentinel.php +++ b/app/Actions/Server/StartSentinel.php @@ -21,6 +21,6 @@ public function handle(Server $server, $version = 'latest', bool $restart = fals "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", 'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', 'chmod -R 700 /data/coolify/metrics /data/coolify/logs', - ], $server, false); + ], $server, true); } } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c2f679699..f529f63b9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -61,7 +61,7 @@ private function pull_images($schedule) { $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); foreach ($servers as $server) { - if ($server->isMetricsEnabled()) { + if ($server->isSentinelEnabled()) { $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer(); } $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index e637fb6d4..e3ac193dc 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -22,7 +22,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public ?int $usageBefore = null; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function handle(): void { diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index 30b36d99f..e6252df0f 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -28,7 +28,9 @@ public function uniqueId(): string return $this->server->uuid; } - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function handle(): void { @@ -50,7 +52,7 @@ public function handle(): void } ray('Sentinel image is up to date'); } catch (\Throwable $e) { - send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); + // send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); ray($e->getMessage()); throw $e; } diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index 938f3fe40..1bba912c2 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -25,7 +25,9 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + } public function middleware(): array { @@ -46,7 +48,7 @@ public function handle() if ($this->server->isFunctional()) { $this->cleanup(notify: false); $this->remove_unnecessary_coolify_yaml(); - if ($this->server->isMetricsEnabled()) { + if ($this->server->isSentinelEnabled()) { $this->server->checkSentinel(); } } diff --git a/app/Livewire/Project/Shared/Metrics.php b/app/Livewire/Project/Shared/Metrics.php new file mode 100644 index 000000000..d9d7dd3ef --- /dev/null +++ b/app/Livewire/Project/Shared/Metrics.php @@ -0,0 +1,64 @@ +poll || $this->interval <= 10) { + $this->loadData(); + if ($this->interval > 10) { + $this->poll = false; + } + } + } + + public function loadData() + { + try { + $metrics = $this->resource->getMetrics($this->interval); + $cpuMetrics = collect($metrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $memoryMetrics = collect($metrics)->map(function ($metric) { + return [$metric[0], $metric[2]]; + }); + $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ + 'seriesData' => $cpuMetrics, + ]); + $this->dispatch("refreshChartData-{$this->chartId}-memory", [ + 'seriesData' => $memoryMetrics, + ]); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function setInterval() + { + if ($this->interval <= 10) { + $this->poll = true; + } + $this->loadData(); + } + + public function render() + { + return view('livewire.project.shared.metrics'); + } +} diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 87c0d09d1..5616123a5 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -44,6 +44,7 @@ class Form extends Component 'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1', 'server.settings.metrics_history_days' => 'required|integer|min:1', 'wildcard_domain' => 'nullable|url', + 'server.settings.is_server_api_enabled' => 'required|boolean', ]; protected $validationAttributes = [ @@ -63,7 +64,7 @@ class Form extends Component 'server.settings.metrics_token' => 'Metrics Token', 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', 'server.settings.metrics_history_days' => 'Metrics History', - + 'server.settings.is_server_api_enabled' => 'Server API', ]; public function mount() @@ -85,6 +86,18 @@ public function updatedServerSettingsIsBuildServer() $this->dispatch('proxyStatusUpdated'); } + public function checkPortForServerApi() + { + try { + if ($this->server->settings->is_server_api_enabled === true) { + $this->server->checkServerApi(); + $this->dispatch('success', 'Server API is reachable.'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function instantSave() { try { @@ -94,12 +107,22 @@ public function instantSave() $this->server->save(); $this->dispatch('success', 'Server updated.'); $this->dispatch('refreshServerShow'); - if ($this->server->isMetricsEnabled()) { + if ($this->server->isSentinelEnabled()) { PullSentinelImageJob::dispatchSync($this->server); - $this->dispatch('reloadWindow'); + ray('Sentinel is enabled'); + if ($this->server->settings->isDirty('is_metrics_enabled')) { + $this->dispatch('reloadWindow'); + } + if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { + ray('Starting sentinel'); + + } } else { + ray('Sentinel is not enabled'); StopSentinel::dispatch($this->server); } + // $this->checkPortForServerApi(); + } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/Application.php b/app/Models/Application.php index f2a7ce51c..c76a42d71 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1167,4 +1167,33 @@ public function generate_preview_fqdn(int $pull_request_id) return $preview; } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = generateApplicationContainerName($this); + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 3d40042bb..7a99940fd 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\Support\Stringable; @@ -460,15 +461,44 @@ public function forceDisableServer() Storage::disk('ssh-mux')->delete($this->muxFilename()); } + public function isSentinelEnabled() + { + return $this->isMetricsEnabled() || $this->isServerApiEnabled(); + } + public function isMetricsEnabled() { return $this->settings->is_metrics_enabled; } + public function isServerApiEnabled() + { + return $this->settings->is_server_api_enabled; + } + + public function checkServerApi() + { + if ($this->isServerApiEnabled()) { + $server_ip = $this->ip; + if (isDev()) { + if ($this->id === 0) { + $server_ip = 'localhost'; + } + } + $command = "curl -s http://{$server_ip}:12172/api/health"; + $process = Process::timeout(5)->run($command); + if ($process->failed()) { + ray($process->exitCode(), $process->output(), $process->errorOutput()); + throw new \Exception("Server API is not reachable on http://{$server_ip}:12172"); + } + + } + } + public function checkSentinel() { ray("Checking sentinel on server: {$this->name}"); - if ($this->isMetricsEnabled()) { + if ($this->isSentinelEnabled()) { $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false); $sentinel_found = json_decode($sentinel_found, true); $status = data_get($sentinel_found, '0.State.Status', 'exited'); @@ -497,10 +527,10 @@ public function getCpuMetrics(int $mins = 5) $cpu = str($cpu)->explode("\n")->skip(1)->all(); $parsedCollection = collect($cpu)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { - [$time, $value] = explode(',', trim($line)); - $value = number_format($value, 0); + [$time, $cpu_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 0); - return [(int) $time, (float) $value]; + return [(int) $time, (float) $cpu_usage_percent]; }); }); diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index c5e252c34..e968db18d 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -226,4 +226,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 8c739d984..c6718acfe 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -226,4 +226,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 5216681c9..142f960aa 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -226,4 +226,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 33fd2cbc2..7e6d2e0d1 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -226,4 +226,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 0cc52b3f7..df895bb34 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -246,4 +246,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 174736f77..bd160f877 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -227,4 +227,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index a5bf4dc2a..114d376e8 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -227,4 +227,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index ed379750e..022cd8d09 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -222,4 +222,33 @@ public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + + public function getMetrics(int $mins = 5) + { + $server = $this->destination->server; + $container_name = $this->uuid; + if ($server->isMetricsEnabled()) { + $from = now()->subMinutes($mins)->toIso8601ZuluString(); + $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); + if (str($metrics)->contains('error')) { + $error = json_decode($metrics, true); + $error = data_get($error, 'error', 'Something is not okay, are you okay?'); + if ($error == 'Unauthorized') { + $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; + } + throw new \Exception($error); + } + $metrics = str($metrics)->explode("\n")->skip(1)->all(); + $parsedCollection = collect($metrics)->flatMap(function ($item) { + return collect(explode("\n", trim($item)))->map(function ($line) { + [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); + $cpu_usage_percent = number_format($cpu_usage_percent, 2); + + return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; + }); + }); + + return $parsedCollection->toArray(); + } + } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 2feda420f..b9dc685b7 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -161,9 +161,6 @@ function get_route_parameters(): array function get_latest_sentinel_version(): string { - if (isDev()) { - return '0.0.8'; - } try { $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); $versions = $response->json(); diff --git a/database/migrations/2024_06_20_102551_add_server_api_sentinel.php b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php new file mode 100644 index 000000000..b840195af --- /dev/null +++ b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php @@ -0,0 +1,28 @@ +boolean('is_server_api_enabled')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('is_server_api_enabled'); + }); + } +}; diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index c1851e2f9..1b2628db1 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -60,6 +60,20 @@ document.documentElement.classList.remove('dark') } } + let theme = localStorage.theme + let baseColor = '#FCD452' + let textColor = '#ffffff' + + function checkTheme() { + theme = localStorage.theme + if (theme == 'dark') { + baseColor = '#FCD452' + textColor = '#ffffff' + } else { + baseColor = 'black' + textColor = '#000000' + } + } @auth window.Pusher = Pusher; window.Echo = new Echo({ diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php index fe7131ade..3aab224f9 100644 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ b/resources/views/livewire/charts/server-cpu.blade.php @@ -1,5 +1,5 @@
    -

    CPU

    +

    CPU (%)

    diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php index 737526b0d..851b9b975 100644 --- a/resources/views/livewire/charts/server-memory.blade.php +++ b/resources/views/livewire/charts/server-memory.blade.php @@ -1,5 +1,5 @@
    -

    Memory

    +

    Memory (MB)

    diff --git a/resources/views/livewire/project/application/configuration.blade.php b/resources/views/livewire/project/application/configuration.blade.php index 4be672063..639776730 100644 --- a/resources/views/livewire/project/application/configuration.blade.php +++ b/resources/views/livewire/project/application/configuration.blade.php @@ -74,6 +74,9 @@ @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" href="#">Resource Operations + Metrics + Tags @@ -126,6 +129,9 @@
    +
    + +
    diff --git a/resources/views/livewire/project/database/configuration.blade.php b/resources/views/livewire/project/database/configuration.blade.php index 809a48c0f..aa5b8ec5e 100644 --- a/resources/views/livewire/project/database/configuration.blade.php +++ b/resources/views/livewire/project/database/configuration.blade.php @@ -42,6 +42,9 @@ @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" href="#">Resource Operations + Metrics + Tags @@ -92,6 +95,9 @@
    +
    + +
    diff --git a/resources/views/livewire/project/shared/metrics.blade.php b/resources/views/livewire/project/shared/metrics.blade.php new file mode 100644 index 000000000..f27c71bf5 --- /dev/null +++ b/resources/views/livewire/project/shared/metrics.blade.php @@ -0,0 +1,243 @@ +
    +
    +

    Metrics

    +
    +
    Basic metrics for your container.
    + @if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') +
    Metrics are not available for Docker Compose applications yet!
    + @else + @if (!str($resource->status)->contains('running')) +
    Metrics are only available when the application is running!
    + @else + + + + + + + + + +
    +

    CPU (%)

    +
    + + + +

    Memory (MB)

    +
    + + +
    + @endif + @endif +
    diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index 9f061ba54..938e47cbd 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -145,13 +145,16 @@ class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-1 helper="You can define the maximum duration for a deployment to run before timing it out." />
    -

    Metrics

    - @if ($server->isMetricsEnabled()) - Restart Collector +

    Sentinel

    + @if ($server->isSentinelEnabled()) + Restart @endif
    - + + {{-- + Check Port for Server API --}}
    diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index d3a3bb8c6..0e8a37717 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -7,22 +7,6 @@ @if ($server->isFunctional() && $server->isMetricsEnabled())
    -
    From 078772495a008f7ab3b9e082116ff034541f73dc Mon Sep 17 00:00:00 2001 From: andrasbacsai Date: Thu, 20 Jun 2024 11:17:53 +0000 Subject: [PATCH 68/71] Fix styling --- app/Jobs/DockerCleanupJob.php | 4 +--- app/Jobs/PullSentinelImageJob.php | 4 +--- app/Jobs/ServerStatusJob.php | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index e3ac193dc..e637fb6d4 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public ?int $usageBefore = null; - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php index e6252df0f..f8c769382 100644 --- a/app/Jobs/PullSentinelImageJob.php +++ b/app/Jobs/PullSentinelImageJob.php @@ -28,9 +28,7 @@ public function uniqueId(): string return $this->server->uuid; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function handle(): void { diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index 1bba912c2..c7321a74c 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -25,9 +25,7 @@ public function backoff(): int return isDev() ? 1 : 3; } - public function __construct(public Server $server) - { - } + public function __construct(public Server $server) {} public function middleware(): array { From 6493ce3fe05426755a9250586eb30345d33e3a9d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 13:30:17 +0200 Subject: [PATCH 69/71] refactor: update container name assignment in Application model --- app/Models/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index c76a42d71..8113f5ec9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1171,7 +1171,7 @@ public function generate_preview_fqdn(int $pull_request_id) public function getMetrics(int $mins = 5) { $server = $this->destination->server; - $container_name = generateApplicationContainerName($this); + $container_name = $this->uuid; if ($server->isMetricsEnabled()) { $from = now()->subMinutes($mins)->toIso8601ZuluString(); $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); From 0468f255e7dc63a34edfb3c915abbc36c478bad9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 13:48:49 +0200 Subject: [PATCH 70/71] fix: static build with new nixpacks build process --- app/Jobs/ApplicationDeploymentJob.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 8f89da2d2..640950be9 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -293,13 +293,13 @@ public function handle(): void } 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->execute_remote_command( + // [ + // "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", + // 'hidden' => true, + // 'ignore_errors' => true, + // ] + // ); // $this->execute_remote_command( // [ @@ -1869,12 +1869,12 @@ private function build_image() $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); - $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, ]); - $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } $base64_build_command = base64_encode($build_command); @@ -1904,7 +1904,6 @@ private function build_image() ] ); } - $dockerfile = base64_encode("FROM {$this->application->static_image} WORKDIR /usr/share/nginx/html/ LABEL coolify.deploymentId={$this->deployment_uuid} From fff7ec9ba729ff6c7d00a1e19444a27b2e8db55d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 20 Jun 2024 13:54:15 +0200 Subject: [PATCH 71/71] refactor: remove commented code for docker container removal --- app/Jobs/ApplicationDeploymentJob.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 640950be9..65e9f2e93 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -293,13 +293,13 @@ public function handle(): void } 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->execute_remote_command( + [ + "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", + 'hidden' => true, + 'ignore_errors' => true, + ] + ); // $this->execute_remote_command( // [