mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-01 12:33:45 +00:00
Compare commits
19 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
_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
|
||||||
|
|
||||||
|
|||||||
@@ -107,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 {
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ class TelegramSettings extends Component
|
|||||||
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
||||||
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
||||||
'team.telegram_notifications_database_backups' => '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 = [
|
protected $validationAttributes = [
|
||||||
'team.telegram_token' => 'Token',
|
'team.telegram_token' => 'Token',
|
||||||
|
|||||||
@@ -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']);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class IsBoardingFlow
|
|||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
ray()->showQueries()->color('orange');
|
// ray()->showQueries()->color('orange');
|
||||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||||
return redirect('boarding');
|
return redirect('boarding');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,28 +24,32 @@ 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 = null;
|
||||||
public string $database_type;
|
public string $database_type;
|
||||||
public StandalonePostgresql $database;
|
public ?StandalonePostgresql $database = null;
|
||||||
public string $database_status;
|
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 = $this->backup->database;
|
||||||
|
if (!$this->database) {
|
||||||
|
ray('Database not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->database_type = $this->database->type();
|
$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->database_status = $this->database->status;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class SendMessageToTelegramJob implements ShouldQueue
|
|||||||
public array $buttons,
|
public array $buttons,
|
||||||
public string $token,
|
public string $token,
|
||||||
public string $chatId,
|
public string $chatId,
|
||||||
|
public ?string $topicId = null,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,9 @@ class SendMessageToTelegramJob implements ShouldQueue
|
|||||||
'chat_id' => $this->chatId,
|
'chat_id' => $this->chatId,
|
||||||
'text' => $this->text,
|
'text' => $this->text,
|
||||||
];
|
];
|
||||||
ray($payload);
|
if ($this->topicId) {
|
||||||
|
$payload['message_thread_id'] = $this->topicId;
|
||||||
|
}
|
||||||
$response = Http::post($url, $payload);
|
$response = Http::post($url, $payload);
|
||||||
if ($response->failed()) {
|
if ($response->failed()) {
|
||||||
throw new \Exception('Telegram notification failed with ' . $response->status() . ' status code.' . $response->body());
|
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
|
||||||
|
|||||||
@@ -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,7 +28,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"token" => data_get($this, 'telegram_token', null),
|
"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),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,16 +10,30 @@ class TelegramChannel
|
|||||||
{
|
{
|
||||||
$data = $notification->toTelegram($notifiable);
|
$data = $notification->toTelegram($notifiable);
|
||||||
$telegramData = $notifiable->routeNotificationForTelegram();
|
$telegramData = $notifiable->routeNotificationForTelegram();
|
||||||
|
|
||||||
$message = data_get($data, 'message');
|
$message = data_get($data, 'message');
|
||||||
$buttons = data_get($data, 'buttons', []);
|
$buttons = data_get($data, 'buttons', []);
|
||||||
ray($message, $buttons);
|
|
||||||
$telegramToken = data_get($telegramData, 'token');
|
$telegramToken = data_get($telegramData, 'token');
|
||||||
$chatId = data_get($telegramData, 'chat_id');
|
$chatId = data_get($telegramData, 'chat_id');
|
||||||
|
$topicId = null;
|
||||||
|
$topicsInstance = get_class($notification);
|
||||||
|
|
||||||
if (!$telegramToken || !$chatId || !$message) {
|
switch ($topicsInstance) {
|
||||||
throw new \Exception('Telegram token, chat id and message are required');
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Test extends Notification implements ShouldQueue
|
|||||||
"buttons" => [
|
"buttons" => [
|
||||||
[
|
[
|
||||||
"text" => "Go to your dashboard",
|
"text" => "Go to your dashboard",
|
||||||
"url" => 'https://coolify.io'
|
"url" => base_url()
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,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
|
||||||
{
|
{
|
||||||
@@ -292,3 +296,25 @@ function setNotificationChannels($notifiable, $event)
|
|||||||
}
|
}
|
||||||
return $channels;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// 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.24',
|
'release' => '4.0.0-beta.28',
|
||||||
'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.24';
|
return '4.0.0-beta.28';
|
||||||
|
|||||||
@@ -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
|
@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>
|
||||||
|
|||||||
@@ -16,27 +16,50 @@
|
|||||||
<x-forms.checkbox instantSave id="team.telegram_enabled" label="Notification Enabled" />
|
<x-forms.checkbox instantSave id="team.telegram_enabled" label="Notification Enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<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
|
<x-forms.input type="password"
|
||||||
id="team.telegram_token" label="Token" />
|
helper="Get it from the <a class='inline-block text-white underline' href='https://t.me/botfather' target='_blank'>BotFather Bot</a> on Telegram."
|
||||||
<x-forms.input type="password" helper="Recommended to add your bot to a group chat and add its Chat ID here." required
|
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" />
|
id="team.telegram_chat_id" label="Chat ID" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
@if (data_get($team, 'telegram_enabled'))
|
@if (data_get($team, 'telegram_enabled'))
|
||||||
<h3 class="mt-4">Subscribe to events</h3>
|
<h2 class="mt-4">Subscribe to events</h2>
|
||||||
<div class="w-64">
|
<div class="w-96">
|
||||||
@if (isDev())
|
@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
|
@endif
|
||||||
<h4 class="mt-4">General</h4>
|
<h3 class="mt-4">Container Status Changes</h3>
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_status_changes"
|
<div class="flex items-end gap-10">
|
||||||
label="Container Status Changes" />
|
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_status_changes"
|
||||||
<h4 class="mt-4">Applications</h4>
|
label="Enabled" />
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_deployments"
|
<x-forms.input
|
||||||
label="Deployments" />
|
helper="If you are using Group chat with Topics, you can specify the topics ID. If empty, General topic will be used."
|
||||||
<h4 class="mt-4">Databases</h4>
|
id="team.telegram_notifications_status_changes_message_thread_id" label="Custom Topic ID" />
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.telegram_notifications_database_backups"
|
</div>
|
||||||
label="Backup Statuses" />
|
<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>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,10 +51,15 @@
|
|||||||
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
||||||
placeholder="If empty, use default. See in docker docs." />
|
placeholder="If empty, use default. See in docker docs." />
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<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" />
|
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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="pb-16">
|
<div class="pb-16">
|
||||||
|
|||||||
@@ -4,23 +4,40 @@
|
|||||||
<h2>Environment Variables</h2>
|
<h2>Environment Variables</h2>
|
||||||
<x-forms.button class="btn" onclick="newVariable.showModal()">+ Add</x-forms.button>
|
<x-forms.button class="btn" onclick="newVariable.showModal()">+ Add</x-forms.button>
|
||||||
<livewire:project.shared.environment-variable.add />
|
<livewire:project.shared.environment-variable.add />
|
||||||
|
<x-forms.button
|
||||||
|
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div>Environment (secrets) variables for this resource.</div>
|
<div>Environment variables (secrets) for this resource.</div>
|
||||||
</div>
|
</div>
|
||||||
@forelse ($resource->environment_variables as $env)
|
@if ($view === 'normal')
|
||||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
@forelse ($resource->environment_variables as $env)
|
||||||
: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)
|
|
||||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||||
:env="$env" />
|
: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
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
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="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' })"
|
: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({
|
:class="toast.select({
|
||||||
error: 'bg-coolgray-300',
|
error: 'bg-coolgray-300',
|
||||||
info: 'bg-coolgray-300',
|
info: 'bg-coolgray-300',
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<span x-html="toast.message" />
|
<span x-html="toast.message" class="w-full pr-10 break-words" />
|
||||||
</i>
|
</i>
|
||||||
|
|
||||||
@if ($closeable)
|
@if ($closeable)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.24"
|
"version": "4.0.0-beta.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user