mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-31 12:34:03 +00:00
Compare commits
84 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1e7c499e | ||
|
|
32fead5753 | ||
|
|
e5e9faba35 | ||
|
|
2852630d6c | ||
|
|
a4cc406114 | ||
|
|
53b15a5762 | ||
|
|
929a4e6474 | ||
|
|
45b597bbab | ||
|
|
0d1a2aa5d1 | ||
|
|
b82353d5e2 | ||
|
|
b17c09f7a7 | ||
|
|
f6c3fe7888 | ||
|
|
2e855e030f | ||
|
|
49f86621f4 | ||
|
|
03d9f93397 | ||
|
|
c472042a94 | ||
|
|
9f4356f67d | ||
|
|
a50317cc76 | ||
|
|
8afa98a1ca | ||
|
|
f6737f21dd | ||
|
|
e4a51cc116 | ||
|
|
acd78ae196 | ||
|
|
953bcfb5bb | ||
|
|
dacfab8b29 | ||
|
|
48b3e99939 | ||
|
|
41ad67c7c9 | ||
|
|
b49725cb1c | ||
|
|
75e674a966 | ||
|
|
9d826d9fb4 | ||
|
|
0af221f9fc | ||
|
|
9066c9bf90 | ||
|
|
77e3208f00 | ||
|
|
db5ecf07bd | ||
|
|
7f28aa6985 | ||
|
|
9d53e04ce9 | ||
|
|
3db9a1dd6e | ||
|
|
38f9761b67 | ||
|
|
7117ecf634 | ||
|
|
522e20f10a | ||
|
|
ebbce2396c | ||
|
|
f9a2ff6d90 | ||
|
|
df1b9e7319 | ||
|
|
e7c0c26b32 | ||
|
|
0dbb8b4420 | ||
|
|
a4b44bacc1 | ||
|
|
1338e68b8c | ||
|
|
f6f4cdde24 | ||
|
|
3daadf13c6 | ||
|
|
cbd5fab2e7 | ||
|
|
48ccb508f9 | ||
|
|
67edce0612 | ||
|
|
e8a41d7e6e | ||
|
|
be1dad03bd | ||
|
|
31db1db636 | ||
|
|
dca332a688 | ||
|
|
35f19ed53f | ||
|
|
a5c45ffe90 | ||
|
|
b6c3a65d2d | ||
|
|
220c8211fd | ||
|
|
ffbd04df29 | ||
|
|
73c59be865 | ||
|
|
3966abaf80 | ||
|
|
759517316a | ||
|
|
3e3024d47e | ||
|
|
517cb77637 | ||
|
|
14bd89a991 | ||
|
|
304de29924 | ||
|
|
ab3055150f | ||
|
|
6b9c7aa9c5 | ||
|
|
040f47b59c | ||
|
|
eac7834083 | ||
|
|
135a298080 | ||
|
|
0065a86371 | ||
|
|
2a842a2f50 | ||
|
|
231c02e00e | ||
|
|
4de4587ea6 | ||
|
|
8675e1d13f | ||
|
|
6ceacc68cc | ||
|
|
4aacf134b7 | ||
|
|
0605772715 | ||
|
|
3fa53556f4 | ||
|
|
76510b8971 | ||
|
|
66162966b9 | ||
|
|
71e1571c39 |
@@ -6,6 +6,7 @@
|
|||||||
USERID=
|
USERID=
|
||||||
GROUPID=
|
GROUPID=
|
||||||
############################################################################################################
|
############################################################################################################
|
||||||
|
APP_NAME=Coolify-localhost
|
||||||
APP_ID=development
|
APP_ID=development
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
|
|||||||
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a new bug report
|
||||||
|
title: '[Bug]: '
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: A clear and concise description of the problem
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Minimal Reproduction (if possible, example repository)
|
||||||
|
description: Please provide a step by step guide to reproduce the issue
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Exception or Error
|
||||||
|
description: Please provide error logs if possible.
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Coolify's version (see bottom left corner).
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 🤔 Community Support (Chat)
|
||||||
|
url: https://coollabs.io/discord
|
||||||
|
about: Reach out to us on Discord.
|
||||||
|
- name: 🙋♂️ Feature Requests
|
||||||
|
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
||||||
|
about: All feature requests will be discussed here.
|
||||||
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
84
.github/workflows/coolify-helper-next.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
name: Coolify Helper Image Development (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "next" ]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify-helper"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-helper/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
aarch64:
|
||||||
|
runs-on: [ self-hosted, arm64 ]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
no-cache: true
|
||||||
|
context: .
|
||||||
|
file: docker/coolify-helper/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [ amd64, aarch64 ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
8
.github/workflows/coolify-helper.yml
vendored
8
.github/workflows/coolify-helper.yml
vendored
@@ -2,7 +2,7 @@ name: Coolify Helper Image (v4)
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "next" ]
|
branches: [ "main" ]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/coolify-helper.yml
|
- .github/workflows/coolify-helper.yml
|
||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
file: docker/coolify-helper/Dockerfile
|
file: docker/coolify-helper/Dockerfile
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -78,3 +78,7 @@ jobs:
|
|||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
|
||||||
|
|||||||
5
.github/workflows/development-build.yml
vendored
5
.github/workflows/development-build.yml
vendored
@@ -3,6 +3,9 @@ name: Development Build (v4)
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["next"]
|
branches: ["next"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
@@ -73,4 +76,4 @@ jobs:
|
|||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ _ide_helper.php
|
|||||||
_ide_helper_models.php
|
_ide_helper_models.php
|
||||||
.rnd
|
.rnd
|
||||||
/.ssh
|
/.ssh
|
||||||
|
scripts/load-test/*
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -1,20 +1,30 @@
|
|||||||
# Coolify v4 Beta
|
# About the Project
|
||||||
|
|
||||||
An open-source & self-hostable Heroku / Netlify alternative.
|
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.
|
||||||
|
|
||||||
|
Image if you could have 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. 🪄️
|
||||||
|
|
||||||
|
For more information, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
|
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3).
|
||||||
|
|
||||||
|
# Cloud
|
||||||
|
|
||||||
|
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||||
|
|
||||||
|
You can easily attach your own servers, get all the automations, free email notifications, etc.
|
||||||
|
|
||||||
|
For more information & pricing, take a look at our landing page [here](https://coolify.io).
|
||||||
|
|
||||||
# Beta
|
# Beta
|
||||||
|
|
||||||
You are checking the next-gen of Coolify, aka v4. Hi 👋
|
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life.
|
||||||
|
|
||||||
It is still in beta, lots of improvements will come every day. Things could break, but we are working hard to make it stable as soon as possible. If you find any bugs, please report them.
|
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
||||||
|
|
||||||
Automatic updates are available, so you will receive the latest version as soon as it is released.
|
|
||||||
|
|
||||||
If you are looking for v3, check out the [v3 branch](https://github.com/coollabsio/coolify/tree/v3).
|
|
||||||
|
|
||||||
## What's new?
|
|
||||||
|
|
||||||
Well, the whole tech stack changed, core is different, so yeah, a lot (documentation incoming).
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -26,13 +36,19 @@ You can find the installation script [here](./scripts/install.sh).
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- Twitter: [@heyandras](https://twitter.com/heyandras)
|
Contact us [here](https://docs.coollabs.io/contact).
|
||||||
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
|
|
||||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
|
||||||
- Discord: [Invitation](https://coollabs.io/discord)
|
|
||||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
|
||||||
|
|
||||||
---
|
## Recognitions
|
||||||
|
|
||||||
|
<a href="https://news.ycombinator.com/item?id=26624341">
|
||||||
|
<img
|
||||||
|
style="width: 250px; height: 54px;" width="250" height="54"
|
||||||
|
alt="Featured on Hacker News"
|
||||||
|
src="https://hackernews-badge.vercel.app/api?id=26624341"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An open-source & self-hostable Heroku, Netlify alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
## 💰 Financial Contributors
|
## 💰 Financial Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class StartPostgresql
|
|||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->database->destination->network => [
|
$this->database->destination->network => [
|
||||||
'external' => false,
|
'external' => true,
|
||||||
'name' => $this->database->destination->network,
|
'name' => $this->database->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,8 +10,14 @@ class InstallDocker
|
|||||||
{
|
{
|
||||||
public function __invoke(Server $server, Team $team)
|
public function __invoke(Server $server, Team $team)
|
||||||
{
|
{
|
||||||
$dockerVersion = '23.0';
|
$dockerVersion = '24.0';
|
||||||
$config = base64_encode('{ "live-restore": true }');
|
$config = base64_encode('{
|
||||||
|
"log-driver": "json-file",
|
||||||
|
"log-opts": {
|
||||||
|
"max-size": "10m",
|
||||||
|
"max-file": "3"
|
||||||
|
}
|
||||||
|
}');
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$activity = remote_process([
|
$activity = remote_process([
|
||||||
"echo ####### Installing Prerequisites...",
|
"echo ####### Installing Prerequisites...",
|
||||||
@@ -34,7 +40,7 @@ class InstallDocker
|
|||||||
"echo ####### Restarting Docker Engine...",
|
"echo ####### Restarting Docker Engine...",
|
||||||
"systemctl restart docker",
|
"systemctl restart docker",
|
||||||
"echo ####### Creating default network...",
|
"echo ####### Creating default network...",
|
||||||
"docker network create --attachable coolify",
|
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||||
"echo ####### Done!"
|
"echo ####### Done!"
|
||||||
], $server);
|
], $server);
|
||||||
$found = StandaloneDocker::where('server_id', $server->id);
|
$found = StandaloneDocker::where('server_id', $server->id);
|
||||||
|
|||||||
182
app/Console/Commands/TestEmail.php
Normal file
182
app/Console/Commands/TestEmail.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\TeamInvitation;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\Application\DeploymentFailed;
|
||||||
|
use App\Notifications\Application\DeploymentSuccess;
|
||||||
|
use App\Notifications\Application\StatusChanged;
|
||||||
|
use App\Notifications\Database\BackupFailed;
|
||||||
|
use App\Notifications\Database\BackupSuccess;
|
||||||
|
use App\Notifications\Test;
|
||||||
|
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Mail\Message;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Mail;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
|
||||||
|
class TestEmail extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'email:test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Send a test email to the admin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
private ?MailMessage $mail = null;
|
||||||
|
private string $email = 'andras.bacsai@protonmail.com';
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$type = select(
|
||||||
|
'Which Email should be sent?',
|
||||||
|
options: [
|
||||||
|
'emails-test' => 'Test',
|
||||||
|
'application-deployment-success' => 'Application - Deployment Success',
|
||||||
|
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||||
|
'application-status-changed' => 'Application - Status Changed',
|
||||||
|
'backup-success' => 'Database - Backup Success',
|
||||||
|
'backup-failed' => 'Database - Backup Failed',
|
||||||
|
'invitation-link' => 'Invitation Link',
|
||||||
|
'waitlist-invitation-link' => 'Waitlist Invitation Link',
|
||||||
|
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->email = text('Email Address to send to');
|
||||||
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->subject("Test Email");
|
||||||
|
switch ($type) {
|
||||||
|
case 'emails-test':
|
||||||
|
$this->mail = (new Test())->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-deployment-success':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-deployment-failed':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$preview = ApplicationPreview::all()->first();
|
||||||
|
if (!$preview) {
|
||||||
|
$preview = ApplicationPreview::create([
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => 1,
|
||||||
|
'pull_request_html_url' => 'http://example.com',
|
||||||
|
'fqdn' => $application->fqdn,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new DeploymentFailed($application, 'test'))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
$this->mail = (new DeploymentFailed($application, 'test', $preview))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'application-status-changed':
|
||||||
|
$application = Application::all()->first();
|
||||||
|
$this->mail = (new StatusChanged($application))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'backup-failed':
|
||||||
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
|
$db = StandalonePostgresql::all()->first();
|
||||||
|
if (!$backup) {
|
||||||
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
|
'enabled' => true,
|
||||||
|
'frequency' => 'daily',
|
||||||
|
'save_s3' => false,
|
||||||
|
'database_id' => $db->id,
|
||||||
|
'database_type' => $db->getMorphClass(),
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$output = 'Because of an error, the backup of the database ' . $db->name . ' failed.';
|
||||||
|
$this->mail = (new BackupFailed($backup, $db, $output))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'backup-success':
|
||||||
|
$backup = ScheduledDatabaseBackup::all()->first();
|
||||||
|
$db = StandalonePostgresql::all()->first();
|
||||||
|
if (!$backup) {
|
||||||
|
$backup = ScheduledDatabaseBackup::create([
|
||||||
|
'enabled' => true,
|
||||||
|
'frequency' => 'daily',
|
||||||
|
'save_s3' => false,
|
||||||
|
'database_id' => $db->id,
|
||||||
|
'database_type' => $db->getMorphClass(),
|
||||||
|
'team_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'invitation-link':
|
||||||
|
$user = User::all()->first();
|
||||||
|
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||||
|
if (!$invitation) {
|
||||||
|
$invitation = TeamInvitation::create([
|
||||||
|
'uuid' => Str::uuid(),
|
||||||
|
'email' => $user->email,
|
||||||
|
'team_id' => 1,
|
||||||
|
'link' => 'http://example.com',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->mail = (new InvitationLink($user))->toMail();
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'waitlist-invitation-link':
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
|
'email' => 'test2@example.com',
|
||||||
|
'password' => "supersecretpassword",
|
||||||
|
]);
|
||||||
|
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
case 'waitlist-confirmation':
|
||||||
|
$this->mail = new MailMessage();
|
||||||
|
$this->mail->view(
|
||||||
|
'emails.waitlist-confirmation',
|
||||||
|
[
|
||||||
|
'confirmation_url' => 'http://example.com',
|
||||||
|
'cancel_url' => 'http://example.com',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->mail->subject('You are on the waitlist!');
|
||||||
|
$this->sendEmail();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function sendEmail()
|
||||||
|
{
|
||||||
|
Mail::send(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
fn (Message $message) => $message
|
||||||
|
->to($this->email)
|
||||||
|
->subject($this->mail->subject)
|
||||||
|
->html((string)$this->mail->render())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,20 +6,20 @@ use App\Models\User;
|
|||||||
use App\Models\Waitlist;
|
use App\Models\Waitlist;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class InviteFromWaitlist extends Command
|
class WaitlistInvite extends Command
|
||||||
{
|
{
|
||||||
public Waitlist|null $next_patient = null;
|
public Waitlist|User|null $next_patient = null;
|
||||||
public User|null $new_user = null;
|
|
||||||
public string|null $password = null;
|
public string|null $password = null;
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'app:invite-from-waitlist {email?}';
|
protected $signature = 'waitlist:invite {email?} {--only-email}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -34,7 +34,16 @@ class InviteFromWaitlist extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if ($this->argument('email')) {
|
if ($this->argument('email')) {
|
||||||
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
if ($this->option('only-email')) {
|
||||||
|
$this->next_patient = User::whereEmail($this->argument('email'))->first();
|
||||||
|
$this->password = Str::password();
|
||||||
|
$this->next_patient->update([
|
||||||
|
'password' => Hash::make($this->password),
|
||||||
|
'force_password_reset' => true,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->next_patient = Waitlist::where('email', $this->argument('email'))->first();
|
||||||
|
}
|
||||||
if (!$this->next_patient) {
|
if (!$this->next_patient) {
|
||||||
$this->error("{$this->argument('email')} not found in the waitlist.");
|
$this->error("{$this->argument('email')} not found in the waitlist.");
|
||||||
return;
|
return;
|
||||||
@@ -43,6 +52,10 @@ class InviteFromWaitlist extends Command
|
|||||||
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
$this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first();
|
||||||
}
|
}
|
||||||
if ($this->next_patient) {
|
if ($this->next_patient) {
|
||||||
|
if ($this->option('only-email')) {
|
||||||
|
$this->send_email();
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->register_user();
|
$this->register_user();
|
||||||
$this->remove_from_waitlist();
|
$this->remove_from_waitlist();
|
||||||
$this->send_email();
|
$this->send_email();
|
||||||
@@ -55,7 +68,7 @@ class InviteFromWaitlist extends Command
|
|||||||
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
$already_registered = User::whereEmail($this->next_patient->email)->first();
|
||||||
if (!$already_registered) {
|
if (!$already_registered) {
|
||||||
$this->password = Str::password();
|
$this->password = Str::password();
|
||||||
$this->new_user = User::create([
|
User::create([
|
||||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
@@ -73,10 +86,14 @@ class InviteFromWaitlist extends Command
|
|||||||
}
|
}
|
||||||
private function send_email()
|
private function send_email()
|
||||||
{
|
{
|
||||||
|
ray($this->next_patient->email, $this->password);
|
||||||
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'email' => $this->next_patient->email,
|
'email' => $this->next_patient->email,
|
||||||
'password' => $this->password,
|
'password' => $this->password,
|
||||||
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
|
||||||
send_user_an_email($mail, $this->next_patient->email);
|
send_user_an_email($mail, $this->next_patient->email);
|
||||||
@@ -2,14 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
|
use App\Jobs\ApplicationContainerStatusJob;
|
||||||
use App\Jobs\CheckResaleLicenseJob;
|
use App\Jobs\CheckResaleLicenseJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
|
use App\Jobs\DatabaseContainerStatusJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ProxyCheckJob;
|
use App\Jobs\ProxyCheckJob;
|
||||||
use App\Jobs\ResourceStatusJob;
|
use App\Jobs\ResourceStatusJob;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
@@ -17,28 +22,46 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
// $schedule->call(fn() => $this->check_scheduled_backups($schedule))->everyTenSeconds();
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new ResourceStatusJob)->everyMinute();
|
// $schedule->job(new ResourceStatusJob)->everyMinute();
|
||||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
|
||||||
|
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly();
|
||||||
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
$schedule->job(new DockerCleanupJob)->everyOddHour();
|
||||||
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
|
|
||||||
} else {
|
} else {
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
// $schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
|
||||||
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes();
|
|
||||||
}
|
}
|
||||||
|
$this->instance_auto_update($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
|
$this->check_resources($schedule);
|
||||||
}
|
}
|
||||||
|
private function check_resources($schedule)
|
||||||
|
{
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$schedule->job(new ApplicationContainerStatusJob($application))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
$postgresqls = StandalonePostgresql::all();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function instance_auto_update($schedule){
|
||||||
|
if (isDev()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($settings->is_auto_update_enabled) {
|
||||||
|
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
private function check_scheduled_backups($schedule)
|
private function check_scheduled_backups($schedule)
|
||||||
{
|
{
|
||||||
ray('check_scheduled_backups');
|
ray('check_scheduled_backups');
|
||||||
@@ -51,13 +74,18 @@ class Kernel extends ConsoleKernel
|
|||||||
if (!$scheduled_backup->enabled) {
|
if (!$scheduled_backup->enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (is_null(data_get($scheduled_backup,'database'))) {
|
||||||
|
ray('database not found');
|
||||||
|
$scheduled_backup->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$schedule->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency);
|
))->cron($scheduled_backup->frequency)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Enums;
|
|||||||
|
|
||||||
enum ProxyTypes: string
|
enum ProxyTypes: string
|
||||||
{
|
{
|
||||||
|
case NONE = 'NONE';
|
||||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||||
case NGINX = 'NGINX';
|
case NGINX = 'NGINX';
|
||||||
case CADDY = 'CADDY';
|
case CADDY = 'CADDY';
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Exceptions;
|
|||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
|
use Sentry\State\Scope;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
@@ -48,6 +49,11 @@ class Handler extends ExceptionHandler
|
|||||||
if ($this->settings->do_not_track || isDev()) {
|
if ($this->settings->do_not_track || isDev()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
app('sentry')->configureScope(
|
||||||
|
function (Scope $scope){
|
||||||
|
$scope->setUser(['id'=> config('sentry.server_name')]);
|
||||||
|
}
|
||||||
|
);
|
||||||
Integration::captureUnhandledException($e);
|
Integration::captureUnhandledException($e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,41 @@ use App\Models\S3Storage;
|
|||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Auth;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests, ValidatesRequests;
|
use AuthorizesRequests, ValidatesRequests;
|
||||||
|
|
||||||
|
public function link()
|
||||||
|
{
|
||||||
|
$token = request()->get('token');
|
||||||
|
if ($token) {
|
||||||
|
$decrypted = Crypt::decryptString($token);
|
||||||
|
$email = Str::of($decrypted)->before('@@@');
|
||||||
|
$password = Str::of($decrypted)->after('@@@');
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
if (!$user) {
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
if (Hash::check($password, $user->password)) {
|
||||||
|
Auth::login($user);
|
||||||
|
$team = $user->teams()->first();
|
||||||
|
session(['currentTeam' => $team]);
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
|
}
|
||||||
public function subscription()
|
public function subscription()
|
||||||
{
|
{
|
||||||
if (!isCloud()) {
|
if (!isCloud()) {
|
||||||
@@ -37,10 +63,12 @@ class Controller extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function force_passoword_reset() {
|
public function force_passoword_reset()
|
||||||
|
{
|
||||||
return view('auth.force-password-reset');
|
return view('auth.force-password-reset');
|
||||||
}
|
}
|
||||||
public function boarding() {
|
public function boarding()
|
||||||
|
{
|
||||||
if (currentTeam()->boarding || isDev()) {
|
if (currentTeam()->boarding || isDev()) {
|
||||||
return view('boarding');
|
return view('boarding');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ class ProjectController extends Controller
|
|||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
'database_uuid' => $standalone_postgresql->uuid,
|
'database_uuid' => $standalone_postgresql->uuid,
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
if ($server) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return view('project.new', [
|
return view('project.new', [
|
||||||
'type' => $type
|
'type' => $type
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Actions\Server\InstallDocker;
|
|||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -70,9 +71,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
}
|
}
|
||||||
public function skipBoarding()
|
public function skipBoarding()
|
||||||
{
|
{
|
||||||
currentTeam()->update([
|
Team::find(currentTeam()->id)->update([
|
||||||
'show_boarding' => false
|
'show_boarding' => false
|
||||||
]);
|
]);
|
||||||
|
ray(currentTeam());
|
||||||
refreshSession();
|
refreshSession();
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ class Dashboard extends Component
|
|||||||
}
|
}
|
||||||
$this->projects = $projects->count();
|
$this->projects = $projects->count();
|
||||||
}
|
}
|
||||||
|
// public function getIptables()
|
||||||
|
// {
|
||||||
|
// $servers = Server::ownedByCurrentTeam()->get();
|
||||||
|
// foreach ($servers as $server) {
|
||||||
|
// checkRequiredCommands($server);
|
||||||
|
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
|
||||||
|
// ray($iptables);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.dashboard');
|
return view('livewire.dashboard');
|
||||||
|
|||||||
@@ -18,22 +18,26 @@ class ForcePasswordReset extends Component
|
|||||||
'password' => 'required|min:8',
|
'password' => 'required|min:8',
|
||||||
'password_confirmation' => 'required|same:password',
|
'password_confirmation' => 'required|same:password',
|
||||||
];
|
];
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
$this->email = auth()->user()->email;
|
$this->email = auth()->user()->email;
|
||||||
}
|
}
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->rateLimit(10);
|
$this->rateLimit(10);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
|
||||||
auth()->user()->forceFill([
|
auth()->user()->forceFill([
|
||||||
'password' => Hash::make($this->password),
|
'password' => Hash::make($this->password),
|
||||||
'force_password_reset' => false,
|
'force_password_reset' => false,
|
||||||
])->save();
|
])->save();
|
||||||
auth()->logout();
|
if ($firstLogin) {
|
||||||
return redirect()->route('login')->with('status', 'Your initial password has been set.');
|
send_internal_notification('First login for ' . auth()->user()->email);
|
||||||
} catch(\Exception $e) {
|
}
|
||||||
return general_error_handler(err:$e, that:$this);
|
return redirect()->route('dashboard');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
54
app/Http/Livewire/Help.php
Normal file
54
app/Http/Livewire/Help.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Route;
|
||||||
|
|
||||||
|
class Help extends Component
|
||||||
|
{
|
||||||
|
use WithRateLimiting;
|
||||||
|
public string $description;
|
||||||
|
public string $subject;
|
||||||
|
public ?string $path = null;
|
||||||
|
protected $rules = [
|
||||||
|
'description' => 'required|min:10',
|
||||||
|
'subject' => 'required|min:3'
|
||||||
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->path = Route::current()->uri();
|
||||||
|
if (isDev()) {
|
||||||
|
$this->description = "I'm having trouble with {$this->path}";
|
||||||
|
$this->subject = "Help with {$this->path}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->rateLimit(1, 60);
|
||||||
|
$this->validate();
|
||||||
|
$subscriptionType = auth()->user()?->subscription?->type() ?? 'unknown';
|
||||||
|
$debug = "Route: {$this->path}";
|
||||||
|
$mail = new MailMessage();
|
||||||
|
$mail->view(
|
||||||
|
'emails.help',
|
||||||
|
[
|
||||||
|
'description' => $this->description,
|
||||||
|
'debug' => $debug
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||||
|
send_user_an_email($mail, 'hi@coollabs.io');
|
||||||
|
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.help')->layout('layouts.app');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,26 +8,30 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class DiscordSettings extends Component
|
class DiscordSettings extends Component
|
||||||
{
|
{
|
||||||
public Team $model;
|
public Team $team;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'model.discord_enabled' => 'nullable|boolean',
|
'team.discord_enabled' => 'nullable|boolean',
|
||||||
'model.discord_webhook_url' => 'required|url',
|
'team.discord_webhook_url' => 'required|url',
|
||||||
'model.discord_notifications_test' => 'nullable|boolean',
|
'team.discord_notifications_test' => 'nullable|boolean',
|
||||||
'model.discord_notifications_deployments' => 'nullable|boolean',
|
'team.discord_notifications_deployments' => 'nullable|boolean',
|
||||||
'model.discord_notifications_status_changes' => 'nullable|boolean',
|
'team.discord_notifications_status_changes' => 'nullable|boolean',
|
||||||
'model.discord_notifications_database_backups' => 'nullable|boolean',
|
'team.discord_notifications_database_backups' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'model.discord_webhook_url' => 'Discord Webhook',
|
'team.discord_webhook_url' => 'Discord Webhook',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->submit();
|
$this->submit();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
$this->model->discord_enabled = false;
|
$this->team->discord_enabled = false;
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,8 +45,8 @@ class DiscordSettings extends Component
|
|||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->model->save();
|
$this->team->save();
|
||||||
if (is_a($this->model, Team::class)) {
|
if (is_a($this->team, Team::class)) {
|
||||||
refreshSession();
|
refreshSession();
|
||||||
}
|
}
|
||||||
$this->emit('success', 'Settings saved.');
|
$this->emit('success', 'Settings saved.');
|
||||||
@@ -50,7 +54,7 @@ class DiscordSettings extends Component
|
|||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->model->notify(new Test);
|
$this->team->notify(new Test());
|
||||||
$this->emit('success', 'Test notification sent.');
|
$this->emit('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class EmailSettings extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
||||||
$this->emails = auth()->user()->email;
|
$this->emails = auth()->user()->email;
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,14 @@ class EmailSettings extends Component
|
|||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function saveModel()
|
||||||
|
{
|
||||||
|
$this->team->save();
|
||||||
|
if (is_a($this->team, Team::class)) {
|
||||||
|
refreshSession();
|
||||||
|
}
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
66
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
66
app/Http/Livewire/Notifications/TelegramSettings.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class TelegramSettings extends Component
|
||||||
|
{
|
||||||
|
public Team $team;
|
||||||
|
protected $rules = [
|
||||||
|
'team.telegram_enabled' => 'nullable|boolean',
|
||||||
|
'team.telegram_token' => 'required|string',
|
||||||
|
'team.telegram_chat_id' => 'required|string',
|
||||||
|
'team.telegram_notifications_test' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_database_backups' => 'nullable|boolean',
|
||||||
|
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
|
||||||
|
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
|
||||||
|
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
|
||||||
|
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'team.telegram_token' => 'Token',
|
||||||
|
'team.telegram_chat_id' => 'Chat ID',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->submit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
$this->team->telegram_enabled = false;
|
||||||
|
$this->validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->validate();
|
||||||
|
$this->saveModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveModel()
|
||||||
|
{
|
||||||
|
$this->team->save();
|
||||||
|
if (is_a($this->team, Team::class)) {
|
||||||
|
refreshSession();
|
||||||
|
}
|
||||||
|
$this->emit('success', 'Settings saved.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sendTestNotification()
|
||||||
|
{
|
||||||
|
$this->team->notify(new Test());
|
||||||
|
$this->emit('success', 'Test notification sent.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ class Change extends Component
|
|||||||
if ($this->private_key->isEmpty()) {
|
if ($this->private_key->isEmpty()) {
|
||||||
$this->private_key->delete();
|
$this->private_key->delete();
|
||||||
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||||
return redirect()->route('private-key.all');
|
return redirect()->route('security.private-key.index');
|
||||||
}
|
}
|
||||||
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -42,8 +42,13 @@ class Heading extends Component
|
|||||||
["docker rm -f {$this->database->uuid}"],
|
["docker rm -f {$this->database->uuid}"],
|
||||||
$this->database->destination->server
|
$this->database->destination->server
|
||||||
);
|
);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
stopPostgresProxy($this->database);
|
||||||
|
$this->database->is_public = false;
|
||||||
|
}
|
||||||
$this->database->status = 'stopped';
|
$this->database->status = 'stopped';
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
|
$this->emit('refresh');
|
||||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class General extends Component
|
|||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public string $new_filename;
|
public string $new_filename;
|
||||||
public string $new_content;
|
public string $new_content;
|
||||||
|
public string $db_url;
|
||||||
|
|
||||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ class General extends Component
|
|||||||
'database.init_scripts' => 'nullable',
|
'database.init_scripts' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
|
'database.is_public' => 'nullable|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'database.name' => 'Name',
|
'database.name' => 'Name',
|
||||||
@@ -38,8 +41,44 @@ class General extends Component
|
|||||||
'database.init_scripts' => 'Init Scripts',
|
'database.init_scripts' => 'Init Scripts',
|
||||||
'database.image' => 'Image',
|
'database.image' => 'Image',
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
|
'database.is_public' => 'Is Public',
|
||||||
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->getDbUrl();
|
||||||
|
}
|
||||||
|
public function getDbUrl() {
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
|
||||||
|
} else {
|
||||||
|
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->emit('success', 'Starting TCP proxy...');
|
||||||
|
startPostgresProxy($this->database);
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
stopPostgresProxy($this->database);
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->getDbUrl();
|
||||||
|
$this->database->save();
|
||||||
|
} catch(Exception $e) {
|
||||||
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
return general_error_handler(err: $e, that: $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
public function save_init_script($script)
|
public function save_init_script($script)
|
||||||
{
|
{
|
||||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class PublicGitRepository extends Component
|
|||||||
'publish_directory' => 'publish directory',
|
'publish_directory' => 'publish directory',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
private object $repository_url_parsed;
|
||||||
private GithubApp|GitlabApp $git_source;
|
private GithubApp|GitlabApp|null $git_source = null;
|
||||||
private string $git_host;
|
private string $git_host;
|
||||||
private string $git_repository;
|
private string $git_repository;
|
||||||
|
|
||||||
@@ -67,18 +67,17 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
public function load_branch()
|
public function load_branch()
|
||||||
{
|
{
|
||||||
$this->branch_found = false;
|
try {
|
||||||
|
$this->branch_found = false;
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'repository_url' => 'required|url'
|
'repository_url' => 'required|url'
|
||||||
]);
|
]);
|
||||||
$this->get_git_source();
|
$this->get_git_source();
|
||||||
try {
|
$this->get_branch();
|
||||||
$this->get_branch();
|
$this->selected_branch = $this->git_branch;
|
||||||
$this->selected_branch = $this->git_branch;
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler(err: $e, that: $this);
|
return general_error_handler(err: $e, that: $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||||
try {
|
try {
|
||||||
$this->git_branch = 'master';
|
$this->git_branch = 'master';
|
||||||
@@ -103,6 +102,9 @@ class PublicGitRepository extends Component
|
|||||||
} elseif ($this->git_host == 'bitbucket.org') {
|
} elseif ($this->git_host == 'bitbucket.org') {
|
||||||
// Not supported yet
|
// Not supported yet
|
||||||
}
|
}
|
||||||
|
if (is_null($this->git_source)) {
|
||||||
|
throw new \Exception('Git source not found. What?!');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_branch()
|
private function get_branch()
|
||||||
|
|||||||
@@ -22,34 +22,52 @@ class Select extends Component
|
|||||||
public Collection|array $swarmDockers = [];
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|
||||||
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'server',
|
'server',
|
||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
if (isDev()) {
|
||||||
|
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_type(string $type)
|
// public function addExistingPostgresql()
|
||||||
|
// {
|
||||||
|
// try {
|
||||||
|
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
|
||||||
|
// $this->emit('success', 'Successfully connected to the database.');
|
||||||
|
// } catch (\Exception $e) {
|
||||||
|
// return general_error_handler($e, $this);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
public function setType(string $type)
|
||||||
{
|
{
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
|
if ($type === "existing-postgresql") {
|
||||||
|
$this->current_step = $type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (count($this->servers) === 1) {
|
if (count($this->servers) === 1) {
|
||||||
$server = $this->servers->first();
|
$server = $this->servers->first();
|
||||||
$this->set_server($server);
|
$this->setServer($server);
|
||||||
if (count($server->destinations()) === 1) {
|
if (count($server->destinations()) === 1) {
|
||||||
$this->set_destination($server->destinations()->first()->uuid);
|
$this->setDestination($server->destinations()->first()->uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_null($this->server)) {
|
if (!is_null($this->server)) {
|
||||||
$foundServer = $this->servers->where('id', $this->server)->first();
|
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||||
if ($foundServer) {
|
if ($foundServer) {
|
||||||
return $this->set_server($foundServer);
|
return $this->setServer($foundServer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->current_step = 'servers';
|
$this->current_step = 'servers';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_server(Server $server)
|
public function setServer(Server $server)
|
||||||
{
|
{
|
||||||
$this->server_id = $server->id;
|
$this->server_id = $server->id;
|
||||||
$this->standaloneDockers = $server->standaloneDockers;
|
$this->standaloneDockers = $server->standaloneDockers;
|
||||||
@@ -57,7 +75,7 @@ class Select extends Component
|
|||||||
$this->current_step = 'destinations';
|
$this->current_step = 'destinations';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set_destination(string $destination_uuid)
|
public function setDestination(string $destination_uuid)
|
||||||
{
|
{
|
||||||
$this->destination_uuid = $destination_uuid;
|
$this->destination_uuid = $destination_uuid;
|
||||||
redirect()->route('project.resources.new', [
|
redirect()->route('project.resources.new', [
|
||||||
|
|||||||
@@ -5,21 +5,84 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
use Str;
|
||||||
|
|
||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
|
public bool $showPreview = false;
|
||||||
public string|null $modalId = null;
|
public string|null $modalId = null;
|
||||||
|
public ?string $variables = null;
|
||||||
|
public ?string $variablesPreview = null;
|
||||||
|
public string $view = 'normal';
|
||||||
protected $listeners = ['refreshEnvs', 'submit'];
|
protected $listeners = ['refreshEnvs', 'submit'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$resourceClass = get_class($this->resource);
|
||||||
|
$resourceWithPreviews = ['App\Models\Application'];
|
||||||
|
$simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile'));
|
||||||
|
if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
|
||||||
|
$this->showPreview = true;
|
||||||
|
}
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
|
$this->getDevView();
|
||||||
|
}
|
||||||
|
public function getDevView()
|
||||||
|
{
|
||||||
|
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||||
|
return "$item->key=$item->value";
|
||||||
|
})->sort()->join('
|
||||||
|
');
|
||||||
|
if ($this->showPreview) {
|
||||||
|
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||||
|
return "$item->key=$item->value";
|
||||||
|
})->sort()->join('
|
||||||
|
');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function switch()
|
||||||
|
{
|
||||||
|
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
|
||||||
|
}
|
||||||
|
public function saveVariables($isPreview)
|
||||||
|
{
|
||||||
|
if ($isPreview) {
|
||||||
|
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||||
|
$existingVariables = $this->resource->environment_variables_preview();
|
||||||
|
$this->resource->environment_variables_preview()->delete();
|
||||||
|
} else {
|
||||||
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
|
$existingVariables = $this->resource->environment_variables();
|
||||||
|
$this->resource->environment_variables()->delete();
|
||||||
|
}
|
||||||
|
foreach ($variables as $key => $variable) {
|
||||||
|
$found = $existingVariables->where('key', $key)->first();
|
||||||
|
if ($found) {
|
||||||
|
$found->value = $variable;
|
||||||
|
$found->save();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$environment = new EnvironmentVariable();
|
||||||
|
$environment->key = $key;
|
||||||
|
$environment->value = $variable;
|
||||||
|
$environment->is_build_time = false;
|
||||||
|
$environment->is_preview = $isPreview ? true : false;
|
||||||
|
if ($this->resource->type() === 'application') {
|
||||||
|
$environment->application_id = $this->resource->id;
|
||||||
|
}
|
||||||
|
if ($this->resource->type() === 'standalone-postgresql') {
|
||||||
|
$environment->standalone_postgresql_id = $this->resource->id;
|
||||||
|
}
|
||||||
|
$environment->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->refreshEnvs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refreshEnvs()
|
public function refreshEnvs()
|
||||||
{
|
{
|
||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
|
$this->getDevView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit($data)
|
public function submit($data)
|
||||||
@@ -43,7 +106,7 @@ class All extends Component
|
|||||||
$environment->standalone_postgresql_id = $this->resource->id;
|
$environment->standalone_postgresql_id = $this->resource->id;
|
||||||
}
|
}
|
||||||
$environment->save();
|
$environment->save();
|
||||||
$this->resource->refresh();
|
$this->refreshEnvs();
|
||||||
$this->emit('success', 'Environment variable added successfully.');
|
$this->emit('success', 'Environment variable added successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler(err: $e, that: $this);
|
return general_error_handler(err: $e, that: $this);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\New;
|
namespace App\Http\Livewire\Server\New;
|
||||||
|
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -67,6 +69,11 @@ class ByIp extends Component
|
|||||||
'port' => $this->port,
|
'port' => $this->port,
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
'private_key_id' => $this->private_key_id,
|
'private_key_id' => $this->private_key_id,
|
||||||
|
'proxy' => [
|
||||||
|
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||||
|
"status" => ProxyStatus::EXITED->value,
|
||||||
|
]
|
||||||
|
|
||||||
]);
|
]);
|
||||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Proxy extends Component
|
|||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public ProxyTypes $selectedProxy = ProxyTypes::TRAEFIK_V2;
|
public ?string $selectedProxy = null;
|
||||||
public $proxy_settings = null;
|
public $proxy_settings = null;
|
||||||
public string|null $redirect_url = null;
|
public string|null $redirect_url = null;
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ class Proxy extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->redirect_url = $this->server->proxy->redirect_url;
|
$this->redirect_url = $this->server->proxy->redirect_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +36,12 @@ class Proxy extends Component
|
|||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function select_proxy(ProxyTypes $proxy_type)
|
public function select_proxy($proxy_type)
|
||||||
{
|
{
|
||||||
$this->server->proxy->type = $proxy_type;
|
$this->server->proxy->type = $proxy_type;
|
||||||
$this->server->proxy->status = 'exited';
|
$this->server->proxy->status = 'exited';
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
$this->selectedProxy = $this->server->proxy->type;
|
||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ class Email extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'settings.resend_api_key' => 'required'
|
'settings.resend_api_key' => 'required'
|
||||||
]);
|
]);
|
||||||
$this->settings->smtp_enabled = false;
|
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->emit('success', 'Settings saved successfully.');
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -68,9 +67,18 @@ class Email extends Component
|
|||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function instantSaveResend() {
|
||||||
|
try {
|
||||||
|
$this->settings->smtp_enabled = false;
|
||||||
|
$this->submitResend();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return general_error_handler($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->settings->resend_enabled = false;
|
||||||
$this->submit();
|
$this->submit();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return general_error_handler($e, $this);
|
return general_error_handler($e, $this);
|
||||||
@@ -89,7 +97,6 @@ class Email extends Component
|
|||||||
'settings.smtp_password' => 'nullable',
|
'settings.smtp_password' => 'nullable',
|
||||||
'settings.smtp_timeout' => 'nullable',
|
'settings.smtp_timeout' => 'nullable',
|
||||||
]);
|
]);
|
||||||
$this->settings->resend_enabled = false;
|
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->emit('success', 'Settings saved successfully.');
|
$this->emit('success', 'Settings saved successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -32,16 +32,19 @@ class Create extends Component
|
|||||||
"custom_port" => 'required|int',
|
"custom_port" => 'required|int',
|
||||||
"is_system_wide" => 'required|bool',
|
"is_system_wide" => 'required|bool',
|
||||||
]);
|
]);
|
||||||
$github_app = GithubApp::create([
|
$payload = [
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'organization' => $this->organization,
|
'organization' => $this->organization,
|
||||||
'api_url' => $this->api_url,
|
'api_url' => $this->api_url,
|
||||||
'html_url' => $this->html_url,
|
'html_url' => $this->html_url,
|
||||||
'custom_user' => $this->custom_user,
|
'custom_user' => $this->custom_user,
|
||||||
'custom_port' => $this->custom_port,
|
'custom_port' => $this->custom_port,
|
||||||
'is_system_wide' => $this->is_system_wide,
|
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
];
|
||||||
|
if (isCloud()) {
|
||||||
|
$payload['is_system_wide'] = $this->is_system_wide;
|
||||||
|
}
|
||||||
|
$github_app = GithubApp::create($payload);
|
||||||
if (session('from')) {
|
if (session('from')) {
|
||||||
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class PricingPlans extends Component
|
|||||||
'tax_id_collection' => [
|
'tax_id_collection' => [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
],
|
],
|
||||||
|
'automatic_tax' => [
|
||||||
|
'enabled' => true,
|
||||||
|
],
|
||||||
'mode' => 'subscription',
|
'mode' => 'subscription',
|
||||||
'success_url' => route('dashboard', ['success' => true]),
|
'success_url' => route('dashboard', ['success' => true]),
|
||||||
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
'cancel_url' => route('subscription.index', ['cancelled' => true]),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SwitchTeam extends Component
|
|||||||
if (!$team_to_switch_to) {
|
if (!$team_to_switch_to) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session(['currentTeam' => $team_to_switch_to]);
|
refreshSession($team_to_switch_to);
|
||||||
return redirect(request()->header('Referer'));
|
return redirect(request()->header('Referer'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ class CheckForcePasswordReset
|
|||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if (auth()->user()) {
|
if (auth()->user()) {
|
||||||
|
if ($request->path() === 'auth/link') {
|
||||||
|
auth()->logout();
|
||||||
|
request()->session()->invalidate();
|
||||||
|
request()->session()->regenerateToken();
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
$force_password_reset = auth()->user()->force_password_reset;
|
$force_password_reset = auth()->user()->force_password_reset;
|
||||||
if ($force_password_reset) {
|
if ($force_password_reset) {
|
||||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
||||||
|
|||||||
@@ -66,12 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
private $log_model;
|
private $log_model;
|
||||||
private Collection $saved_outputs;
|
private Collection $saved_outputs;
|
||||||
|
|
||||||
public function middleware(): array
|
public $tries = 1;
|
||||||
{
|
|
||||||
return [
|
|
||||||
(new WithoutOverlapping("dockerimagejobs"))->shared(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
ray()->clearScreen();
|
ray()->clearScreen();
|
||||||
@@ -181,8 +176,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->build_image_name = "{$this->application->git_repository}:build";
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:latest";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
@@ -206,8 +201,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
$tag = $tag->substr(0, 128);
|
$tag = $tag->substr(0, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->build_image_name = "{$this->application->git_repository}:{$tag}-build";
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:{$tag}";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
|
|
||||||
if (!$this->force_rebuild) {
|
if (!$this->force_rebuild) {
|
||||||
@@ -242,7 +237,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
private function health_check()
|
private function health_check()
|
||||||
{
|
{
|
||||||
ray('New container name: ',$this->container_name);
|
ray('New container name: ', $this->container_name);
|
||||||
if ($this->container_name) {
|
if ($this->container_name) {
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
@@ -264,7 +259,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
);
|
);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'New application version health check status: {$this->saved_outputs->get('health_check')}'"
|
"echo 'New version health check status: {$this->saved_outputs->get('health_check')}'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
@@ -282,8 +277,8 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
private function deploy_pull_request()
|
private function deploy_pull_request()
|
||||||
{
|
{
|
||||||
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
|
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||||
$this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}";
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
||||||
@@ -304,12 +299,19 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
|
|
||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
|
$pull = "--pull=always";
|
||||||
|
if (isDev()) {
|
||||||
|
$pull = "--pull=never";
|
||||||
|
}
|
||||||
|
$helperImage = config('coolify.helper_image');
|
||||||
|
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-helper).'",
|
"echo -n 'Pulling helper image from $helperImage.'",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-helper",
|
$runCommand,
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -494,7 +496,7 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network => [
|
$this->destination->network => [
|
||||||
'external' => false,
|
'external' => true,
|
||||||
'name' => $this->destination->network,
|
'name' => $this->destination->network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
@@ -654,12 +656,12 @@ class ApplicationDeploymentJob implements ShouldQueue
|
|||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo -n 'Building docker image.'",
|
"echo -n 'Building docker image for your application.'",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($this->application->settings->is_static) {
|
if ($this->application->settings->is_static) {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
@@ -692,12 +694,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
$this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -706,7 +708,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
{
|
{
|
||||||
if ($this->currently_running_container_name) {
|
if ($this->currently_running_container_name) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Removing old application version.'"],
|
["echo -n 'Removing old version of your application.'"],
|
||||||
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,31 +24,27 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public Team|null $team = null;
|
public ?Team $team = null;
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public ScheduledDatabaseBackup|null $backup;
|
public ScheduledDatabaseBackup $backup;
|
||||||
public string $database_type;
|
|
||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public string $database_status;
|
|
||||||
|
|
||||||
public string|null $container_name = null;
|
public ?string $container_name = null;
|
||||||
public ScheduledDatabaseBackupExecution|null $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
public string $backup_status;
|
public string $backup_status;
|
||||||
public string|null $backup_location = null;
|
public ?string $backup_location = null;
|
||||||
public string $backup_dir;
|
public string $backup_dir;
|
||||||
public string $backup_file;
|
public string $backup_file;
|
||||||
public int $size = 0;
|
public int $size = 0;
|
||||||
public string|null $backup_output = null;
|
public ?string $backup_output = null;
|
||||||
public S3Storage|null $s3 = null;
|
public ?S3Storage $s3 = null;
|
||||||
|
|
||||||
public function __construct($backup)
|
public function __construct($backup)
|
||||||
{
|
{
|
||||||
$this->backup = $backup;
|
$this->backup = $backup;
|
||||||
$this->team = Team::find($backup->team_id);
|
$this->team = Team::find($backup->team_id);
|
||||||
$this->database = $this->backup->database;
|
$this->database = data_get($this->backup, 'database');
|
||||||
$this->database_type = $this->database->type();
|
|
||||||
$this->server = $this->database->destination->server;
|
$this->server = $this->database->destination->server;
|
||||||
$this->database_status = $this->database->status;
|
|
||||||
$this->s3 = $this->backup->s3;
|
$this->s3 = $this->backup->s3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +61,7 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database_status !== 'running') {
|
if (data_get($this->database, 'status') !== 'running') {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,14 +73,14 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
$ip = Str::slug($this->server->ip);
|
$ip = Str::slug($this->server->ip);
|
||||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||||
}
|
}
|
||||||
$this->backup_file = "/dumpall-" . Carbon::now()->timestamp . ".sql";
|
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
|
||||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||||
|
|
||||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||||
'filename' => $this->backup_location,
|
'filename' => $this->backup_location,
|
||||||
'scheduled_database_backup_id' => $this->backup->id,
|
'scheduled_database_backup_id' => $this->backup->id,
|
||||||
]);
|
]);
|
||||||
if ($this->database_type === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$this->backup_standalone_postgresql();
|
$this->backup_standalone_postgresql();
|
||||||
}
|
}
|
||||||
$this->calculate_size();
|
$this->calculate_size();
|
||||||
@@ -99,7 +95,6 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
send_internal_notification('DatabaseBackupJob failed with: ' . $th->getMessage());
|
send_internal_notification('DatabaseBackupJob failed with: ' . $th->getMessage());
|
||||||
throw $th;
|
throw $th;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function backup_standalone_postgresql(): void
|
private function backup_standalone_postgresql(): void
|
||||||
@@ -107,7 +102,7 @@ class DatabaseBackupJob implements ShouldQueue
|
|||||||
try {
|
try {
|
||||||
ray($this->backup_dir);
|
ray($this->backup_dir);
|
||||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dumpall -U {$this->database->postgres_user} > $this->backup_location";
|
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
|
||||||
|
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -9,7 +10,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldQueue
|
class DockerCleanupJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,11 @@ class DockerCleanupJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
$queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
|
||||||
|
if ($queue->count() > 0) {
|
||||||
|
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
ray()->showQueries()->color('orange');
|
ray()->showQueries()->color('orange');
|
||||||
$servers = Server::all();
|
$servers = Server::all();
|
||||||
|
|||||||
75
app/Jobs/SendMessageToTelegramJob.php
Normal file
75
app/Jobs/SendMessageToTelegramJob.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
class SendMessageToTelegramJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*/
|
||||||
|
public int $maxExceptions = 3;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public string $text,
|
||||||
|
public array $buttons,
|
||||||
|
public string $token,
|
||||||
|
public string $chatId,
|
||||||
|
public ?string $topicId = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$url = 'https://api.telegram.org/bot' . $this->token . '/sendMessage';
|
||||||
|
$inlineButtons = [];
|
||||||
|
if (!empty($this->buttons)) {
|
||||||
|
foreach ($this->buttons as $button) {
|
||||||
|
$buttonUrl = data_get($button, 'url');
|
||||||
|
if ($buttonUrl && Str::contains($buttonUrl, 'http://localhost')) {
|
||||||
|
$buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl);
|
||||||
|
}
|
||||||
|
$inlineButtons[] = [
|
||||||
|
'text' => $button['text'],
|
||||||
|
'url' => $buttonUrl,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$payload = [
|
||||||
|
'parse_mode' => 'markdown',
|
||||||
|
'reply_markup' => json_encode([
|
||||||
|
'inline_keyboard' => [
|
||||||
|
[...$inlineButtons],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
'chat_id' => $this->chatId,
|
||||||
|
'text' => $this->text,
|
||||||
|
];
|
||||||
|
if ($this->topicId) {
|
||||||
|
$payload['message_thread_id'] = $this->topicId;
|
||||||
|
}
|
||||||
|
$response = Http::post($url, $payload);
|
||||||
|
if ($response->failed()) {
|
||||||
|
throw new \Exception('Telegram notification failed with ' . $response->status() . ' status code.' . $response->body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,7 +105,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function environment_variables(): HasMany
|
public function environment_variables(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false);
|
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function runtime_environment_variables(): HasMany
|
public function runtime_environment_variables(): HasMany
|
||||||
@@ -127,7 +127,7 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
public function environment_variables_preview(): HasMany
|
public function environment_variables_preview(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true);
|
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function runtime_environment_variables_preview(): HasMany
|
public function runtime_environment_variables_preview(): HasMany
|
||||||
|
|||||||
@@ -4,15 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
class ApplicationPreview extends BaseModel
|
class ApplicationPreview extends BaseModel
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $guarded = [];
|
||||||
'uuid',
|
|
||||||
'pull_request_id',
|
|
||||||
'pull_request_html_url',
|
|
||||||
'pull_request_issue_comment_id',
|
|
||||||
'fqdn',
|
|
||||||
'status',
|
|
||||||
'application_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,13 +20,16 @@ class EnvironmentVariable extends Model
|
|||||||
{
|
{
|
||||||
static::created(function ($environment_variable) {
|
static::created(function ($environment_variable) {
|
||||||
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
||||||
ModelsEnvironmentVariable::create([
|
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview',true)->first();
|
||||||
'key' => $environment_variable->key,
|
if (!$found) {
|
||||||
'value' => $environment_variable->value,
|
ModelsEnvironmentVariable::create([
|
||||||
'is_build_time' => $environment_variable->is_build_time,
|
'key' => $environment_variable->key,
|
||||||
'application_id' => $environment_variable->application_id,
|
'value' => $environment_variable->value,
|
||||||
'is_preview' => true,
|
'is_build_time' => $environment_variable->is_build_time,
|
||||||
]);
|
'application_id' => $environment_variable->application_id,
|
||||||
|
'is_preview' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ class StandaloneDocker extends BaseModel
|
|||||||
|
|
||||||
public function attachedTo()
|
public function attachedTo()
|
||||||
{
|
{
|
||||||
return $this->applications->count() > 0 || $this->databases->count() > 0;
|
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ class StandalonePostgresql extends BaseModel
|
|||||||
'is_readonly' => true
|
'is_readonly' => true
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
static::deleted(function ($database) {
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function portsMappings(): Attribute
|
public function portsMappings(): Attribute
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
return data_get($this, 'discord_webhook_url', null);
|
return data_get($this, 'discord_webhook_url', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function routeNotificationForTelegram()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"token" => data_get($this, 'telegram_token', null),
|
||||||
|
"chat_id" => data_get($this, 'telegram_chat_id', null),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function getRecepients($notification)
|
public function getRecepients($notification)
|
||||||
{
|
{
|
||||||
$recipients = data_get($notification, 'emails', null);
|
$recipients = data_get($notification, 'emails', null);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||||
|
use Cache;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@@ -94,7 +95,9 @@ class User extends Authenticatable implements SendsEmail
|
|||||||
|
|
||||||
public function currentTeam()
|
public function currentTeam()
|
||||||
{
|
{
|
||||||
return Team::find(session('currentTeam')->id);
|
return Cache::remember('team:' . auth()->user()->id, 3600, function() {
|
||||||
|
return Team::find(session('currentTeam')->id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function otherTeams()
|
public function otherTeams()
|
||||||
|
|||||||
@@ -16,17 +16,18 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public Application $application;
|
public Application $application;
|
||||||
public string $deployment_uuid;
|
public string $deployment_uuid;
|
||||||
public ApplicationPreview|null $preview;
|
public ?ApplicationPreview $preview = null;
|
||||||
|
|
||||||
public string $application_name;
|
public string $application_name;
|
||||||
public string|null $deployment_url = null;
|
public ?string $deployment_url = null;
|
||||||
public string $project_uuid;
|
public string $project_uuid;
|
||||||
public string $environment_name;
|
public string $environment_name;
|
||||||
public string|null $fqdn;
|
public ?string $fqdn = null;
|
||||||
|
|
||||||
public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview)
|
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)
|
||||||
{
|
{
|
||||||
$this->application = $application;
|
$this->application = $application;
|
||||||
$this->deployment_uuid = $deployment_uuid;
|
$this->deployment_uuid = $deployment_uuid;
|
||||||
@@ -43,19 +44,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'deployments');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -67,9 +56,8 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
$mail->subject('❌ Deployment failed of ' . $this->application_name . '.');
|
$mail->subject('❌ Deployment failed of ' . $this->application_name . '.');
|
||||||
} else {
|
} else {
|
||||||
$fqdn = $this->preview->fqdn;
|
$fqdn = $this->preview->fqdn;
|
||||||
$mail->subject('❌ Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' deployment failed.');
|
$mail->subject('❌ Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$mail->view('emails.application-deployment-failed', [
|
$mail->view('emails.application-deployment-failed', [
|
||||||
'name' => $this->application_name,
|
'name' => $this->application_name,
|
||||||
'fqdn' => $fqdn,
|
'fqdn' => $fqdn,
|
||||||
@@ -90,4 +78,19 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
}
|
}
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||||
|
} else {
|
||||||
|
$message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
"text" => "View Deployment Logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public Application $application;
|
public Application $application;
|
||||||
public string $deployment_uuid;
|
public string $deployment_uuid;
|
||||||
public ApplicationPreview|null $preview = null;
|
public ApplicationPreview|null $preview = null;
|
||||||
@@ -43,19 +44,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'deployments');
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -99,4 +88,34 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
}
|
}
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
if ($this->preview) {
|
||||||
|
$message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
|
||||||
|
if ($this->preview->fqdn) {
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Open Application",
|
||||||
|
"url" => $this->preview->fqdn
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message = '✅ New version successfully deployed of ' . $this->application_name . '';
|
||||||
|
if ($this->fqdn) {
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Open Application",
|
||||||
|
"url" => $this->fqdn
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Deployment logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
...$buttons
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public $application;
|
public $application;
|
||||||
|
|
||||||
public string $application_name;
|
public string $application_name;
|
||||||
@@ -37,19 +38,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'status_changes');
|
||||||
$isEmailEnabled = data_get($notifiable, 'smtp_enabled');
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -70,7 +59,20 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
$message = '⛔ ' . $this->application_name . ' has been stopped.
|
$message = '⛔ ' . $this->application_name . ' has been stopped.
|
||||||
|
|
||||||
';
|
';
|
||||||
$message .= '[Application URL](' . $this->application_url . ')';
|
$message .= '[Open Application in Coolify](' . $this->application_url . ')';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = '⛔ ' . $this->application_name . ' has been stopped.';
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
"buttons" => [
|
||||||
|
[
|
||||||
|
"text" => "Open Application in Coolify",
|
||||||
|
"url" => $this->application_url
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use Illuminate\Support\Facades\Mail;
|
|||||||
|
|
||||||
class EmailChannel
|
class EmailChannel
|
||||||
{
|
{
|
||||||
private bool $isResend = false;
|
|
||||||
public function send(SendsEmail $notifiable, Notification $notification): void
|
public function send(SendsEmail $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$this->bootConfigs($notifiable);
|
$this->bootConfigs($notifiable);
|
||||||
@@ -20,35 +19,14 @@ class EmailChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
if ($this->isResend) {
|
Mail::send(
|
||||||
foreach ($recepients as $receipient) {
|
[],
|
||||||
Mail::send(
|
[],
|
||||||
[],
|
fn (Message $message) => $message
|
||||||
[],
|
->to($recepients)
|
||||||
fn (Message $message) => $message
|
->subject($mailMessage->subject)
|
||||||
->from(
|
->html((string)$mailMessage->render())
|
||||||
data_get($notifiable, 'smtp_from_address'),
|
);
|
||||||
data_get($notifiable, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->to($receipient)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Mail::send(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
fn (Message $message) => $message
|
|
||||||
->from(
|
|
||||||
data_get($notifiable, 'smtp_from_address'),
|
|
||||||
data_get($notifiable, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->bcc($recepients)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function bootConfigs($notifiable): void
|
private function bootConfigs($notifiable): void
|
||||||
@@ -58,13 +36,11 @@ class EmailChannel
|
|||||||
if (!$type) {
|
if (!$type) {
|
||||||
throw new Exception('No email settings found.');
|
throw new Exception('No email settings found.');
|
||||||
}
|
}
|
||||||
if ($type === 'resend') {
|
|
||||||
$this->isResend = true;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address'));
|
||||||
|
config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name'));
|
||||||
if (data_get($notifiable, 'resend_enabled')) {
|
if (data_get($notifiable, 'resend_enabled')) {
|
||||||
$this->isResend = true;
|
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
config()->set('resend.api_key', data_get($notifiable, 'resend_api_key'));
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/Notifications/Channels/SendsTelegram.php
Normal file
9
app/Notifications/Channels/SendsTelegram.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
interface SendsTelegram
|
||||||
|
{
|
||||||
|
public function routeNotificationForTelegram();
|
||||||
|
|
||||||
|
}
|
||||||
39
app/Notifications/Channels/TelegramChannel.php
Normal file
39
app/Notifications/Channels/TelegramChannel.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
|
use App\Jobs\SendMessageToTelegramJob;
|
||||||
|
|
||||||
|
class TelegramChannel
|
||||||
|
{
|
||||||
|
public function send($notifiable, $notification): void
|
||||||
|
{
|
||||||
|
$data = $notification->toTelegram($notifiable);
|
||||||
|
$telegramData = $notifiable->routeNotificationForTelegram();
|
||||||
|
$message = data_get($data, 'message');
|
||||||
|
$buttons = data_get($data, 'buttons', []);
|
||||||
|
$telegramToken = data_get($telegramData, 'token');
|
||||||
|
$chatId = data_get($telegramData, 'chat_id');
|
||||||
|
$topicId = null;
|
||||||
|
$topicsInstance = get_class($notification);
|
||||||
|
|
||||||
|
switch ($topicsInstance) {
|
||||||
|
case 'App\Notifications\StatusChange':
|
||||||
|
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
|
||||||
|
break;
|
||||||
|
case 'App\Notifications\Test':
|
||||||
|
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
|
||||||
|
break;
|
||||||
|
case 'App\Notifications\Deployment':
|
||||||
|
$topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
|
||||||
|
break;
|
||||||
|
case 'App\Notifications\DatabaseBackup':
|
||||||
|
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!$telegramToken || !$chatId || !$message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ use Log;
|
|||||||
|
|
||||||
class TransactionalEmailChannel
|
class TransactionalEmailChannel
|
||||||
{
|
{
|
||||||
private bool $isResend = false;
|
|
||||||
public function send(User $notifiable, Notification $notification): void
|
public function send(User $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
@@ -26,33 +25,14 @@ class TransactionalEmailChannel
|
|||||||
}
|
}
|
||||||
$this->bootConfigs();
|
$this->bootConfigs();
|
||||||
$mailMessage = $notification->toMail($notifiable);
|
$mailMessage = $notification->toMail($notifiable);
|
||||||
if ($this->isResend) {
|
Mail::send(
|
||||||
Mail::send(
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
fn (Message $message) => $message
|
||||||
fn (Message $message) => $message
|
->to($email)
|
||||||
->from(
|
->subject($mailMessage->subject)
|
||||||
data_get($settings, 'smtp_from_address'),
|
->html((string)$mailMessage->render())
|
||||||
data_get($settings, 'smtp_from_name'),
|
);
|
||||||
)
|
|
||||||
->to($email)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Mail::send(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
fn (Message $message) => $message
|
|
||||||
->from(
|
|
||||||
data_get($settings, 'smtp_from_address'),
|
|
||||||
data_get($settings, 'smtp_from_name'),
|
|
||||||
)
|
|
||||||
->bcc($email)
|
|
||||||
->subject($mailMessage->subject)
|
|
||||||
->html((string)$mailMessage->render())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function bootConfigs(): void
|
private function bootConfigs(): void
|
||||||
@@ -61,8 +41,5 @@ class TransactionalEmailChannel
|
|||||||
if (!$type) {
|
if (!$type) {
|
||||||
throw new Exception('No email settings found.');
|
throw new Exception('No email settings found.');
|
||||||
}
|
}
|
||||||
if ($type === 'resend') {
|
|
||||||
$this->isResend = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,42 +14,42 @@ class BackupFailed extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public string $message = 'Backup FAILED';
|
public $tries = 5;
|
||||||
|
public string $name;
|
||||||
|
public string $frequency;
|
||||||
|
|
||||||
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
|
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
|
||||||
{
|
{
|
||||||
$this->message = "❌ Database backup for {$database->name} with frequency of $backup->frequency was FAILED.\n\nReason: $output";
|
$this->name = $database->name;
|
||||||
|
$this->frequency = $backup->frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'database_backups');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
ray($channels);
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("❌ Backup FAILED for {$this->database->name}");
|
$mail->subject("❌ [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
|
||||||
$mail->line($this->message);
|
$mail->view('emails.backup-failed', [
|
||||||
|
'name' => $this->name,
|
||||||
|
'frequency' => $this->frequency,
|
||||||
|
'output' => $this->output,
|
||||||
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,41 +14,41 @@ class BackupSuccess extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public string $message = 'Backup Success';
|
public $tries = 5;
|
||||||
|
public string $name;
|
||||||
|
public string $frequency;
|
||||||
|
|
||||||
public function __construct(ScheduledDatabaseBackup $backup, public $database)
|
public function __construct(ScheduledDatabaseBackup $backup, public $database)
|
||||||
{
|
{
|
||||||
$this->message = "✅ Database backup for {$database->name} with frequency of $backup->frequency was successful.";
|
$this->name = $database->name;
|
||||||
|
$this->frequency = $backup->frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'database_backups');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups');
|
|
||||||
|
|
||||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("✅ Backup success for {$this->database->name}");
|
$mail->subject("✅ Backup successfully done for {$this->database->name}");
|
||||||
$mail->line($this->message);
|
$mail->view('emails.backup-success', [
|
||||||
|
'name' => $this->name,
|
||||||
|
'frequency' => $this->frequency,
|
||||||
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||||
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
$message = "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||||
|
return [
|
||||||
|
"message" => $message,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Notifications\Internal;
|
namespace App\Notifications\Internal;
|
||||||
|
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
@@ -11,17 +12,24 @@ class GeneralNotification extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public function __construct(public string $message)
|
public function __construct(public string $message)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels[] = DiscordChannel::class;
|
return [TelegramChannel::class, DiscordChannel::class];
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toDiscord(): string
|
public function toDiscord(): string
|
||||||
{
|
{
|
||||||
return $this->message;
|
return $this->message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => $this->message,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class NotReachable extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public function __construct(public Server $server)
|
public function __construct(public Server $server)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -22,19 +23,7 @@ class NotReachable extends Notification implements ShouldQueue
|
|||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'status_changes');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
$isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes');
|
|
||||||
$isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes');
|
|
||||||
|
|
||||||
// if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
|
||||||
// $channels[] = EmailChannel::class;
|
|
||||||
// }
|
|
||||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
@@ -55,4 +44,10 @@ class NotReachable extends Notification implements ShouldQueue
|
|||||||
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
|
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.'
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Notifications;
|
|||||||
|
|
||||||
use App\Notifications\Channels\DiscordChannel;
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
use App\Notifications\Channels\EmailChannel;
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
@@ -13,30 +14,20 @@ class Test extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public function __construct(public string|null $emails = null)
|
public function __construct(public string|null $emails = null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
$channels = [];
|
return setNotificationChannels($notifiable, 'test');
|
||||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
|
||||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
|
||||||
|
|
||||||
if ($isDiscordEnabled && empty($this->emails)) {
|
|
||||||
$channels[] = DiscordChannel::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isEmailEnabled && !empty($this->emails)) {
|
|
||||||
$channels[] = EmailChannel::class;
|
|
||||||
}
|
|
||||||
return $channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject("Coolify Test Notification");
|
$mail->subject("Test Email");
|
||||||
$mail->view('emails.test');
|
$mail->view('emails.test');
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
@@ -48,4 +39,16 @@ class Test extends Notification implements ShouldQueue
|
|||||||
$message .= '[Go to your dashboard](' . base_url() . ')';
|
$message .= '[Go to your dashboard](' . base_url() . ')';
|
||||||
return $message;
|
return $message;
|
||||||
}
|
}
|
||||||
|
public function toTelegram(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"message" => 'This is a test Telegram notification from Coolify.',
|
||||||
|
"buttons" => [
|
||||||
|
[
|
||||||
|
"text" => "Go to your dashboard",
|
||||||
|
"url" => base_url()
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,21 +15,25 @@ class InvitationLink extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public function via(): array
|
public function via(): array
|
||||||
{
|
{
|
||||||
return [TransactionalEmailChannel::class];
|
return [TransactionalEmailChannel::class];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toMail(User $user): MailMessage
|
public function __construct(public User $user)
|
||||||
{
|
{
|
||||||
$invitation = TeamInvitation::whereEmail($user->email)->first();
|
}
|
||||||
|
public function toMail(): MailMessage
|
||||||
|
{
|
||||||
|
$invitation = TeamInvitation::whereEmail($this->user->email)->first();
|
||||||
$invitation_team = Team::find($invitation->team->id);
|
$invitation_team = Team::find($invitation->team->id);
|
||||||
|
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject('Invitation for ' . $invitation_team->name);
|
$mail->subject('Invitation for ' . $invitation_team->name);
|
||||||
$mail->view('emails.invitation-link', [
|
$mail->view('emails.invitation-link', [
|
||||||
'team' => $invitation_team->name,
|
'team' => $invitation_team->name,
|
||||||
'email' => $user->email,
|
'email' => $this->user->email,
|
||||||
'invitation_link' => $invitation->link,
|
'invitation_link' => $invitation->link,
|
||||||
]);
|
]);
|
||||||
return $mail;
|
return $mail;
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ class ResetPassword extends Notification
|
|||||||
protected function buildMailMessage($url)
|
protected function buildMailMessage($url)
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->from(
|
|
||||||
data_get($this->settings, 'smtp_from_address'),
|
|
||||||
data_get($this->settings, 'smtp_from_name'),
|
|
||||||
);
|
|
||||||
$mail->subject('Reset Password');
|
$mail->subject('Reset Password');
|
||||||
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
||||||
return $mail;
|
return $mail;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Test extends Notification implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
public $tries = 5;
|
||||||
public function __construct(public string $emails)
|
public function __construct(public string $emails)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ class Test extends Notification implements ShouldQueue
|
|||||||
public function toMail(): MailMessage
|
public function toMail(): MailMessage
|
||||||
{
|
{
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->subject('Test Notification');
|
$mail->subject('Test Email');
|
||||||
$mail->view('emails.test');
|
$mail->view('emails.test');
|
||||||
return $mail;
|
return $mail;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand
|
|||||||
}
|
}
|
||||||
return StandalonePostgresql::create([
|
return StandalonePostgresql::create([
|
||||||
'name' => generate_database_name('postgresql'),
|
'name' => generate_database_name('postgresql'),
|
||||||
'postgres_password' => \Illuminate\Support\Str::password(),
|
'postgres_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||||
'environment_id' => $environment_id,
|
'environment_id' => $environment_id,
|
||||||
'destination_id' => $destination->id,
|
'destination_id' => $destination->id,
|
||||||
'destination_type' => $destination->getMorphClass(),
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
|
|
||||||
function generateApplicationContainerName(string $uuid, int $pull_request_id = 0)
|
function generateApplicationContainerName(string $uuid, int $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
$now = now()->format('YmdHis');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
return $uuid . '-pr-' . $pull_request_id . '-' . $now;
|
return $uuid . '-pr-' . $pull_request_id . '-' . $now;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
function get_proxy_path()
|
function get_proxy_path()
|
||||||
@@ -166,3 +167,75 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startPostgresProxy(StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
$containerName = "{$database->uuid}-proxy";
|
||||||
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
|
$nginxconf = <<<EOF
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
stream {
|
||||||
|
server {
|
||||||
|
listen $database->public_port;
|
||||||
|
proxy_pass $database->uuid:5432;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF;
|
||||||
|
$docker_compose = [
|
||||||
|
'version' => '3.8',
|
||||||
|
'services' => [
|
||||||
|
$containerName => [
|
||||||
|
'image' => "nginx:stable-alpine",
|
||||||
|
'container_name' => $containerName,
|
||||||
|
'restart' => RESTART_MODE,
|
||||||
|
'volumes' => [
|
||||||
|
"$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro",
|
||||||
|
],
|
||||||
|
'ports' => [
|
||||||
|
"$database->public_port:$database->public_port",
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network,
|
||||||
|
],
|
||||||
|
'healthcheck' => [
|
||||||
|
'test' => [
|
||||||
|
'CMD-SHELL',
|
||||||
|
'stat /etc/nginx/nginx.conf || exit 1',
|
||||||
|
],
|
||||||
|
'interval' => '5s',
|
||||||
|
'timeout' => '5s',
|
||||||
|
'retries' => 3,
|
||||||
|
'start_period' => '1s'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'networks' => [
|
||||||
|
$database->destination->network => [
|
||||||
|
'external' => true,
|
||||||
|
'name' => $database->destination->network,
|
||||||
|
'attachable' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||||
|
$nginxconf_base64 = base64_encode($nginxconf);
|
||||||
|
instant_remote_process([
|
||||||
|
"mkdir -p $configuration_dir",
|
||||||
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
|
"docker compose --project-directory {$configuration_dir} up -d >/dev/null",
|
||||||
|
|
||||||
|
|
||||||
|
], $database->destination->server);
|
||||||
|
}
|
||||||
|
function stopPostgresProxy(StandalonePostgresql $database)
|
||||||
|
{
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function generate_ssh_command(string $private_key_location, string $server_ip, s
|
|||||||
if ($isMux && config('coolify.mux_enabled')) {
|
if ($isMux && config('coolify.mux_enabled')) {
|
||||||
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
||||||
}
|
}
|
||||||
|
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||||
$ssh_command .= "-i {$private_key_location} "
|
$ssh_command .= "-i {$private_key_location} "
|
||||||
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
. '-o PasswordAuthentication=no '
|
. '-o PasswordAuthentication=no '
|
||||||
@@ -92,7 +93,18 @@ function generate_ssh_command(string $private_key_location, string $server_ip, s
|
|||||||
|
|
||||||
return $ssh_command;
|
return $ssh_command;
|
||||||
}
|
}
|
||||||
|
function instantCommand(string $command, $throwError = true) {
|
||||||
|
$process = Process::run($command);
|
||||||
|
$output = trim($process->output());
|
||||||
|
$exitCode = $process->exitCode();
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
if (!$throwError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new \RuntimeException($process->errorOutput(), $exitCode);
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
|
||||||
{
|
{
|
||||||
$command_string = implode("\n", $command);
|
$command_string = implode("\n", $command);
|
||||||
@@ -216,3 +228,29 @@ function check_server_connection(Server $server)
|
|||||||
$server->save();
|
$server->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkRequiredCommands(Server $server)
|
||||||
|
{
|
||||||
|
$commands = collect(["jq", "jc"]);
|
||||||
|
foreach ($commands as $command) {
|
||||||
|
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||||
|
if ($commandFound) {
|
||||||
|
ray($command . ' found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray('could not install ' . $command);
|
||||||
|
ray($e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||||
|
if ($commandFound) {
|
||||||
|
ray($command . ' found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ray('could not install ' . $command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
|
use App\Notifications\Channels\DiscordChannel;
|
||||||
|
use App\Notifications\Channels\EmailChannel;
|
||||||
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Internal\GeneralNotification;
|
use App\Notifications\Internal\GeneralNotification;
|
||||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||||
use Illuminate\Database\QueryException;
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -26,6 +28,10 @@ function database_configuration_dir(): string
|
|||||||
{
|
{
|
||||||
return '/data/coolify/databases';
|
return '/data/coolify/databases';
|
||||||
}
|
}
|
||||||
|
function database_proxy_dir($uuid): string
|
||||||
|
{
|
||||||
|
return "/data/coolify/databases/$uuid/proxy";
|
||||||
|
}
|
||||||
|
|
||||||
function backup_dir(): string
|
function backup_dir(): string
|
||||||
{
|
{
|
||||||
@@ -51,9 +57,15 @@ function showBoarding(): bool
|
|||||||
{
|
{
|
||||||
return currentTeam()->show_boarding ?? false;
|
return currentTeam()->show_boarding ?? false;
|
||||||
}
|
}
|
||||||
function refreshSession(): void
|
function refreshSession(?Team $team = null): void
|
||||||
{
|
{
|
||||||
$team = Team::find(currentTeam()->id);
|
if (!$team) {
|
||||||
|
$team = Team::find(currentTeam()->id);
|
||||||
|
}
|
||||||
|
Cache::forget('team:' . auth()->user()->id);
|
||||||
|
Cache::remember('team:' . auth()->user()->id, 3600, function() use ($team) {
|
||||||
|
return $team;
|
||||||
|
});
|
||||||
session(['currentTeam' => $team]);
|
session(['currentTeam' => $team]);
|
||||||
}
|
}
|
||||||
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
|
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
|
||||||
@@ -149,6 +161,8 @@ function set_transanctional_email_settings(InstanceSettings | null $settings = n
|
|||||||
if (!$settings) {
|
if (!$settings) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
}
|
}
|
||||||
|
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
||||||
|
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
||||||
if (data_get($settings, 'resend_enabled')) {
|
if (data_get($settings, 'resend_enabled')) {
|
||||||
config()->set('mail.default', 'resend');
|
config()->set('mail.default', 'resend');
|
||||||
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
||||||
@@ -241,9 +255,9 @@ function validate_cron_expression($expression_to_validate): bool
|
|||||||
function send_internal_notification(string $message): void
|
function send_internal_notification(string $message): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$baseUrl = base_url(false);
|
$baseUrl = config('app.name');
|
||||||
$team = Team::find(0);
|
$team = Team::find(0);
|
||||||
$team->notify(new GeneralNotification("👀 Internal notifications from {$baseUrl}: " . $message));
|
$team->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
ray($th->getMessage());
|
ray($th->getMessage());
|
||||||
}
|
}
|
||||||
@@ -259,17 +273,54 @@ function send_user_an_email(MailMessage $mail, string $email): void
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
fn (Message $message) => $message
|
fn (Message $message) => $message
|
||||||
->from(
|
|
||||||
data_get($settings, 'smtp_from_address'),
|
|
||||||
data_get($settings, 'smtp_from_name')
|
|
||||||
)
|
|
||||||
->to($email)
|
->to($email)
|
||||||
->subject($mail->subject)
|
->subject($mail->subject)
|
||||||
->html((string) $mail->render())
|
->html((string) $mail->render())
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
function isEmailEnabled($notifiable)
|
function isEmailEnabled($notifiable)
|
||||||
{
|
{
|
||||||
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
||||||
}
|
}
|
||||||
|
function setNotificationChannels($notifiable, $event)
|
||||||
|
{
|
||||||
|
$channels = [];
|
||||||
|
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||||
|
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||||
|
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||||
|
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
|
||||||
|
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
|
||||||
|
|
||||||
|
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
||||||
|
$channels[] = DiscordChannel::class;
|
||||||
|
}
|
||||||
|
if ($isEmailEnabled) {
|
||||||
|
$channels[] = EmailChannel::class;
|
||||||
|
}
|
||||||
|
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
|
||||||
|
$channels[] = TelegramChannel::class;
|
||||||
|
}
|
||||||
|
return $channels;
|
||||||
|
}
|
||||||
|
function parseEnvFormatToArray($env_file_contents) {
|
||||||
|
$env_array = array();
|
||||||
|
$lines = explode("\n", $env_file_contents);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if ($line === '' || substr($line, 0, 1) === '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$equals_pos = strpos($line, '=');
|
||||||
|
if ($equals_pos !== false) {
|
||||||
|
$key = substr($line, 0, $equals_pos);
|
||||||
|
$value = substr($line, $equals_pos + 1);
|
||||||
|
if (substr($value, 0, 1) === '"' && substr($value, -1) === '"') {
|
||||||
|
$value = substr($value, 1, -1);
|
||||||
|
}
|
||||||
|
elseif (substr($value, 0, 1) === "'" && substr($value, -1) === "'") {
|
||||||
|
$value = substr($value, 1, -1);
|
||||||
|
}
|
||||||
|
$env_array[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $env_array;
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ function getEndDate()
|
|||||||
|
|
||||||
function isSubscriptionActive()
|
function isSubscriptionActive()
|
||||||
{
|
{
|
||||||
|
if (!isCloud()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
if (!$team) {
|
if (!$team) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v10.7.1",
|
"laravel/framework": "^v10.7.1",
|
||||||
"laravel/horizon": "^5.15",
|
"laravel/horizon": "^5.15",
|
||||||
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v3.2.1",
|
"laravel/sanctum": "^v3.2.1",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
|
|||||||
2
composer.lock
generated
2
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "dbb08df7a80c46ce2b9b9fa397ed71c1",
|
"content-hash": "0603276b60e77cd859fabacdaaf31550",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Coolify'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ return [
|
|||||||
'users' => [
|
'users' => [
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
'table' => 'password_reset_tokens',
|
'table' => 'password_reset_tokens',
|
||||||
'expire' => 60,
|
'expire' => 10,
|
||||||
'throttle' => 60,
|
'throttle' => 60,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ return [
|
|||||||
'mux_enabled' => env('MUX_ENABLED', true),
|
'mux_enabled' => env('MUX_ENABLED', true),
|
||||||
'dev_webhook' => env('SERVEO_URL'),
|
'dev_webhook' => env('SERVEO_URL'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||||
'dsn' => 'https://abe219b6573947128ecf523c835f5f38@o1082494.ingest.sentry.io/4505347448045568',
|
'dsn' => 'https://62de992090e4e0cb28f18231835ea006@o1082494.ingest.sentry.io/4505347448045568',
|
||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.22',
|
'release' => '4.0.0-beta.30',
|
||||||
'server_name' => env('APP_ID', 'coolify'),
|
'server_name' => env('APP_ID', 'coolify'),
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.22';
|
return '4.0.0-beta.30';
|
||||||
|
|||||||
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
32
database/migrations/2023_08_22_071054_add_stripe_reasons.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->string('stripe_feedback')->nullable()->after('stripe_cancel_at_period_end');
|
||||||
|
$table->string('stripe_comment')->nullable()->after('stripe_feedback');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('stripe_feedback');
|
||||||
|
$table->dropColumn('stripe_comment');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->boolean('telegram_enabled')->default(false);
|
||||||
|
$table->text('telegram_token')->nullable();
|
||||||
|
$table->text('telegram_chat_id')->nullable();
|
||||||
|
$table->boolean('telegram_notifications_test')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_deployments')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_status_changes')->default(true);
|
||||||
|
$table->boolean('telegram_notifications_database_backups')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('telegram_enabled');
|
||||||
|
$table->dropColumn('telegram_token');
|
||||||
|
$table->dropColumn('telegram_chat_id');
|
||||||
|
$table->dropColumn('telegram_notifications_test');
|
||||||
|
$table->dropColumn('telegram_notifications_deployments');
|
||||||
|
$table->dropColumn('telegram_notifications_status_changes');
|
||||||
|
$table->dropColumn('telegram_notifications_database_backups');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->text('telegram_notifications_test_message_thread_id')->nullable();
|
||||||
|
$table->text('telegram_notifications_deployments_message_thread_id')->nullable();
|
||||||
|
$table->text('telegram_notifications_status_changes_message_thread_id')->nullable();
|
||||||
|
$table->text('telegram_notifications_database_backups_message_thread_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('teams', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('telegram_message_thread_id');
|
||||||
|
$table->dropColumn('telegram_notifications_test_message_thread_id');
|
||||||
|
$table->dropColumn('telegram_notifications_deployments_message_thread_id');
|
||||||
|
$table->dropColumn('telegram_notifications_status_changes_message_thread_id');
|
||||||
|
$table->dropColumn('telegram_notifications_database_backups_message_thread_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -45,7 +45,7 @@ class ProductionSeeder extends Seeder
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config('app.name') !== 'coolify-cloud') {
|
if (config('app.name') !== 'Coolify Cloud') {
|
||||||
// Save SSH Keys for the Coolify Host
|
// Save SSH Keys for the Coolify Host
|
||||||
$coolify_key_name = "id.root@host.docker.internal";
|
$coolify_key_name = "id.root@host.docker.internal";
|
||||||
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ x-testing-host: &testing-host-base
|
|||||||
context: ./docker/testing-host
|
context: ./docker/testing-host
|
||||||
networks:
|
networks:
|
||||||
- coolify
|
- coolify
|
||||||
|
init: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
@@ -53,6 +54,7 @@ services:
|
|||||||
<<: *testing-host-base
|
<<: *testing-host-base
|
||||||
container_name: coolify-testing-host
|
container_name: coolify-testing-host
|
||||||
volumes:
|
volumes:
|
||||||
|
- /:/host
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /data/coolify/:/data/coolify
|
- /data/coolify/:/data/coolify
|
||||||
mailpit:
|
mailpit:
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ FROM alpine:3.17
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=23.0.6
|
ARG DOCKER_VERSION=24.0.5
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.18.1
|
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.10.5
|
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.29.0
|
ARG PACK_VERSION=0.30.0
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.12.0
|
ARG NIXPACKS_VERSION=1.13.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /artifacts
|
WORKDIR /artifacts
|
||||||
@@ -38,5 +38,5 @@ COPY --from=minio/mc /usr/bin/mc /usr/bin/mc
|
|||||||
RUN chmod +x /usr/bin/mc
|
RUN chmod +x /usr/bin/mc
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD ["sh", "-c", "while true; do sleep 1; done"]
|
CMD ["tail", "-f", "/dev/null"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
FROM alpine:3.17
|
FROM debian:12-slim
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=23.0.6
|
ARG DOCKER_VERSION=24.0.5
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.18.1
|
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.10.5
|
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.29.0
|
ARG PACK_VERSION=0.30.0
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.12.0
|
ARG NIXPACKS_VERSION=1.13.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
RUN apk add --no-cache bash curl git git-lfs openssh-client openssh-server tar tini postgresql-client lsof
|
ENV PATH "$PATH:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin"
|
||||||
|
|
||||||
|
RUN apt update && apt -y install openssh-client openssh-server curl wget git jq jc
|
||||||
RUN mkdir -p ~/.docker/cli-plugins
|
RUN mkdir -p ~/.docker/cli-plugins
|
||||||
RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
RUN curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||||
curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \
|
RUN curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
||||||
curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose && \
|
RUN (curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker)
|
||||||
(curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker) && \
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /root/.docker/cli-plugins/docker-buildx
|
||||||
(curl -sSL https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz | tar -C /usr/local/bin/ --no-same-owner -xzv pack) && \
|
|
||||||
curl -sSL https://nixpacks.com/install.sh | bash && \
|
|
||||||
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
|
|
||||||
;fi
|
|
||||||
|
|
||||||
# Setup sshd
|
# Setup sshd
|
||||||
RUN ssh-keygen -A
|
RUN ssh-keygen -A
|
||||||
@@ -32,6 +31,4 @@ RUN mkdir -p ~/.ssh
|
|||||||
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
|
RUN echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFuGmoeGq/pojrsyP1pszcNVuZx9iFkCELtxrh31QJ68 coolify@coolify-instance" >> ~/.ssh/authorized_keys
|
||||||
|
|
||||||
EXPOSE 22
|
EXPOSE 22
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
|
||||||
CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"]
|
CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"]
|
||||||
|
|
||||||
|
|||||||
@@ -593,7 +593,7 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/source/new`
|
targetUrl.pathname = `/source/new`
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
targetUrl.pathname = `/private-key/new`
|
targetUrl.pathname = `/security/private-key/new`
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
targetUrl.pathname = `/destination/new`
|
targetUrl.pathname = `/destination/new`
|
||||||
@@ -612,7 +612,7 @@ async function redirect() {
|
|||||||
targetUrl.pathname = `/servers`
|
targetUrl.pathname = `/servers`
|
||||||
break;
|
break;
|
||||||
case 13:
|
case 13:
|
||||||
targetUrl.pathname = `/private-keys`
|
targetUrl.pathname = `/security/private-key`
|
||||||
break;
|
break;
|
||||||
case 14:
|
case 14:
|
||||||
targetUrl.pathname = `/projects`
|
targetUrl.pathname = `/projects`
|
||||||
|
|||||||
@@ -51,6 +51,11 @@
|
|||||||
{{ session('status') }}
|
{{ session('status') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if (session('error'))
|
||||||
|
<div class="mb-4 font-medium text-red-600">
|
||||||
|
{{ session('error') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
6
resources/views/components/emails/footer.blade.php
Normal file
6
resources/views/components/emails/footer.blade.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{{ Illuminate\Mail\Markdown::parse('---') }}
|
||||||
|
|
||||||
|
Thank you,<br>
|
||||||
|
{{ config('app.name') ?? 'Coolify' }}
|
||||||
|
|
||||||
|
{{ Illuminate\Mail\Markdown::parse('[Contact Support](https://docs.coollabs.io)') }}
|
||||||
1
resources/views/components/emails/header.blade.php
Normal file
1
resources/views/components/emails/header.blade.php
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello,
|
||||||
6
resources/views/components/emails/layout.blade.php
Normal file
6
resources/views/components/emails/layout.blade.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<x-emails.header />
|
||||||
|
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||||
|
<x-emails.footer />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
@if ($attributes->get('type') === 'submit')
|
@if ($attributes->get('type') === 'submit')
|
||||||
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
||||||
@else
|
@else
|
||||||
@if ($attributes->has('wire:click'))
|
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||||
<span wire:target="{{ $attributes->get('wire:click') }}" wire:loading.delay
|
<span wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" wire:loading.delay
|
||||||
class="loading loading-xs loading-spinner"></span>
|
class="loading loading-xs loading-spinner"></span>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input @disabled($disabled) type="checkbox" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
@if ($instantSave) wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
@if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
||||||
wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
|
wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
wire:model.defer={{ $id }} wire:dirty.class.remove='text-white'
|
wire:model.defer={{ $id }} wire:dirty.class.remove='text-white'
|
||||||
wire:dirty.class="input-warning" wire:loading.attr="disabled" type="{{ $type }}"
|
wire:dirty.class="input-warning" wire:loading.attr="disabled" type="{{ $type }}"
|
||||||
@readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}"
|
@readonly($readonly) @disabled($disabled) id="{{ $id }}" name="{{ $name }}"
|
||||||
placeholder="{{ $attributes->get('placeholder') }}">
|
placeholder="{{ $attributes->get('placeholder') }}"
|
||||||
|
aria-placeholder="{{ $attributes->get('placeholder') }}">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
|
<div class="absolute z-40 hidden text-xs rounded group-hover:block border-coolgray-400 bg-coolgray-500">
|
||||||
<div class="p-4 card-body">
|
<div class="p-4">
|
||||||
{!! $helper !!}
|
{!! $helper !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,10 +43,6 @@
|
|||||||
@endisset
|
@endisset
|
||||||
@if ($modalSubmit)
|
@if ($modalSubmit)
|
||||||
{{ $modalSubmit }}
|
{{ $modalSubmit }}
|
||||||
@else
|
|
||||||
<x-forms.button onclick="{{ $modalId }}.close()" type="submit">
|
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -50,6 +50,23 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li title="Source">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('source.all') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li title="Security">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1-4.069 0l-.301-.301l-6.558 6.558a2 2 0 0 1-1.239.578L5.172 21H4a1 1 0 0 1-.993-.883L3 20v-1.172a2 2 0 0 1 .467-1.284l.119-.13L4 17h2v-2h2v-2l2.144-2.144l-.301-.301a2.877 2.877 0 0 1 0-4.069l2.643-2.643a2.877 2.877 0 0 1 4.069 0zM15 9h.01" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li title="Teams">
|
<li title="Teams">
|
||||||
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
@@ -64,6 +81,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@if (isInstanceAdmin() && !isCloud())
|
@if (isInstanceAdmin() && !isCloud())
|
||||||
<livewire:upgrade />
|
<livewire:upgrade />
|
||||||
@@ -95,6 +113,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
@if (isSubscriptionActive() || isDev())
|
||||||
|
<li title="Help" class="mt-auto">
|
||||||
|
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
|
||||||
|
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
|
||||||
|
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
<li class="pb-6" title="Logout">
|
<li class="pb-6" title="Logout">
|
||||||
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
<form action="/logout" method="POST" class=" hover:bg-transparent">
|
||||||
@csrf
|
@csrf
|
||||||
|
|||||||
17
resources/views/components/security/navbar.blade.php
Normal file
17
resources/views/components/security/navbar.blade.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="pb-6">
|
||||||
|
<h1>Security</h1>
|
||||||
|
<nav class="flex pt-2 pb-10">
|
||||||
|
<ol class="inline-flex items-center">
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>Security related settings</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<nav class="navbar-main">
|
||||||
|
<a class="{{ request()->routeIs('security.private-key.index') ? 'text-white' : '' }}" href="{{ route('security.private-key.index') }}">
|
||||||
|
<button>Private Keys</button>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
@if ($pull_request_id !== 0)
|
<x-emails.layout>
|
||||||
Pull Request #{{ $pull_request_id }} of {{ $name }} (<a target="_blank"
|
@if ($pull_request_id === 0)
|
||||||
href="{{ $fqdn }}">{{ $fqdn }}</a>) deployment failed:
|
Failed to deploy a new version of {{ $name }} at [{{ $fqdn }}]({{ $fqdn }}) .
|
||||||
@else
|
@else
|
||||||
Deployment failed of {{ $name }} (<a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a>):
|
Failed to deploy a pull request #{{ $pull_request_id }} of {{ $name }} at
|
||||||
@endif
|
[{{ $fqdn }}]({{ $fqdn }}).
|
||||||
|
@endif
|
||||||
|
|
||||||
<a target="_blank" href="{{ $deployment_url }}">View Deployment Logs</a><br><br>
|
[View Deployment Logs]({{ $deployment_url }})
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@if ($pull_request_id === 0)
|
<x-emails.layout>
|
||||||
A new version of <a target="_blank" href="{{ $fqdn }}">{{ $fqdn }}</a> is available:
|
@if ($pull_request_id === 0)
|
||||||
@else
|
A new version of {{ $name }} is available at [{{ $fqdn }}]({{ $fqdn }}) .
|
||||||
Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully: <a target="_blank"
|
@else
|
||||||
href="{{ $fqdn }}">Application Link</a> |
|
Pull request #{{ $pull_request_id }} of {{ $name }} deployed successfully [{{ $fqdn }}]({{ $fqdn }}).
|
||||||
@endif
|
@endif
|
||||||
<a target="_blank" href="{{ $deployment_url }}">View
|
|
||||||
Deployment Logs</a><br><br>
|
[View Deployment Logs]({{ $deployment_url }})
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
{{ $name }} has been stopped.<br><br>
|
<x-emails.layout>
|
||||||
<a target="_blank" href="{{ $application_url }}">Open in Coolify</a><br><br>
|
|
||||||
|
{{ $name }} has been stopped.
|
||||||
|
|
||||||
|
If it was your intention to stop this application, you can ignore this email.
|
||||||
|
|
||||||
|
If not, [check what is going on]({{ $application_url }}).
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
|
|||||||
8
resources/views/emails/backup-failed.blade.php
Normal file
8
resources/views/emails/backup-failed.blade.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Database backup for {{ $name }} with frequency of {{ $frequency }} was FAILED.
|
||||||
|
|
||||||
|
### Reason
|
||||||
|
|
||||||
|
{{ $output }}
|
||||||
|
|
||||||
|
</x-emails.layout>
|
||||||
3
resources/views/emails/backup-success.blade.php
Normal file
3
resources/views/emails/backup-success.blade.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<x-emails.layout>
|
||||||
|
Database backup for {{ $name }} with frequency of {{ $frequency }} was successful.
|
||||||
|
</x-emails.layout>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user