mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 04:59:32 +00:00
Compare commits
32 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ _ide_helper.php
|
||||
_ide_helper_models.php
|
||||
.rnd
|
||||
/.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
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -26,13 +36,19 @@ You can find the installation script [here](./scripts/install.sh).
|
||||
|
||||
## Support
|
||||
|
||||
- Twitter: [@heyandras](https://twitter.com/heyandras)
|
||||
- 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)
|
||||
Contact us [here](https://docs.coollabs.io/contact).
|
||||
|
||||
---
|
||||
## 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
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class InstallDocker
|
||||
"echo ####### Restarting Docker Engine...",
|
||||
"systemctl restart docker",
|
||||
"echo ####### Creating default network...",
|
||||
"docker network create --attachable coolify",
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
"echo ####### Done!"
|
||||
], $server);
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
|
||||
@@ -23,6 +23,7 @@ use Mail;
|
||||
use Str;
|
||||
|
||||
use function Laravel\Prompts\select;
|
||||
use function Laravel\Prompts\text;
|
||||
|
||||
class TestEmail extends Command
|
||||
{
|
||||
@@ -44,9 +45,10 @@ class TestEmail extends Command
|
||||
* Execute the console command.
|
||||
*/
|
||||
private ?MailMessage $mail = null;
|
||||
private string $email = 'andras.bacsai@protonmail.com';
|
||||
public function handle()
|
||||
{
|
||||
$email = select(
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'emails-test' => 'Test',
|
||||
@@ -60,15 +62,15 @@ class TestEmail extends Command
|
||||
'waitlist-confirmation' => 'Waitlist Confirmation',
|
||||
],
|
||||
);
|
||||
$type = set_transanctional_email_settings();
|
||||
if (!$type) {
|
||||
throw new Exception('No email settings found.');
|
||||
}
|
||||
$this->email = text('Email Address to send to');
|
||||
set_transanctional_email_settings();
|
||||
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($email) {
|
||||
switch ($type) {
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success':
|
||||
$application = Application::all()->first();
|
||||
@@ -172,11 +174,7 @@ class TestEmail extends Command
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->from(
|
||||
'internal@example.com',
|
||||
'Test Email',
|
||||
)
|
||||
->to('test@example.com')
|
||||
->to($this->email)
|
||||
->subject($this->mail->subject)
|
||||
->html((string)$this->mail->render())
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Actions\Server\InstallDocker;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -70,9 +71,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
currentTeam()->update([
|
||||
Team::find(currentTeam()->id)->update([
|
||||
'show_boarding' => false
|
||||
]);
|
||||
ray(currentTeam());
|
||||
refreshSession();
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
@@ -107,7 +107,14 @@ class EmailSettings extends Component
|
||||
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()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -17,6 +17,10 @@ class TelegramSettings extends Component
|
||||
'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',
|
||||
|
||||
@@ -42,8 +42,13 @@ class Heading extends Component
|
||||
["docker rm -f {$this->database->uuid}"],
|
||||
$this->database->destination->server
|
||||
);
|
||||
if ($this->database->is_public) {
|
||||
stopPostgresProxy($this->database);
|
||||
$this->database->is_public = false;
|
||||
}
|
||||
$this->database->status = 'stopped';
|
||||
$this->database->save();
|
||||
$this->emit('refresh');
|
||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class General extends Component
|
||||
public StandalonePostgresql $database;
|
||||
public string $new_filename;
|
||||
public string $new_content;
|
||||
public string $db_url;
|
||||
|
||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||
|
||||
@@ -26,6 +27,8 @@ class General extends Component
|
||||
'database.init_scripts' => 'nullable',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
@@ -38,8 +41,44 @@ class General extends Component
|
||||
'database.init_scripts' => 'Init Scripts',
|
||||
'database.image' => 'Image',
|
||||
'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)
|
||||
{
|
||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
||||
|
||||
@@ -5,21 +5,84 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Str;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public $resource;
|
||||
public bool $showPreview = false;
|
||||
public string|null $modalId = null;
|
||||
public ?string $variables = null;
|
||||
public ?string $variablesPreview = null;
|
||||
public string $view = 'normal';
|
||||
protected $listeners = ['refreshEnvs', 'submit'];
|
||||
|
||||
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->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()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
$this->getDevView();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
@@ -43,7 +106,7 @@ class All extends Component
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
}
|
||||
$environment->save();
|
||||
$this->resource->refresh();
|
||||
$this->refreshEnvs();
|
||||
$this->emit('success', 'Environment variable added successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler(err: $e, that: $this);
|
||||
|
||||
@@ -60,7 +60,6 @@ class Email extends Component
|
||||
$this->validate([
|
||||
'settings.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->settings->smtp_enabled = false;
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
@@ -68,9 +67,18 @@ class Email extends Component
|
||||
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()
|
||||
{
|
||||
try {
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->submit();
|
||||
} catch (\Exception $e) {
|
||||
return general_error_handler($e, $this);
|
||||
@@ -89,7 +97,6 @@ class Email extends Component
|
||||
'settings.smtp_password' => 'nullable',
|
||||
'settings.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->settings->resend_enabled = false;
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings saved successfully.');
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class SwitchTeam extends Component
|
||||
if (!$team_to_switch_to) {
|
||||
return;
|
||||
}
|
||||
session(['currentTeam' => $team_to_switch_to]);
|
||||
refreshSession($team_to_switch_to);
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,28 +24,32 @@ class DatabaseBackupJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Team|null $team = null;
|
||||
public ?Team $team = null;
|
||||
public Server $server;
|
||||
public ScheduledDatabaseBackup|null $backup;
|
||||
public ?ScheduledDatabaseBackup $backup = null;
|
||||
public string $database_type;
|
||||
public StandalonePostgresql $database;
|
||||
public ?StandalonePostgresql $database = null;
|
||||
public string $database_status;
|
||||
|
||||
public string|null $container_name = null;
|
||||
public ScheduledDatabaseBackupExecution|null $backup_log = null;
|
||||
public ?string $container_name = null;
|
||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||
public string $backup_status;
|
||||
public string|null $backup_location = null;
|
||||
public ?string $backup_location = null;
|
||||
public string $backup_dir;
|
||||
public string $backup_file;
|
||||
public int $size = 0;
|
||||
public string|null $backup_output = null;
|
||||
public S3Storage|null $s3 = null;
|
||||
public ?string $backup_output = null;
|
||||
public ?S3Storage $s3 = null;
|
||||
|
||||
public function __construct($backup)
|
||||
{
|
||||
$this->backup = $backup;
|
||||
$this->team = Team::find($backup->team_id);
|
||||
$this->database = $this->backup->database;
|
||||
if (!$this->database) {
|
||||
ray('Database not found');
|
||||
return;
|
||||
}
|
||||
$this->database_type = $this->database->type();
|
||||
$this->server = $this->database->destination->server;
|
||||
$this->database_status = $this->database->status;
|
||||
|
||||
@@ -31,6 +31,7 @@ class SendMessageToTelegramJob implements ShouldQueue
|
||||
public array $buttons,
|
||||
public string $token,
|
||||
public string $chatId,
|
||||
public ?string $topicId = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -63,7 +64,9 @@ class SendMessageToTelegramJob implements ShouldQueue
|
||||
'chat_id' => $this->chatId,
|
||||
'text' => $this->text,
|
||||
];
|
||||
ray($payload);
|
||||
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
|
||||
{
|
||||
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
|
||||
@@ -127,7 +127,7 @@ class Application extends BaseModel
|
||||
|
||||
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
|
||||
|
||||
@@ -20,13 +20,16 @@ class EnvironmentVariable extends Model
|
||||
{
|
||||
static::created(function ($environment_variable) {
|
||||
if ($environment_variable->application_id && !$environment_variable->is_preview) {
|
||||
ModelsEnvironmentVariable::create([
|
||||
'key' => $environment_variable->key,
|
||||
'value' => $environment_variable->value,
|
||||
'is_build_time' => $environment_variable->is_build_time,
|
||||
'application_id' => $environment_variable->application_id,
|
||||
'is_preview' => true,
|
||||
]);
|
||||
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview',true)->first();
|
||||
if (!$found) {
|
||||
ModelsEnvironmentVariable::create([
|
||||
'key' => $environment_variable->key,
|
||||
'value' => $environment_variable->value,
|
||||
'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()
|
||||
{
|
||||
return $this->applications->count() > 0 || $this->databases->count() > 0;
|
||||
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
{
|
||||
return [
|
||||
"token" => data_get($this, 'telegram_token', null),
|
||||
"chat_id" => data_get($this, 'telegram_chat_id', null)
|
||||
"chat_id" => data_get($this, 'telegram_chat_id', null),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Notifications\Channels\SendsEmail;
|
||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||
use Cache;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -94,7 +95,9 @@ class User extends Authenticatable implements SendsEmail
|
||||
|
||||
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()
|
||||
|
||||
@@ -16,6 +16,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public Application $application;
|
||||
public string $deployment_uuid;
|
||||
public ?ApplicationPreview $preview = null;
|
||||
|
||||
@@ -16,6 +16,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public Application $application;
|
||||
public string $deployment_uuid;
|
||||
public ApplicationPreview|null $preview = null;
|
||||
|
||||
@@ -14,6 +14,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public $application;
|
||||
|
||||
public string $application_name;
|
||||
|
||||
@@ -10,16 +10,30 @@ class TelegramChannel
|
||||
{
|
||||
$data = $notification->toTelegram($notifiable);
|
||||
$telegramData = $notifiable->routeNotificationForTelegram();
|
||||
|
||||
$message = data_get($data, 'message');
|
||||
$buttons = data_get($data, 'buttons', []);
|
||||
ray($message, $buttons);
|
||||
$telegramToken = data_get($telegramData, 'token');
|
||||
$chatId = data_get($telegramData, 'chat_id');
|
||||
$topicId = null;
|
||||
$topicsInstance = get_class($notification);
|
||||
|
||||
if (!$telegramToken || !$chatId || !$message) {
|
||||
throw new \Exception('Telegram token, chat id and message are required');
|
||||
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;
|
||||
}
|
||||
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId));
|
||||
if (!$telegramToken || !$chatId || !$message) {
|
||||
return;
|
||||
}
|
||||
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class BackupFailed extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public string $name;
|
||||
public string $frequency;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class BackupSuccess extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public string $name;
|
||||
public string $frequency;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class GeneralNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public function __construct(public string $message)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class NotReachable extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class Test extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public function __construct(public string|null $emails = null)
|
||||
{
|
||||
}
|
||||
@@ -45,7 +46,7 @@ class Test extends Notification implements ShouldQueue
|
||||
"buttons" => [
|
||||
[
|
||||
"text" => "Go to your dashboard",
|
||||
"url" => 'https://coolify.io'
|
||||
"url" => base_url()
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@ class InvitationLink extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public function via(): array
|
||||
{
|
||||
return [TransactionalEmailChannel::class];
|
||||
|
||||
@@ -12,6 +12,7 @@ class Test extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public function __construct(public string $emails)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand
|
||||
}
|
||||
return StandalonePostgresql::create([
|
||||
'name' => generate_database_name('postgresql'),
|
||||
'postgres_password' => \Illuminate\Support\Str::password(),
|
||||
'postgres_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ function database_configuration_dir(): string
|
||||
{
|
||||
return '/data/coolify/databases';
|
||||
}
|
||||
function database_proxy_dir($uuid): string
|
||||
{
|
||||
return "/data/coolify/databases/$uuid/proxy";
|
||||
}
|
||||
|
||||
function backup_dir(): string
|
||||
{
|
||||
@@ -53,9 +57,15 @@ function showBoarding(): bool
|
||||
{
|
||||
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]);
|
||||
}
|
||||
function general_error_handler(Throwable | null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed
|
||||
@@ -292,3 +302,25 @@ function setNotificationChannels($notifiable, $event)
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
return [
|
||||
|
||||
// @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
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.24',
|
||||
'release' => '4.0.0-beta.29',
|
||||
'server_name' => env('APP_ID', 'coolify'),
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.24';
|
||||
return '4.0.0-beta.29';
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -24,7 +24,7 @@
|
||||
@endif
|
||||
</span>
|
||||
<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 />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -20,20 +20,31 @@
|
||||
id="team.discord_webhook_url" label="Webhook" />
|
||||
</form>
|
||||
@if (data_get($team, 'discord_enabled'))
|
||||
<h3 class="mt-4">Subscribe to events</h3>
|
||||
<h2 class="mt-4">Subscribe to events</h2>
|
||||
<div class="w-64">
|
||||
|
||||
|
||||
@if (isDev())
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_test" label="Test" />
|
||||
<h3 class="mt-4">Test</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_test" label="Enabled" />
|
||||
</div>
|
||||
@endif
|
||||
<h4 class="mt-4">General</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_status_changes"
|
||||
label="Container Status Changes" />
|
||||
<h4 class="mt-4">Applications</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_deployments"
|
||||
label="Deployments" />
|
||||
<h4 class="mt-4">Databases</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_database_backups"
|
||||
label="Backup Statuses" />
|
||||
<h3 class="mt-4">Container Status Changes</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_status_changes"
|
||||
label="Enabled" />
|
||||
</div>
|
||||
<h3 class="mt-4">Application Deployments</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_deployments"
|
||||
label="Enabled" />
|
||||
</div>
|
||||
<h3 class="mt-4">Backup Status</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_database_backups"
|
||||
label="Enabled" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Copy from Instance Settings
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
||||
@if (isEmailEnabled($team) && auth()->user()->isAdminFromSession())
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
@@ -36,12 +36,11 @@
|
||||
label="Use hosted email service" />
|
||||
</div>
|
||||
@else
|
||||
<div class="pb-4 w-96">
|
||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||
label="Use hosted email service (Pro+ subscription required)" />
|
||||
</div>
|
||||
<div class="pb-4 w-96">
|
||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||
label="Use hosted email service (Pro+ subscription required)" />
|
||||
</div>
|
||||
@endif
|
||||
<h3 class="pb-4">Custom Email Service</h3>
|
||||
@if (!$team->use_instance_email_settings)
|
||||
<form class="flex flex-col items-end gap-2 pb-4 xl:flex-row" wire:submit.prevent='submitFromFields'>
|
||||
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
||||
@@ -110,19 +109,27 @@
|
||||
</div>
|
||||
@endif
|
||||
@if (isEmailEnabled($team) || data_get($team, 'use_instance_email_settings'))
|
||||
<h3 class="mt-4">Subscribe to events</h3>
|
||||
<h2 class="mt-4">Subscribe to events</h2>
|
||||
<div class="w-64">
|
||||
@if (isDev())
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_test" label="Test" />
|
||||
<h3 class="mt-4">Test</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_test" label="Enabled" />
|
||||
</div>
|
||||
@endif
|
||||
<h4 class="mt-4">General</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_status_changes"
|
||||
label="Container Status Changes" />
|
||||
<h4 class="mt-4">Applications</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_deployments" label="Deployments" />
|
||||
<h4 class="mt-4">Databases</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_database_backups"
|
||||
label="Backup Statuses" />
|
||||
<h3 class="mt-4">Container Status Changes</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_status_changes" label="Enabled" />
|
||||
</div>
|
||||
<h3 class="mt-4">Application Deployments</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_deployments" label="Enabled" />
|
||||
</div>
|
||||
<h3 class="mt-4">Backup Status</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.smtp_notifications_database_backups"
|
||||
label="Enabled" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -16,27 +16,50 @@
|
||||
<x-forms.checkbox instantSave id="team.telegram_enabled" label="Notification Enabled" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input type="password" helper="Get it from the <a class='inline-block text-white underline' href='https://t.me/botfather' target='_blank'>BotFather Bot</a> on Telegram." required
|
||||
id="team.telegram_token" label="Token" />
|
||||
<x-forms.input type="password" helper="Recommended to add your bot to a group chat and add its Chat ID here." required
|
||||
<x-forms.input type="password"
|
||||
helper="Get it from the <a class='inline-block text-white underline' href='https://t.me/botfather' target='_blank'>BotFather Bot</a> on Telegram."
|
||||
required id="team.telegram_token" label="Token" />
|
||||
<x-forms.input
|
||||
helper="Recommended to add your bot to a group chat and add its Chat ID here." required
|
||||
id="team.telegram_chat_id" label="Chat ID" />
|
||||
</div>
|
||||
</form>
|
||||
@if (data_get($team, 'telegram_enabled'))
|
||||
<h3 class="mt-4">Subscribe to events</h3>
|
||||
<div class="w-64">
|
||||
<h2 class="mt-4">Subscribe to events</h2>
|
||||
<div class="w-96">
|
||||
@if (isDev())
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_test" label="Test" />
|
||||
<h3 class="mt-4">Test</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_test" label="Enabled" />
|
||||
<x-forms.input
|
||||
helper="If you are using Group chat with Topics, you can specify the topics ID. If empty, General topic will be used."
|
||||
id="team.telegram_notifications_test_message_thread_id" label="Custom Topic ID" />
|
||||
</div>
|
||||
@endif
|
||||
<h4 class="mt-4">General</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_status_changes"
|
||||
label="Container Status Changes" />
|
||||
<h4 class="mt-4">Applications</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_deployments"
|
||||
label="Deployments" />
|
||||
<h4 class="mt-4">Databases</h4>
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_database_backups"
|
||||
label="Backup Statuses" />
|
||||
<h3 class="mt-4">Container Status Changes</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_status_changes"
|
||||
label="Enabled" />
|
||||
<x-forms.input
|
||||
helper="If you are using Group chat with Topics, you can specify the topics ID. If empty, General topic will be used."
|
||||
id="team.telegram_notifications_status_changes_message_thread_id" label="Custom Topic ID" />
|
||||
</div>
|
||||
<h3 class="mt-4">Application Deployments</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_deployments"
|
||||
label="Enabled" />
|
||||
<x-forms.input
|
||||
helper="If you are using Group chat with Topics, you can specify the topics ID. If empty, General topic will be used."
|
||||
id="team.telegram_notifications_deployments_message_thread_id" label="Custom Topic ID" />
|
||||
</div>
|
||||
<h3 class="mt-4">Backup Status</h3>
|
||||
<div class="flex items-end gap-10">
|
||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_database_backups"
|
||||
label="Enabled" />
|
||||
<x-forms.input
|
||||
helper="If you are using Group chat with Topics, you can specify the topics ID. If empty, General topic will be used."
|
||||
id="team.telegram_notifications_database_backups_message_thread_id" label="Custom Topic ID" />
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -51,10 +51,15 @@
|
||||
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
||||
placeholder="If empty, use default. See in docker docs." />
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
|
||||
<x-forms.input placeholder="5432" disabled="{{$database->is_public}}" id="database.public_port" label="Public Port" />
|
||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||
</div>
|
||||
<x-forms.input label="Postgres URL" readonly wire:model="db_url" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="pb-16">
|
||||
|
||||
@@ -4,23 +4,40 @@
|
||||
<h2>Environment Variables</h2>
|
||||
<x-forms.button class="btn" onclick="newVariable.showModal()">+ Add</x-forms.button>
|
||||
<livewire:project.shared.environment-variable.add />
|
||||
<x-forms.button
|
||||
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||
</div>
|
||||
<div>Environment (secrets) variables for this resource.</div>
|
||||
<div>Environment variables (secrets) for this resource.</div>
|
||||
</div>
|
||||
@forelse ($resource->environment_variables as $env)
|
||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||
:env="$env" />
|
||||
@empty
|
||||
<div class="text-neutral-500">No environment variables found.</div>
|
||||
@endforelse
|
||||
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0)
|
||||
<div>
|
||||
<h3>Preview Deployments</h3>
|
||||
<div>Environment (secrets) variables for Preview Deployments.</div>
|
||||
</div>
|
||||
@foreach ($resource->environment_variables_preview as $env)
|
||||
@if ($view === 'normal')
|
||||
@forelse ($resource->environment_variables as $env)
|
||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||
:env="$env" />
|
||||
@endforeach
|
||||
@empty
|
||||
<div class="text-neutral-500">No environment variables found.</div>
|
||||
@endforelse
|
||||
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)
|
||||
<div>
|
||||
<h3>Preview Deployments</h3>
|
||||
<div>Environment (secrets) variables for Preview Deployments.</div>
|
||||
</div>
|
||||
@foreach ($resource->environment_variables_preview as $env)
|
||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||
:env="$env" />
|
||||
@endforeach
|
||||
@endif
|
||||
@else
|
||||
<form wire:submit.prevent='saveVariables(false)' class="flex flex-col gap-2">
|
||||
<x-forms.textarea rows=5 class="whitespace-pre-wrap" label="Environment Variables"
|
||||
id="variables"></x-forms.textarea>
|
||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
||||
</form>
|
||||
@if ($showPreview)
|
||||
<form wire:submit.prevent='saveVariables(true)' class="flex flex-col gap-2">
|
||||
<x-forms.textarea rows=5 class="whitespace-pre-wrap" label="Preview Environment Variables"
|
||||
id="variablesPreview"></x-forms.textarea>
|
||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
||||
</form>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if ($settings->resend_enabled || $settings->smtp_enabled)
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
</x-forms.button>
|
||||
@endif
|
||||
@if (isEmailEnabled($settings))
|
||||
<x-forms.button onclick="sendTestEmail.showModal()"
|
||||
class="text-white normal-case btn btn-xs no-animation btn-primary">
|
||||
Send Test Email
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
<div class="flex flex-col gap-4">
|
||||
@@ -67,14 +67,15 @@
|
||||
<summary class="text-xl collapse-title">
|
||||
<div>Resend</div>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave='submitResend' id="settings.resend_enabled" label="Enabled" />
|
||||
<x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapse-content">
|
||||
<form wire:submit.prevent='submitResend' class="flex flex-col">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" label="Host" />
|
||||
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key"
|
||||
label="Host" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-4 pt-6">
|
||||
|
||||
4
resources/views/vendor/toaster/hub.blade.php
vendored
4
resources/views/vendor/toaster/hub.blade.php
vendored
@@ -22,7 +22,7 @@
|
||||
class="relative flex duration-300 transform transition ease-in-out max-w-md w-full pointer-events-auto {{ $position->is('center') ? 'text-center' : 'text-left' }}"
|
||||
:class="toast.select({ error: 'text-white', info: 'text-white', success: 'text-white', warning: 'text-white' })"
|
||||
>
|
||||
<i class=" flex items-center gap-2 select-none not-italic pr-6 pl-4 py-3 rounded shadow-lg w-full {{ $alignment->is('bottom') ? 'mt-3' : 'mb-3' }}"
|
||||
<i class=" flex items-center gap-2 select-none not-italic pr-6 pl-4 py-3 rounded shadow-lg w-full {{ $alignment->is('bottom') ? 'mt-3' : 'mb-3' }}"
|
||||
:class="toast.select({
|
||||
error: 'bg-coolgray-300',
|
||||
info: 'bg-coolgray-300',
|
||||
@@ -71,7 +71,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
<span x-html="toast.message" />
|
||||
<span x-html="toast.message" class="w-full pr-10 break-words" />
|
||||
</i>
|
||||
|
||||
@if ($closeable)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.24"
|
||||
"version": "4.0.0-beta.29"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user