Compare commits

...

117 Commits

Author SHA1 Message Date
Andras Bacsai
cc8f09f05e Merge pull request #2422 from coollabsio/next
v4.0.0-beta.296
2024-06-10 12:09:53 +02:00
Andras Bacsai
2beda08717 Refactor CheckLogDrainContainerJob handle method 2024-06-10 11:56:42 +02:00
Andras Bacsai
b455d153ae Update version to 4.0.0-beta.296 2024-06-10 11:56:39 +02:00
Andras Bacsai
5fddf01820 Merge pull request #2355 from coollabsio/next
v4.0.0-beta.295
2024-06-10 11:04:39 +02:00
Andras Bacsai
c80434141d fix: gitlab merge request should close PR 2024-06-10 10:42:52 +02:00
Andras Bacsai
d1128c7a1e fix: multiline variable should be literal + should be multiline in bash with \ 2024-06-09 22:37:23 +02:00
Andras Bacsai
aae81313a6 Refactor TelegramChannel to handle additional notification types 2024-06-09 21:44:13 +02:00
Andras Bacsai
4667f96b40 feat: db proxy logs 2024-06-09 21:33:17 +02:00
Andras Bacsai
28c320ae97 chore: Update install.sh script to version 1.3.2 and handle Linux Mint as Ubuntu 2024-06-09 15:52:23 +02:00
Andras Bacsai
45017efe00 rename migration 2024-06-07 18:16:42 +02:00
Andras Bacsai
a20290cac8 wip: new services based git apps 2024-06-07 17:21:46 +02:00
Andras Bacsai
31e02a154c refactor: Improve handling of Docker volumes in parseDockerComposeFile function 2024-06-07 17:06:27 +02:00
Andras Bacsai
023ee5db99 fix: Set default name for Docker volumes if it is null 2024-06-07 16:55:08 +02:00
Andras Bacsai
05d2e15ab5 update service-templates 2024-06-07 12:28:42 +02:00
Andras Bacsai
7d6590c60a Merge pull request #2347 from tikotzky/patch-1
Add GLITCHTIP_DOMAIN to glitchtip worker service
2024-06-07 12:28:21 +02:00
Andras Bacsai
3152ce183b Merge pull request #2374 from coollabsio/revert-2365-fix-navbar-scroll
Revert "Enhancement: Preserve scroll position in navbar to improve UX"
2024-06-07 12:25:37 +02:00
Andras Bacsai
d9f1a7c4d0 Revert "Enhancement: Preserve scroll position in navbar to improve UX" 2024-06-07 12:25:05 +02:00
Andras Bacsai
952aed3c49 Merge pull request #2365 from avila-gabriel/fix-navbar-scroll
Enhancement: Preserve scroll position in navbar to improve UX
2024-06-07 11:05:55 +02:00
Andras Bacsai
ab3c433450 Merge pull request #2352 from Geczy/patch-2
fix: supabase service, newest versions
2024-06-07 11:04:50 +02:00
Andras Bacsai
2b5e4a34d4 Merge pull request #2364 from TheLazyLemur/main
Add support  to install.sh for PopOS
2024-06-07 11:04:02 +02:00
Andras Bacsai
35cea852ca feat: add titles 2024-06-07 11:01:10 +02:00
Andras Bacsai
88581c8983 Update BUG_REPORT.yml 2024-06-07 10:28:43 +02:00
Andras Bacsai
a7a9aab189 feat: Add bounty program link to bug report template 2024-06-07 10:28:01 +02:00
Andras Bacsai
370c9b63cf fix: post deployment command could fail, but won't make the deployment fail anymore
feat: better error for post deployment command
2024-06-06 15:13:21 +02:00
Andras Bacsai
7cb08849de refactor: Improve pre and post deployment command inputs 2024-06-06 15:11:17 +02:00
Andras Bacsai
7f052163e3 fix: comment id should be string
fix: do not wait for GH response, stop preview before
2024-06-06 12:50:38 +02:00
Andras Bacsai
277d939033 services: rocketchat 2024-06-06 11:39:23 +02:00
Andras Bacsai
26fbdcfab0 Merge pull request #2344 from LEstradioto/feat--add-rocketchat-template
feat: add rocketchat template
2024-06-06 11:37:50 +02:00
Andras Bacsai
6d63ba9d4d chore: Update supported OS list with almalinux 2024-06-06 11:36:51 +02:00
Andras Bacsai
8963f4fd62 refactor: Initialize null properties in Github Change component 2024-06-06 11:10:16 +02:00
Andras Bacsai
463021a9f3 refactor: Remove unused variables and improve code readability 2024-06-06 11:09:27 +02:00
Andras Bacsai
f71a8e9fef fix: sort backup executions 2024-06-06 10:46:19 +02:00
Dan Rousseau
2dd5be1b4e chore: Update install.sh to support PopOS 2024-06-06 09:48:02 +02:00
Gabriel Avila
608838045f Preserve scroll position in navbar after livewire update 2024-06-06 04:14:53 -03:00
Andras Bacsai
899d506faa disable internal notifications on the cloud 2024-06-05 15:34:25 +02:00
Andras Bacsai
21b3e3ea05 refactor: Update deployment previews heading to "Deployments" 2024-06-05 15:32:56 +02:00
Andras Bacsai
a68951541c fix: handle previously defined compose previews 2024-06-05 15:29:00 +02:00
Andras Bacsai
7fd0deedb1 feat: able to add several domains to compose based previews 2024-06-05 15:14:44 +02:00
Andras Bacsai
e9e12ad843 feat: able to change database passwords on the UI. It won't sync to the database. 2024-06-05 11:44:25 +02:00
Andras Bacsai
4fd3185d12 fix: backup executions view 2024-06-05 11:44:10 +02:00
Andras Bacsai
f5ccebfd41 early return 2024-06-05 11:21:02 +02:00
Andras Bacsai
294721eef9 fix: autoupdate process 2024-06-04 21:57:00 +02:00
Andras Bacsai
8af509992d fix: custom docker compose commands, add project dir if needed 2024-06-04 21:26:49 +02:00
Matt
11fccb8e89 fix: supabase service, newest versions 2024-06-04 07:58:40 -05:00
Andras Bacsai
1e126dd2c3 refactor: Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview 2024-06-04 12:59:45 +02:00
Andras Bacsai
cfe2f889a4 refactor: Append utm_source parameter to documentation URL 2024-06-04 12:59:35 +02:00
Andras Bacsai
1bd76b0e07 chore: Update version numbers to 4.0.0-beta.295 2024-06-04 12:23:43 +02:00
Andras Bacsai
6d8c935cc7 Merge pull request #2330 from coollabsio/next
v4.0.0-beta.294
2024-06-04 11:30:11 +02:00
Andras Bacsai
7144cee0f6 chore: Update Dockerfile with latest versions of Docker, Docker Compose, Docker Buildx, Pack, and Nixpacks 2024-06-04 11:27:50 +02:00
Andras Bacsai
f75a8d56f2 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-06-04 11:25:55 +02:00
Andras Bacsai
2f321bcfd9 refactor: Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview 2024-06-04 11:25:53 +02:00
Andras Bacsai
a157f4f17b refactor: Remove commented out code for clearing Ray logs 2024-06-04 11:25:46 +02:00
Andras Bacsai
7723c623d5 fix: check env in args for compose based apps 2024-06-04 11:25:40 +02:00
Luan Estradioto
bc3bb78916 add rocketchat template 2024-06-03 18:12:47 -03:00
Mordy Tikotzky
0ebf5e49fb Add GLITCHTIP_DOMAIN to glitchtip worker service
Without it set on the worker emails send via the worker point to localhost instead of the correct domain.
2024-06-03 15:01:52 -04:00
Andras Bacsai
c340921fbb refactor 2024-06-03 19:12:49 +02:00
Andras Bacsai
30e26b101c Merge pull request #2343 from hamedyosefian/main
Create fa.json
2024-06-03 18:47:17 +02:00
hamedyosefian
1b17cab663 Create fa.json 2024-06-02 20:55:06 +03:30
Andras Bacsai
ab039adf97 remove new deploymentjob 2024-05-31 14:07:09 +02:00
Andras Bacsai
62fe10df31 fix: only ignore volumes with driver_opts 2024-05-31 14:04:17 +02:00
Andras Bacsai
2004a751dd Merge pull request #2199 from ivangsm/sonarqube-template
update sonarqube template
2024-05-31 13:02:32 +02:00
Andras Bacsai
ace127acf4 refactor: Improve display of deployment time in index.blade.php 2024-05-31 13:01:44 +02:00
Andras Bacsai
82c5497a06 Merge pull request #2278 from EstebX/wrong-time-during-a-failed-deployment
fix: wrong time during a failed deployment
2024-05-31 12:57:37 +02:00
Andras Bacsai
dbb7989027 Merge branch 'next' into wrong-time-during-a-failed-deployment 2024-05-31 12:56:20 +02:00
Andras Bacsai
103f677a93 Update StackForm to sort fields by name 2024-05-31 12:55:14 +02:00
Andras Bacsai
cb6bf78595 feat: Add port configuration for Vaultwarden service 2024-05-31 12:54:16 +02:00
Andras Bacsai
62334ddef7 Merge pull request #2293 from iamEvanYT/vaultwarden-fix
Fix Vaultwarden + Add Configs
2024-05-31 12:44:16 +02:00
Andras Bacsai
778f67f2e4 refactor: Update slogan in shlink.yaml 2024-05-31 12:28:56 +02:00
Andras Bacsai
48737f8e60 refactor: Update Docker Compose parsing for services 2024-05-31 12:28:49 +02:00
Andras Bacsai
94de62e503 refactor: Improve Docker Compose parsing for services 2024-05-31 12:09:11 +02:00
Andras Bacsai
0445052898 refactor: Add log entry when starting new application deployment 2024-05-31 12:07:42 +02:00
Andras Bacsai
ccde90ea91 refactor: Update form layout in invite-link.blade.php 2024-05-31 11:20:36 +02:00
Andras Bacsai
ed94355019 default dark mode 2024-05-31 11:20:31 +02:00
Andras Bacsai
02fcd1b5fc refactor: Remove unnecessary form class in profile index.blade.php 2024-05-31 11:14:07 +02:00
Andras Bacsai
ca934e7cdf fix: allow invitations via email 2024-05-31 11:03:43 +02:00
Andras Bacsai
0ddd5f0a79 refactor: Remove unnecessary port appending in updateCompose function 2024-05-31 10:55:08 +02:00
Andras Bacsai
099d6801b7 fix: logto service 2024-05-31 10:55:02 +02:00
Andras Bacsai
62293926ec Merge pull request #2327 from iamEvanYT/service-fqdn-fix
fix: SERVICE_FQDN appending source port
2024-05-31 10:46:37 +02:00
Andras Bacsai
c96daad12c refactor: Improve Docker Compose parsing for services 2024-05-31 10:34:07 +02:00
Andras Bacsai
9cc3be152f fix: compose issues 2024-05-31 10:21:38 +02:00
Andras Bacsai
f841c0d4ba refactor: Update storage form inputs in show.blade.php 2024-05-31 09:59:39 +02:00
Andras Bacsai
3cd1d8135e chore: Update Dockerfile to install vim 2024-05-31 09:41:40 +02:00
Andras Bacsai
86474d9f90 fix: parse docker version better 2024-05-31 09:41:34 +02:00
Andras Bacsai
9bf87a3033 chore: Add Lightspeed.run as a sponsor 2024-05-31 08:45:17 +02:00
iamEvan
71e32520cf fix: SERVICE_FQDN has source port in it 2024-05-30 20:05:44 +01:00
Andras Bacsai
e3e938c8eb refactor: Remove unnecessary logging statements from UpdateCoolify 2024-05-30 20:32:07 +02:00
Andras Bacsai
03aa440424 chore: Update version numbers to 4.0.0-beta.294 2024-05-30 20:31:47 +02:00
Andras Bacsai
85ca38be90 Merge pull request #2325 from coollabsio/next
v4.0.0-beta.293
2024-05-30 20:08:37 +02:00
Andras Bacsai
7c9790dff0 chore: Improve upgrade.blade.php with clearer instructions and formatting 2024-05-30 20:07:42 +02:00
Andras Bacsai
40a71a11cb chore: Add upgrade guide link to upgrade.blade.php 2024-05-30 20:07:06 +02:00
Andras Bacsai
46a500f5e5 async update process 2024-05-30 20:02:11 +02:00
Andras Bacsai
2d1d03bf8e chore: Update version numbers to 4.0.0-beta.293 2024-05-30 20:02:03 +02:00
Andras Bacsai
1092d00c7a Merge pull request #2324 from coollabsio/next
v4.0.0-beta.292
2024-05-30 19:46:11 +02:00
Andras Bacsai
cd58e0d01e fixes 2024-05-30 19:45:36 +02:00
Andras Bacsai
30a9783348 feat: Add manual update option to UpdateCoolify handle method 2024-05-30 19:38:33 +02:00
Andras Bacsai
c839cf50af fix: spamming :D 2024-05-30 19:35:44 +02:00
Andras Bacsai
f8d607b06f chore: Update version numbers to 4.0.0-beta.292 2024-05-30 19:35:38 +02:00
Andras Bacsai
cfadeb07b1 Merge pull request #2317 from coollabsio/next
v4.0.0-beta.291
2024-05-30 13:06:30 +02:00
Andras Bacsai
072850be0b Refactor ApplicationDeploymentJob.php to remove unnecessary code and improve code structure 2024-05-30 13:02:01 +02:00
Andras Bacsai
71d120bc4e fix: fine-tune cdn pulls 2024-05-30 12:56:29 +02:00
Andras Bacsai
68d3cea528 fix: multiple server deployments
feat: custom preview deployment fqdn
ui: improvements here and there
2024-05-30 12:28:29 +02:00
Andras Bacsai
07e801f44d refactor: Remove unnecessary debug statements and improve code structure in RunRemoteProcess.php and ApplicationDeploymentJob.php 2024-05-30 10:14:48 +02:00
Andras Bacsai
ee5c694aa2 fix: compose previews does have env variables 2024-05-30 10:14:43 +02:00
Andras Bacsai
efa5eb1770 chore: Update version numbers to 4.0.0-beta.291 2024-05-30 10:14:29 +02:00
Esteban Ecallard
e16bd194a3 fix: removal of the failed deployment condition, addition of since started instead of finished time 2024-05-29 12:35:32 +00:00
Esteban Ecallard
a3765c19e3 feat: if the time seems too long it remains at 0s 2024-05-29 12:32:47 +00:00
Evan
ba4be02e75 Merge branch 'next' into vaultwarden-fix 2024-05-28 12:39:54 +01:00
iamEvan
d4f6a86a57 Remove SMTP Env Variables 2024-05-28 12:39:40 +01:00
iamEvan
7c0c1e6cf8 Add Real IP support for Reverse Proxies 2024-05-28 01:12:59 +01:00
iamEvan
39f787b7db fix: sort by defined order + fixed typo 2024-05-27 23:41:42 +01:00
iamEvan
424437446d Added Push Notification Variables 2024-05-27 23:40:35 +01:00
Evan
908c74eb27 Merge branch 'next' into vaultwarden-fix 2024-05-27 17:45:00 +08:00
Evan
97da13c3c4 Merge branch 'coollabsio:main' into vaultwarden-fix 2024-05-27 16:04:02 +08:00
iamEvan
7134b46cdc Fix Vaultwarden + Add Configs 2024-05-27 08:51:32 +01:00
Esteban Ecallard
de3b8a10a0 Update index.blade.php 2024-05-23 15:20:28 +02:00
Esteban Ecallard
8feece702c fix: wrong time during a failed deployment 2024-05-23 15:15:18 +02:00
Iván Salazar
f3fe4433ae update sonarqube template 2024-05-13 21:33:36 -06:00
151 changed files with 1798 additions and 2799 deletions

View File

@@ -1,17 +1,28 @@
name: Bug report
description: Create a new bug report
description: 'Create a new bug report.'
title: '[Bug]: '
body:
- type: markdown
attributes:
value: >-
# 💎 Bounty program (with
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
If you would like to prioritize the issue resolution, you can add bounty
to this issue.
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
get started.
- 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
description: Please provide a step by step guide to reproduce the issue.
validations:
required: true
- type: textarea

View File

@@ -42,6 +42,7 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
<a href="https://www.runpod.io/?utm_source=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>

View File

@@ -5,7 +5,6 @@ namespace App\Actions\CoolifyTask;
use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus;
use App\Jobs\ApplicationDeploymentJob;
use App\Jobs\ApplicationDeploymentJobNew;
use App\Models\Server;
use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB;
@@ -166,23 +165,13 @@ class RunRemoteProcess
public function encodeOutput($type, $output)
{
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
if (isDev()) {
$outputStack[] = [
'type' => $type,
'output' => $output,
'timestamp' => hrtime(true),
'batch' => ApplicationDeploymentJobNew::$batch_counter,
'order' => $this->getLatestCounter(),
];
} else {
$outputStack[] = [
'type' => $type,
'output' => $output,
'timestamp' => hrtime(true),
'batch' => ApplicationDeploymentJob::$batch_counter,
'order' => $this->getLatestCounter(),
];
}
$outputStack[] = [
'type' => $type,
'output' => $output,
'timestamp' => hrtime(true),
'batch' => ApplicationDeploymentJob::$batch_counter,
'order' => $this->getLatestCounter(),
];
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
}

View File

@@ -5,7 +5,6 @@ namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Support\Facades\Log;
class UpdateCoolify
{
@@ -14,7 +13,7 @@ class UpdateCoolify
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public function handle()
public function handle($manual_update = false)
{
try {
$settings = InstanceSettings::get();
@@ -26,24 +25,19 @@ class UpdateCoolify
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
if (!$settings->is_auto_update_enabled) {
Log::info('Auto update is disabled');
throw new \Exception('Auto update is disabled');
if (!$manual_update) {
if (!$settings->is_auto_update_enabled) {
return;
}
if ($this->latestVersion === $this->currentVersion) {
return;
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return;
}
}
if ($this->latestVersion === $this->currentVersion) {
Log::info('Already on latest version');
throw new \Exception('Already on latest version');
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
Log::info('Latest version is lower than current version?!');
throw new \Exception('Latest version is lower than current version?!');
}
Log::info("Updating from {$this->currentVersion} -> {$this->latestVersion}");
$this->update();
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e;
}
}
@@ -51,16 +45,15 @@ class UpdateCoolify
private function update()
{
if (isDev()) {
instant_remote_process([
remote_process([
"sleep 10"
], $this->server);
return;
}
instant_remote_process([
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
return;
}
}

View File

@@ -26,7 +26,6 @@ class ServicesGenerate extends Command
*/
public function handle()
{
// ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
@@ -63,6 +62,7 @@ class ServicesGenerate extends Command
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
$documentation = str($documentation)->append('?utm_source=coolify.io');
} else {
$documentation = 'https://coolify.io/docs';
}

View File

@@ -6,13 +6,12 @@ use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesAndVersions;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -30,35 +29,33 @@ class Kernel extends ConsoleKernel
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullTemplatesAndVersions)->everyTenMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullTemplatesAndVersions)->everyTenMinutes()->onOneServer();
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
$this->pull_images($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes();
}
}
private function pull_helper_image($schedule)
private function pull_images($schedule)
{
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
@@ -89,16 +86,6 @@ class Kernel extends ConsoleKernel
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule)
{
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
if ($settings->is_auto_update_enabled) {
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
}
}
private function check_scheduled_backups($schedule)
{
$scheduled_backups = ScheduledDatabaseBackup::all();

View File

@@ -410,11 +410,12 @@ class Github extends Controller
if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$container_name = generateApplicationContainerName($application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
@@ -430,7 +431,6 @@ class Github extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());

View File

@@ -202,7 +202,7 @@ class Gitlab extends Controller
]);
ray('Preview deployments disabled for ' . $application->name);
}
} else if ($action === 'closed' || $action === 'close') {
} else if ($action === 'closed' || $action === 'close' || $action === 'merge') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();

View File

@@ -67,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Save original server between phases
private Server $original_server;
private Server $mainServer;
private bool $is_this_additional_server = false;
private ?ApplicationPreview $preview = null;
private ?string $git_type = null;
private bool $only_this_server = false;
@@ -123,6 +124,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
$this->only_this_server = $this->application_deployment_queue->only_this_server;
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
@@ -136,6 +138,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
$this->server = $this->mainServer = $this->destination->server;
$this->serverUser = $this->server->user;
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
@@ -149,28 +153,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Set preview fqdn
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
if (str($this->application->fqdn)->contains(',')) {
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->application->fqdn);
if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
}
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$this->preview->fqdn = $preview_fqdn;
$this->preview->save();
}
$this->preview = $this->application->generate_preview_fqdn($this->pull_request_id);
if ($this->application->is_github_based()) {
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
}
@@ -284,7 +267,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function decide_what_to_do()
{
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
if ($this->restart_only) {
$this->just_restart();
return;
} else if ($this->pull_request_id !== 0) {
@@ -306,7 +289,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function post_deployment()
{
if ($this->server->isProxyShouldRun()) {
GetContainersStatus::dispatch($this->server);
// dispatch(new ContainerStatusJob($this->server));
@@ -334,18 +316,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
$this->generate_image_names();
// Always rebuild dockerfile based container.
// if (!$this->force_rebuild) {
// $this->check_image_locally_or_remotely();
// if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
// $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
// $this->generate_compose_file();
// $this->push_to_docker_registry();
// $this->rolling_update();
// return;
// }
// }
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@@ -375,9 +345,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
if (data_get($this->application, 'docker_compose_custom_start_command')) {
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) {
$this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
}
}
if (data_get($this->application, 'docker_compose_custom_build_command')) {
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) {
$this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
}
}
if ($this->pull_request_id === 0) {
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
@@ -393,15 +369,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
$yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables();
} else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id'));
$this->save_environment_variables();
if (!is_null($this->env_filename)) {
$services = collect($composeFile['services']);
$services = $services->map(function ($service, $name) {
$service['env_file'] = [$this->env_filename];
return $service;
});
$composeFile['services'] = $services->toArray();
}
if (is_null($composeFile)) {
$this->application_deployment_queue->addLogEntry("Failed to parse docker-compose file.");
$this->fail("Failed to parse docker-compose file.");
return;
}
$yaml = Yaml::dump($composeFile->toArray(), 10);
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]);
$this->save_environment_variables();
// Build new container to limit downtime.
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
@@ -410,13 +400,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
);
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
}
$this->stop_running_container(force: true);
$this->application_deployment_queue->addLogEntry("Starting new application.");
$networkId = $this->application->uuid;
if ($this->pull_request_id !== 0) {
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
@@ -441,9 +436,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
["command" => $command, "hidden" => true],
);
}
} else {
@@ -453,8 +454,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
);
$this->write_deployment_configurations();
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
$this->write_deployment_configurations();
}
@@ -473,16 +479,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->clone_repository();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@@ -502,21 +503,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
ray('pushing to docker registry');
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
if ($this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
}
$this->clone_repository();
$this->cleanup_git();
@@ -535,15 +527,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@@ -611,7 +598,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ray('additional_servers');
$forceFail = true;
}
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
if ($this->is_this_additional_server) {
ray('this is an additional_servers, no pushy pushy');
return;
}
@@ -626,8 +613,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
// Tag image with docker_registry_image_tag
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
@@ -637,7 +624,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
if ($forceFail) {
@@ -668,9 +654,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_tag) {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
// if ($this->application->docker_registry_image_tag) {
// $this->dockerImageTag = $this->application->docker_registry_image_tag;
// }
if ($this->application->docker_registry_image_name) {
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}";
@@ -685,19 +671,42 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if ($this->should_skip_build()) {
return;
}
}
private function should_skip_build()
{
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->generate_compose_file();
$this->rolling_update();
$this->post_deployment();
if ($this->is_this_additional_server) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->restart_only) {
$this->post_deployment();
}
return true;
}
if (!$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return true;
} else {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
} else {
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application.");
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Building new image.");
}
if ($this->restart_only) {
$this->restart_only = false;
$this->decide_what_to_do();
}
return false;
}
private function check_image_locally_or_remotely()
{
@@ -754,7 +763,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
@@ -787,7 +796,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
foreach ($sorted_environment_variables as $env) {
@@ -795,10 +804,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
ray($real_value);
}
}
$envs->push($env->key . '=' . $real_value);
@@ -864,7 +874,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
);
}
}
}
@@ -1031,7 +1040,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@@ -1153,10 +1161,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]));
}
}
private function set_base_dir()
{
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
}
private function set_coolify_variables()
{
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
@@ -1945,11 +1949,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$value = escapeshellarg($env->real_value);
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $value);
}
$this->build_args->push("--build-arg {$env->key}={$value}");
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
$value = escapeshellarg($env->real_value);
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $value);
}
$this->build_args->push("--build-arg {$env->key}={$value}");
}
}
@@ -1965,10 +1975,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $env->real_value);
} else {
$value = $env->real_value;
}
$dockerfile->splice(1, 0, "ARG {$env->key}={$value}");
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
$value = str_replace("\n", "\\\n", $env->real_value);
} else {
$value = $env->real_value;
}
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
}
}
@@ -1988,7 +2008,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($containers->count() == 0) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output).");
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output/errors).");
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
@@ -2011,6 +2031,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (empty($this->application->post_deployment_command)) {
return;
}
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output).");
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
@@ -2019,11 +2040,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
'command' => $exec, 'hidden' => true
],
);
try {
$this->execute_remote_command(
[
'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output'
],
);
} catch (Exception $e) {
$post_deployment_command_output = $this->saved_outputs->get('post-deployment-command-output');
if ($post_deployment_command_output) {
$this->application_deployment_queue->addLogEntry("Post-deployment command failed.");
$this->application_deployment_queue->addLogEntry($post_deployment_command_output, 'stderr');
}
}
return;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
{
try {
if ($this->application->is_public_repository()) {
ray('Public repository. Skipping comment update.');
return;
}
if ($this->status === ProcessStatus::CLOSED) {
@@ -59,7 +60,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
}
} catch (\Throwable $e) {
ray($e);
throw $e;
return $e;
}
}

View File

@@ -40,7 +40,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
return false;
}
}
public function handle(): void
public function handle()
{
// ray("checking log drain statuses for {$this->server->id}");
try {
@@ -80,9 +80,9 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
}
}
} catch (\Throwable $e) {
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
if (!isCloud()) send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
handleError($e);
return handleError($e);
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function __construct()
{
}
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$server = Server::findOrFail(0);
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
}
$latest_version = get_latest_version_of_coolify();
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
$settings = InstanceSettings::get();
$current_version = config('version');
if (!$settings->is_auto_update_enabled) {
return;
}
if ($latest_version === $current_version) {
return;
}
if (version_compare($latest_version, $current_version, '<')) {
return;
}
instant_remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $latest_version"
], $server);
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -12,7 +12,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullTemplatesAndVersions implements ShouldQueue, ShouldBeEncrypted
class PullTemplatesFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -23,21 +23,6 @@ class PullTemplatesAndVersions implements ShouldQueue, ShouldBeEncrypted
}
public function handle(): void
{
try {
if (!isDev() && !isCloud()) {
ray('PullTemplatesAndVersions versions.json');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage());
ray($e->getMessage());
}
try {
if (!isDev()) {
ray('PullTemplatesAndVersions service-templates');

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullVersionsFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 10;
public function __construct()
{
}
public function handle(): void
{
try {
if (!isDev() && !isCloud()) {
ray('PullTemplatesAndVersions versions.json');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -141,7 +141,7 @@ class General extends Component
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
$this->initLoadingCompose = true;
$this->dispatch('info', 'Loading docker compose file...');
$this->dispatch('info', 'Loading docker compose file.');
}
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
@@ -287,7 +287,7 @@ class General extends Component
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
}
}
@@ -352,7 +352,7 @@ class General extends Component
$domain = data_get($service, 'domain');
if ($domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
check_domain_usage(resource: $this->application);
}

View File

@@ -25,6 +25,7 @@ class Heading extends Component
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
"compose_loaded" => '$refresh',
"update_links" => '$refresh',
];
}
public function mount()

View File

@@ -2,10 +2,12 @@
namespace App\Livewire\Project\Application;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class Previews extends Component
@@ -16,6 +18,9 @@ class Previews extends Component
public Collection $pull_requests;
public int $rate_limit_remaining;
protected $rules = [
'application.previews.*.fqdn' => 'string|nullable',
];
public function mount()
{
$this->pull_requests = collect();
@@ -33,7 +38,88 @@ class Previews extends Component
return handleError($e, $this);
}
}
public function save_preview($preview_id)
{
try {
$success = true;
$preview = $this->application->previews->find($preview_id);
if (data_get_str($preview, 'fqdn')->isNotEmpty()) {
$preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->trim()->lower();
if (!validate_dns_entry($preview->fqdn, $this->application->destination->server)) {
$this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$success = false;
}
check_domain_usage(resource: $this->application, domain: $preview->fqdn);
}
if (!$preview) {
throw new \Exception('Preview not found');
}
$success && $preview->save();
$success && $this->dispatch('success', 'Preview saved.<br><br>Do not forget to redeploy the preview to apply the changes.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function generate_preview($preview_id)
{
$preview = $this->application->previews->find($preview_id);
if (!$preview) {
$this->dispatch('error', 'Preview not found.');
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
$url = Url::fromString($fqdn);
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
$this->dispatch('success', 'Domain generated.');
}
public function add(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
if ($this->application->build_pack === 'dockercompose') {
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found && !is_null($pull_request_html_url)) {
$found = ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $this->application->docker_compose_domains,
]);
}
$found->generate_preview_fqdn_compose();
$this->application->refresh();
} else {
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found && !is_null($pull_request_html_url)) {
$found = ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
$this->application->generate_preview_fqdn($pull_request_id);
$this->application->refresh();
$this->dispatch('update_links');
$this->dispatch('success', 'Preview added.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
@@ -82,17 +168,31 @@ class Previews extends Component
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
$this->application->refresh();
GetContainersStatus::dispatchSync($this->application->destination->server);
$this->dispatch('reloadWindow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function previewRefresh()
public function delete(int $pull_request_id)
{
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
try {
if ($this->application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
} else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
$this->application->refresh();
$this->dispatch('update_links');
$this->dispatch('success', 'Preview deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\ApplicationPreview;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class PreviewsCompose extends Component
{
public $service;
public $serviceName;
public ApplicationPreview $preview;
public function render()
{
return view('livewire.project.application.previews-compose');
}
public function save()
{
$domain = data_get($this->service, 'domain');
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$this->serviceName]['domain'] = $domain;
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
$this->preview->save();
$this->dispatch('update_links');
$this->dispatch('success', 'Domain saved.');
}
public function generate()
{
$domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect();
$domain = $domains->first(function ($_, $key) {
return $key === $this->serviceName;
});
if ($domain) {
$domain = data_get($domain, 'domain');
$url = Url::fromString($domain);
$template = $this->preview->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn;
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
$this->preview->save();
}
$this->dispatch('update_links');
$this->dispatch('success', 'Domain generated.');
}
}

View File

@@ -7,7 +7,6 @@ use Livewire\Component;
class Index extends Component
{
public $database;
public $s3s;
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
@@ -36,7 +35,6 @@ class Index extends Component
]);
}
$this->database = $database;
$this->s3s = currentTeam()->s3s;
}
public function render()
{

View File

@@ -50,7 +50,7 @@ class BackupExecutions extends Component
public function refreshBackupExecutions(): void
{
if ($this->backup) {
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
$this->executions = $this->backup->executions()->get()->sortBy('created_at');
}
}
}

View File

@@ -4,12 +4,14 @@ namespace App\Livewire\Project\Database\Clickhouse;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneClickhouse;
use Exception;
use Livewire\Component;
class General extends Component
{
public Server $server;
public StandaloneClickhouse $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,11 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -25,6 +25,7 @@ class CreateScheduledBackup extends Component
];
public function mount()
{
$this->s3s = currentTeam()->s3s;
if ($this->s3s->count() > 0) {
$this->s3_storage_id = $this->s3s->first()->id;
}

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Dragonfly;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneDragonfly;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneDragonfly $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -41,10 +43,11 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Keydb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneKeydb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneKeydb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mariadb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMariadb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMariadb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -50,10 +52,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mongodb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMongodb;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMongodb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -48,11 +50,13 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mysql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneMysql;
use Exception;
use Livewire\Component;
@@ -13,6 +14,7 @@ class General extends Component
protected $listeners = ['refresh'];
public StandaloneMysql $database;
public Server $server;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -50,11 +52,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
@@ -13,6 +14,7 @@ use function Aws\filter;
class General extends Component
{
public StandalonePostgresql $database;
public Server $server;
public string $new_filename;
public string $new_content;
public ?string $db_url = null;
@@ -57,11 +59,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\Server;
use App\Models\StandaloneRedis;
use Exception;
use Livewire\Component;
@@ -12,6 +13,7 @@ class General extends Component
{
protected $listeners = ['refresh'];
public Server $server;
public StandaloneRedis $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
@@ -43,10 +45,12 @@ class General extends Component
if ($this->database->is_public) {
$this->db_url_public = $this->database->get_db_url();
}
$this->server = data_get($this->database,'destination.server');
}
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;

View File

@@ -6,6 +6,7 @@ use App\Models\Application;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\Project;
use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Carbon\Carbon;
@@ -33,6 +34,8 @@ class PublicGitRepository extends Component
public $build_pack = 'nixpacks';
public bool $show_is_static = true;
public bool $new_compose_services = false;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -177,6 +180,31 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services ) {
$server = $destination->server;
$new_service = [
'name' => 'service' . str()->random(10),
'docker_compose_raw' => 'coolify',
'environment_id' => $environment->id,
'server_id' => $server->id,
];
if ($this->git_source === 'other') {
$new_service['git_repository'] = $this->git_repository;
$new_service['git_branch'] = $this->git_branch;
} else {
$new_service['git_repository'] = $this->git_repository;
$new_service['git_branch'] = $this->git_branch;
$new_service['source_id'] = $this->git_source->id;
$new_service['source_type'] = $this->git_source->getMorphClass();
}
$service = Service::create($new_service);
return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
return;
}
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),

View File

@@ -10,6 +10,7 @@ use Livewire\Component;
class Create extends Component
{
public $type;
public $project;
public function mount()
{
$type = str(request()->query('type'));
@@ -20,6 +21,7 @@ class Create extends Component
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
if (!$environment) {
return redirect()->route('dashboard');

View File

@@ -42,7 +42,7 @@ class StackForm extends Component
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortDesc();
$this->fields = $this->fields->sortBy('name');
}
public function saveCompose($raw)
{
@@ -52,7 +52,7 @@ class StackForm extends Component
public function instantSave()
{
$this->service->save();
$this->dispatch('success', 'Service settings saved.');
$this->dispatch('success', 'Service settings saved.');
}
public function submit()

View File

@@ -43,6 +43,11 @@ class GetLogs extends Component
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
}
}
}
public function doSomethingWithThisChunkOfOutput($output)
@@ -77,13 +82,6 @@ class GetLogs extends Component
if (!$this->server->isFunctional()) {
return;
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
} else {
$this->pull_request = 'branch';
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
if (!$this->numberOfLines) {
$this->numberOfLines = 1000;

View File

@@ -103,6 +103,14 @@ class Logs extends Component
}
}
$this->containers = $this->containers->sort();
if (data_get($this->query,'pull_request_id')) {
$this->containers = $this->containers->filter(function ($container) {
return str_contains($container, $this->query['pull_request_id']);
});
ray($this->containers);
}
$this->loadMetrics();
} catch (\Exception $e) {
return handleError($e, $this);

View File

@@ -70,9 +70,8 @@ class Configuration extends Component
$this->validate();
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}

View File

@@ -11,16 +11,16 @@ use Livewire\Component;
class Change extends Component
{
public string $webhook_endpoint;
public ?string $ipv4;
public ?string $ipv6;
public ?string $fqdn;
public ?string $ipv4 = null;
public ?string $ipv6 = null;
public ?string $fqdn = null;
public ?bool $default_permissions = true;
public ?bool $preview_deployment_permissions = true;
public ?bool $administration = false;
public $parameters;
public ?GithubApp $github_app;
public ?GithubApp $github_app = null;
public string $name;
public bool $is_system_wide;

View File

@@ -17,6 +17,10 @@ class InviteLink extends Component
public string $email;
public string $role = 'member';
protected $rules = [
'email' => 'required|email',
'role' => 'required|string',
];
public function mount()
{
$this->email = isDev() ? 'test3@example.com' : '';
@@ -34,6 +38,7 @@ class InviteLink extends Component
private function generate_invite_link(bool $sendEmail = false)
{
try {
$this->validate();
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");

View File

@@ -5,11 +5,9 @@ namespace App\Livewire;
use App\Actions\Server\UpdateCoolify;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $updateInProgress = false;
public bool $isUpgradeAvailable = false;
@@ -31,9 +29,8 @@ class Upgrade extends Component
if ($this->updateInProgress) {
return;
}
$this->rateLimit(1, 60);
$this->updateInProgress = true;
UpdateCoolify::run();
UpdateCoolify::run(manual_update: true);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -861,14 +861,10 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false);
}
function parseCompose(int $pull_request_id = 0)
function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->docker_compose_raw) {
$mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id);
if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) {
parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true);
}
return $mainCompose;
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else {
return collect([]);
}
@@ -1052,4 +1048,30 @@ class Application extends BaseModel
}
}
}
function generate_preview_fqdn(int $pull_request_id)
{
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id);
if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) {
if (str($this->fqdn)->contains(',')) {
$url = Url::fromString(str($this->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->fqdn);
if (data_get($preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn'));
}
}
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
}
return $preview;
}
}

View File

@@ -2,6 +2,9 @@
namespace App\Models;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class ApplicationPreview extends BaseModel
{
protected $guarded = [];
@@ -34,4 +37,26 @@ class ApplicationPreview extends BaseModel
{
return $this->belongsTo(Application::class);
}
function generate_preview_fqdn_compose()
{
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect();
foreach ($domains as $service_name => $domain) {
$domain = data_get($domain, 'domain');
$url = Url::fromString($domain);
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$docker_compose_domains = data_get($this, 'docker_compose_domains');
$docker_compose_domains = json_decode($docker_compose_domains, true);
$docker_compose_domains[$service_name]['domain'] = $preview_fqdn;
$docker_compose_domains = json_encode($docker_compose_domains);
$this->docker_compose_domains = $docker_compose_domains;
$this->save();
}
}
}

View File

@@ -90,6 +90,7 @@ class EnvironmentVariable extends Model
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
@@ -102,21 +103,35 @@ class EnvironmentVariable extends Model
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose;
return $found_in_compose || $found_in_args;
}
);
}

View File

@@ -897,7 +897,9 @@ $schema://$host {
}
public function validateDockerEngineVersion()
{
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
$dockerVersionRaw = instant_remote_process(["docker version --format json"], $this, false);
$dockerVersionJson = json_decode($dockerVersionRaw, true);
$dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0');
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;

View File

@@ -472,6 +472,72 @@ class Service extends BaseModel
}
$fields->put('Admin', $data->toArray());
break;
case str($image)?->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
$ADMIN_TOKEN = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_ADMIN')->first();
$SIGNUP_ALLOWED = $this->environment_variables()->where('key', 'SIGNUP_ALLOWED')->first();
$PUSH_ENABLED = $this->environment_variables()->where('key', 'PUSH_ENABLED')->first();
$PUSH_INSTALLATION_ID = $this->environment_variables()->where('key', 'PUSH_SERVICE_ID')->first();
$PUSH_INSTALLATION_KEY = $this->environment_variables()->where('key', 'PUSH_SERVICE_KEY')->first();
if ($DATABASE_URL) {
$data = $data->merge([
'Database URL' => [
'key' => data_get($DATABASE_URL, 'key'),
'value' => data_get($DATABASE_URL, 'value'),
],
]);
}
if ($ADMIN_TOKEN) {
$data = $data->merge([
'Admin Password' => [
'key' => data_get($ADMIN_TOKEN, 'key'),
'value' => data_get($ADMIN_TOKEN, 'value'),
'isPassword' => true,
],
]);
}
if ($SIGNUP_ALLOWED) {
$data = $data->merge([
'Signup Allowed' => [
'key' => data_get($SIGNUP_ALLOWED, 'key'),
'value' => data_get($SIGNUP_ALLOWED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_ENABLED) {
$data = $data->merge([
'Push Enabled' => [
'key' => data_get($PUSH_ENABLED, 'key'),
'value' => data_get($PUSH_ENABLED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_INSTALLATION_ID) {
$data = $data->merge([
'Push Installation ID' => [
'key' => data_get($PUSH_INSTALLATION_ID, 'key'),
'value' => data_get($PUSH_INSTALLATION_ID, 'value'),
],
]);
}
if ($PUSH_INSTALLATION_KEY) {
$data = $data->merge([
'Push Installation Key' => [
'key' => data_get($PUSH_INSTALLATION_KEY, 'key'),
'value' => data_get($PUSH_INSTALLATION_KEY, 'value'),
'isPassword' => true,
],
]);
}
$fields->put('Vaultwarden', $data);
break;
}
}
$databases = $this->databases()->get();

View File

@@ -22,6 +22,8 @@ class TelegramChannel
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
break;
case 'App\Notifications\Application\StatusChanged':
case 'App\Notifications\Container\ContainerRestarted':
case 'App\Notifications\Container\ContainerStopped':
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
case 'App\Notifications\Application\DeploymentSuccess':

View File

@@ -2,7 +2,6 @@
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\ApplicationDeploymentJob;
use App\Jobs\ApplicationDeploymentJobNew;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
@@ -43,26 +42,14 @@ function queue_application_deployment(Application $application, string $deployme
'only_this_server' => $only_this_server
]);
if (isDev()) {
if ($no_questions_asked) {
dispatch(new ApplicationDeploymentJobNew(
application_deployment_queue_id: $deployment->id,
));
} else if (next_queuable($server_id, $application_id)) {
dispatch(new ApplicationDeploymentJobNew(
application_deployment_queue_id: $deployment->id,
));
}
} else {
if ($no_questions_asked) {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
} else if (next_queuable($server_id, $application_id)) {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
}
if ($no_questions_asked) {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
} else if (next_queuable($server_id, $application_id)) {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
}
}
function force_start_deployment(ApplicationDeploymentQueue $deployment)
@@ -70,15 +57,10 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
if (isDev()) {
dispatch(new ApplicationDeploymentJobNew(
application_deployment_queue_id: $deployment->id,
));
} else {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
}
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
}
function queue_next_deployment(Application $application)
{
@@ -88,15 +70,10 @@ function queue_next_deployment(Application $application)
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
if (isDev()) {
dispatch(new ApplicationDeploymentJobNew(
application_deployment_queue_id: $next_found->id,
));
} else {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
));
}
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
));
}
}

View File

@@ -38,7 +38,7 @@ const SPECIFIC_SERVICES = [
// Based on /etc/os-release
const SUPPORTED_OS = [
'ubuntu debian raspbian',
'centos fedora rhel ol rocky amzn',
'centos fedora rhel ol rocky amzn almalinux',
'sles opensuse-leap opensuse-tumbleweed'
];

View File

@@ -465,13 +465,34 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$appUuid = $appUuid . '-pr-' . $pull_request_id;
}
$labels = collect([]);
if ($application->fqdn) {
if ($pull_request_id !== 0) {
if ($pull_request_id === 0) {
if ($application->fqdn) {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
// Add Caddy labels
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
} else {
if (data_get($preview,'fqdn')) {
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
} else {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
$domains = collect([]);
}
// Add Traefik labels
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
@@ -490,6 +511,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
return $labels->all();
}

View File

@@ -121,7 +121,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
// ray($generatedEnv);
if ($generatedEnv) {
$generatedEnv->value = $fqdn . ':' . $port . $path;
$generatedEnv->value = $fqdn . $path;
$generatedEnv->save();
}
}
@@ -140,7 +140,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$variableName = $variableName . "_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $url . ':' . $port . $path;
$generatedEnv->value = $url . $path;
$generatedEnv->save();
}
}
@@ -160,7 +160,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$env->value = $host . $path;
$env->save();
}
$port_env->value = $host . ':' . $port . $path;
$port_env->value = $host . $path;
$port_env->save();
}
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
@@ -171,7 +171,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$env->value = $url . $path;
$env->save();
}
$port_env_url->value = $url . ':' . $port . $path;
$port_env_url->value = $url . $path;
$port_env_url->save();
}
} else {

View File

@@ -576,6 +576,13 @@ function getTopLevelNetworks(Service|Application $resource)
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@@ -618,6 +625,13 @@ function getTopLevelNetworks(Service|Application $resource)
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@@ -642,9 +656,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $topLevelNetworks->keys();
}
}
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, bool $is_pr = false)
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
{
// ray()->clearAll();
if ($resource->getMorphClass() === 'App\Models\Service') {
if ($resource->docker_compose_raw) {
try {
@@ -666,6 +680,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
}
$definedNetwork = collect([$resource->uuid]);
if ($topLevelVolumes->count() > 0) {
$tempTopLevelVolumes = collect([]);
foreach ($topLevelVolumes as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$tempTopLevelVolumes->put($volumeName, $volume);
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
// Workarounds for beta users.
if ($serviceName === 'registry') {
@@ -764,6 +788,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@@ -815,7 +846,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// default:
// ipv4_address: 192.168.203.254
// $networks->put($serviceNetwork, null);
ray($key);
$networks->put($key, $serviceNetwork);
}
}
@@ -879,6 +909,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]
);
} else if ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
if (is_string($volume)) {
@@ -1246,26 +1282,29 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$isSameDockerComposeFile = false;
if ($resource->dockerComposePrLocation() === $resource->dockerComposeLocation()) {
$isSameDockerComposeFile = true;
$is_pr = false;
}
if ($is_pr) {
try {
$yaml = Yaml::parse($resource->docker_compose_pr_raw);
} catch (\Exception $e) {
return;
}
} else {
try {
$yaml = Yaml::parse($resource->docker_compose_raw);
} catch (\Exception $e) {
return;
}
try {
$yaml = Yaml::parse($resource->docker_compose_raw);
} catch (\Exception $e) {
return;
}
$server = $resource->destination->server;
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
if ($pull_request_id !== 0) {
$topLevelVolumes = collect([]);
}
if ($topLevelVolumes->count() > 0) {
$tempTopLevelVolumes = collect([]);
foreach ($topLevelVolumes as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$tempTopLevelVolumes->put($volumeName, $volume);
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$services = data_get($yaml, 'services');
@@ -1281,7 +1320,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0) {
$definedNetwork = collect(["{$resource->uuid}-$pull_request_id"]);
}
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id) {
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id, $preview_id) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
$servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
@@ -1329,13 +1368,36 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount");
$topLevelVolumes->put($name, [
'name' => $name,
]);
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $name);
data_set($topLevelVolumes, $name, $v);
}
}
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
@@ -1379,9 +1441,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_set($volume, 'source', $source . ':' . $target);
}
if (!str($source)->startsWith('/')) {
$topLevelVolumes->put($source, [
'name' => $source,
]);
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
}
}
} else {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
}
@@ -1408,6 +1482,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@@ -1639,21 +1720,32 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($fqdns) {
$fqdns = str($fqdns)->explode(',');
if ($pull_request_id !== 0) {
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
$url = Url::fromString($fqdn);
$template = $resource->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
$preview = $resource->previews()->find($preview_id);
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
if ($docker_compose_domains->count() > 0) {
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
if ($found_fqdn) {
$fqdns = collect($found_fqdn);
} else {
$fqdns = collect([]);
}
} else {
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
$url = Url::fromString($fqdn);
$template = $resource->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
}
}
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $resource->uuid,
@@ -1720,13 +1812,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
} else {
if ($is_pr) {
$resource->docker_compose_pr_raw = Yaml::dump($yaml, 10, 2);
$resource->docker_compose_pr = Yaml::dump($finalServices, 10, 2);
} else {
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
}
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
}
$resource->save();
return collect($finalServices);

View File

@@ -7,7 +7,7 @@ return [
// 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.290',
'release' => '4.0.0-beta.296',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.290';
return '4.0.0-beta.296';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->text('docker_compose_domains')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->dropColumn('docker_compose_domains');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->string('pull_request_issue_comment_id')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_previews', function (Blueprint $table) {
$table->integer('pull_request_issue_comment_id')->nullable()->change();
});
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('services', function (Blueprint $table) {
$table->string('git_repository')->nullable();
$table->string('git_branch')->nullable();
$table->nullableMorphs('source');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('git_repository');
$table->dropColumn('git_branch');
$table->dropMorphs('source');
});
}
};

View File

@@ -2,15 +2,15 @@ FROM alpine:3.17
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=26.1.2
ARG DOCKER_VERSION=26.1.3
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.27.0
ARG DOCKER_COMPOSE_VERSION=2.27.1
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.14.0
ARG DOCKER_BUILDX_VERSION=0.14.1
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.33.2
ARG PACK_VERSION=0.34.1
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.21.3
ARG NIXPACKS_VERSION=1.24.1
USER root
WORKDIR /artifacts

View File

@@ -31,7 +31,7 @@ RUN apt-get update
RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
# Coolify requirements
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf

30
lang/fa.json Normal file
View File

@@ -0,0 +1,30 @@
{
"auth.login": "ورود",
"auth.login.azure": "ورود با مایکروسافت",
"auth.login.bitbucket": "ورود با Bitbucket",
"auth.login.github": "ورود با گیت هاب",
"auth.login.gitlab": "ورود با گیت لب",
"auth.login.google": "ورود با گوگل",
"auth.already_registered": "قبلاً ثبت نام کرده‌اید؟",
"auth.confirm_password": "تایید رمز عبور",
"auth.forgot_password": "فراموشی رمز عبور",
"auth.forgot_password_send_email": "ارسال ایمیل بازیابی رمز عبور",
"auth.register_now": "ثبت نام",
"auth.logout": "خروج",
"auth.register": "ثبت نام",
"auth.registration_disabled": "ثبت نام غیر فعال است. لطفا با ادمین تماس بگیرید.",
"auth.reset_password": "بازیابی رمز عبور",
"auth.failed": "این اطلاعات با سوابق ما مطابقت ندارد.",
"auth.failed.callback": "پردازش بازگشت از ارائه‌دهنده ورود با شکست مواجه شد.",
"auth.failed.password": "رمز عبور ارائه شده نادرست است.",
"auth.failed.email": "ما نمی توانیم کاربر با آدرس ایمیل مورد نظر را پیدا کنیم.",
"auth.throttle": "تعداد تلاش‌های ورود بیش از حد است. لطفاً در :seconds ثانیه دوباره تلاش کنید.",
"input.name": "نام",
"input.email": "ایمیل",
"input.password": "رمز عبور",
"input.password.again": "تکرار رمز عبور",
"input.code": "کد یک‌ بار مصرف",
"input.recovery_code": "کد بازیابی",
"button.save": "ذخیره",
"repository.url": "<span class='text-helper'>مثال‌ها</span><br>برای مخازن عمومی، از <span class='text-helper'>https://...</span> استفاده کنید.<br>برای مخازن خصوصی، از <span class='text-helper'>git@...</span> استفاده کنید.<br><br>شاخه <span class='text-helper'>main</span> انتخاب خواهد شد.<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify شاخه <span class='text-helper'>nodejs-fastify</span> انتخاب خواهد شد.<br>https://gitea.com/sedlav/expressjs.git شاخه <span class='text-helper'>main</span> انتخاب خواهد شد.<br>https://gitlab.com/andrasbacsai/nodejs-example.git شاخه <span class='text-helper'>main</span> انتخاب خواهد شد."
}

View File

@@ -0,0 +1,6 @@
<svg width="520" height="520" viewBox="0 0 520 520" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M422.968 205.942C413.422 191.118 400.042 177.995 383.222 166.926C350.724 145.574 308.024 133.811 262.987 133.811C247.939 133.811 233.115 135.12 218.724 137.71C209.796 129.114 199.357 121.382 188.301 115.272C147.282 94.8216 111.134 102.436 92.8693 109.004C86.8687 111.163 85.0173 118.752 89.4554 123.331C102.336 136.624 123.647 162.896 118.408 186.787C98.0427 207.577 87 232.646 87 258.748C87 285.347 98.0427 310.416 118.408 331.206C123.647 355.097 102.336 381.382 89.4554 394.675C85.0304 399.241 86.8687 406.83 92.8693 408.989C111.134 415.557 147.282 423.185 188.314 402.735C199.37 396.625 209.809 388.892 218.737 380.296C233.128 382.887 247.953 384.195 263 384.195C308.05 384.195 350.751 372.446 383.235 351.093C400.055 340.024 413.435 326.914 422.981 312.077C433.617 295.566 439 277.785 439 259.258C438.987 240.234 433.603 222.467 422.968 205.942ZM261.149 353.383C241.676 353.383 223.11 350.871 206.185 346.331L193.816 358.224C187.093 364.687 179.215 370.536 170.995 375.141C160.11 380.466 149.356 383.384 138.721 384.26C139.325 383.175 139.876 382.076 140.467 380.976C152.862 358.211 156.21 337.748 150.499 319.601C130.225 303.678 118.067 283.293 118.067 261.09C118.067 210.129 182.13 168.81 261.149 168.81C340.167 168.81 404.244 210.129 404.244 261.09C404.244 312.064 340.181 353.383 261.149 353.383Z" fill="#F5455C"/>
<path d="M192.7 239.868C181.053 239.868 171.612 249.236 171.612 260.789C171.612 272.342 181.053 281.71 192.7 281.71C204.346 281.71 213.787 272.342 213.787 260.789C213.787 249.236 204.346 239.868 192.7 239.868Z" fill="#F5455C"/>
<path d="M260.571 239.868C248.925 239.868 239.484 249.236 239.484 260.789C239.484 272.342 248.925 281.71 260.571 281.71C272.218 281.71 281.659 272.342 281.659 260.789C281.659 249.236 272.218 239.868 260.571 239.868Z" fill="#F5455C"/>
<path d="M328.455 239.868C316.808 239.868 307.367 249.236 307.367 260.789C307.367 272.342 316.808 281.71 328.455 281.71C340.101 281.71 349.542 272.342 349.542 260.789C349.542 249.236 340.101 239.868 328.455 239.868Z" fill="#F5455C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -45,24 +45,48 @@
</a>
@endforeach
@endif
@if (data_get($application, 'previews', collect([]))->count() > 0)
@foreach (data_get($application, 'previews') as $preview)
@if (data_get($preview, 'fqdn'))
<a class="dropdown-item" target="_blank"
href="{{ getFqdnWithoutPort(data_get($preview, 'fqdn')) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 15l6 -6" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>
PR{{ data_get($preview, 'pull_request_id') }} |
{{ data_get($preview, 'fqdn') }}
</a>
@endif
@endforeach
@if (data_get($application, 'previews', collect())->count() > 0)
@if (data_get($application, 'build_pack') === 'dockercompose')
@foreach ($application->previews as $preview)
@foreach (collect(json_decode($preview->docker_compose_domains)) as $fqdn)
@if (data_get($fqdn, 'domain'))
@foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
<a class="dropdown-item" target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 15l6 -6" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>PR{{ data_get($preview, 'pull_request_id') }} |
{{ getFqdnWithoutPort($domain) }}
</a>
@endforeach
@endif
@endforeach
@endforeach
@else
@foreach (data_get($application, 'previews') as $preview)
@if (data_get($preview, 'fqdn'))
<a class="dropdown-item" target="_blank"
href="{{ getFqdnWithoutPort(data_get($preview, 'fqdn')) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 15l6 -6" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
</svg>
PR{{ data_get($preview, 'pull_request_id') }} |
{{ data_get($preview, 'fqdn') }}
</a>
@endif
@endforeach
@endif
@endif
@if (data_get($application, 'ports_mappings_array'))
@foreach ($application->ports_mappings_array as $port)

View File

@@ -1,54 +1,68 @@
@props([
'title' => 'Are you sure?',
'buttonTitle' => 'Open Modal',
'isErrorButton' => false,
'buttonTitle' => 'REWRITE THIS BUTTON TITLE PLEASSSSEEEE',
'buttonFullWidth' => false,
'customButton' => null,
'disabled' => false,
'action' => 'delete',
'content' => null,
])
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" :class="{ 'z-40': modalOpen }"
class="relative w-auto h-auto">
@if ($content)
<div @click="modalOpen=true">
{{ $content }}
</div>
@else
@if ($disabled)
@if ($buttonFullWidth)
<x-forms.button class="w-full" isError disabled wire:target>
{{ $buttonTitle }}
</x-forms.button>
@else
<x-forms.button isError disabled wire:target>
{{ $buttonTitle }}
</x-forms.button>
@endif
@elseif ($isErrorButton)
@if ($buttonFullWidth)
<x-forms.button class="w-full" isError @click="modalOpen=true">
{{ $buttonTitle }}
</x-forms.button>
@else
<x-forms.button isError @click="modalOpen=true">
{{ $buttonTitle }}
</x-forms.button>
@endif
@if ($customButton)
@if ($buttonFullWidth)
<x-forms.button @click="modalOpen=true" class="w-full">
{{ $customButton }}
</x-forms.button>
@else
@if ($buttonFullWidth)
<x-forms.button @click="modalOpen=true" class="flex w-full gap-2" wire:target>
{{ $buttonTitle }}
</x-forms.button>
<x-forms.button @click="modalOpen=true">
{{ $customButton }}
</x-forms.button>
@endif
@else
@if ($content)
<div @click="modalOpen=true">
{{ $content }}
</div>
@else
@if ($disabled)
@if ($buttonFullWidth)
<x-forms.button class="w-full" isError disabled wire:target>
{{ $buttonTitle }}
</x-forms.button>
@else
<x-forms.button isError disabled wire:target>
{{ $buttonTitle }}
</x-forms.button>
@endif
@elseif ($isErrorButton)
@if ($buttonFullWidth)
<x-forms.button class="w-full" isError @click="modalOpen=true">
{{ $buttonTitle }}
</x-forms.button>
@else
<x-forms.button isError @click="modalOpen=true">
{{ $buttonTitle }}
</x-forms.button>
@endif
@else
<x-forms.button @click="modalOpen=true" class="flex gap-2" wire:target>
{{ $buttonTitle }}
</x-forms.button>
@if ($buttonFullWidth)
<x-forms.button @click="modalOpen=true" class="flex w-full gap-2" wire:target>
{{ $buttonTitle }}
</x-forms.button>
@else
<x-forms.button @click="modalOpen=true" class="flex gap-2" wire:target>
{{ $buttonTitle }}
</x-forms.button>
@endif
@endif
@endif
@endif
<template x-teleport="body">
<div x-show="modalOpen"
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen" style="zoom:1.1;" x-cloak>
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen"
style="zoom:1.1;" x-cloak>
<div x-show="modalOpen" x-transition:enter="ease-out duration-100" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="modalOpen=false"

View File

@@ -1,4 +1,7 @@
<x-layout>
<x-slot:title>
Destinations | Coolify
</x-slot>
<div class="flex items-start gap-2">
<h1>Destinations</h1>
@if ($servers->count() > 0)

View File

@@ -6,7 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://api.fonts.coollabs.io" crossorigin>
<link rel="dns-prefetch" href="https://api.fonts.coollabs.io" />
<link rel="preload" href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap" as="style" />
<link rel="preload" href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap"
as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/400.woff2" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/500.woff2" as="style" />
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/600.woff2" as="style" />
@@ -14,7 +15,7 @@
<link rel="preload" href="https://cdn.fonts.coollabs.io/inter/normal/800.woff2" as="style" />
<link href="https://api.fonts.coollabs.io/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta name="robots" content="noindex">
<title>Coolify</title>
<title>{{ $title ?? 'Coolify' }}</title>
@env('local')
<link rel="icon" href="{{ asset('favicon-dev.png') }}" type="image/x-icon" />
@else
@@ -44,7 +45,10 @@
<body>
<x-toast />
<script data-navigate-once>
if (localStorage.theme === 'dark') {
if (!('theme' in localStorage)) {
localStorage.theme = 'dark';
document.documentElement.classList.add('dark')
} else if (localStorage.theme === 'dark') {
document.documentElement.classList.add('dark')
} else if (localStorage.theme === 'light') {
document.documentElement.classList.remove('dark')

View File

@@ -1,4 +1,7 @@
@php use App\Enums\ProxyTypes; @endphp
<x-slot:title>
Onboarding | Coolify
</x-slot>
<section class="flex flex-col h-full lg:items-center lg:justify-center">
<div
class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow lg:p-20 dark:bg-transparent dark:border-none max-w-7xl ">

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Command Center | Coolify
</x-slot>
<h1>Command Center</h1>
<div class="subtitle">Execute commands on your servers without leaving the browser.</div>
@if ($servers->count() > 0)

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Dashboard | Coolify
</x-slot>
@if (session('error'))
<span x-data x-init="$wire.emit('error', '{{ session('error') }}')" />
@endif

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Notifications | Coolify
</x-slot>
<x-notification.navbar />
<form wire:submit='submit' class="flex flex-col gap-4">
<div class="flex items-center gap-2">
@@ -7,7 +10,7 @@
Save
</x-forms.button>
@if ($team->discord_enabled)
<x-forms.button class="dark:text-white normal-case btn btn-xs no-animation btn-primary"
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notifications
</x-forms.button>

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Notifications | Coolify
</x-slot>
<x-notification.navbar />
<form wire:submit='submit' class="flex flex-col gap-4">
<div class="flex items-center gap-2">

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Notifications | Coolify
</x-slot>
<x-notification.navbar />
<form wire:submit='submit' class="flex flex-col gap-4">
<div class="flex items-center gap-2">

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Profile | Coolify
</x-slot>
<h1>Profile</h1>
<div class="subtitle ">Your user profile settings.</div>
<form wire:submit='submit' class="flex flex-col">
@@ -11,7 +14,7 @@
<x-forms.input id="email" label="Email" readonly />
</div>
</form>
<form wire:submit='resetPassword' class="flex flex-col max-w-xl pt-4">
<form wire:submit='resetPassword' class="flex flex-col pt-4">
<div class="flex items-center gap-2 pb-2">
<h2>Change Password</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
@@ -91,7 +94,7 @@
@else
<form action="/user/two-factor-authentication" method="POST">
@csrf
<x-forms.button type="submit">Configure 2FA</x-forms.button>
<x-forms.button type="submit">Configure</x-forms.button>
</form>
@endif
@endif

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($application, 'name')->limit(10) }} > Configuration | Coolify
</x-slot>
<h1>Configuration</h1>
<livewire:project.shared.configuration-checker :resource="$application" />
<livewire:project.application.heading :application="$application" />

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($application, 'name')->limit(10) }} > Deployments | Coolify
</x-slot>
<h1>Deployments</h1>
<livewire:project.shared.configuration-checker :resource="$application" />
<livewire:project.application.heading :application="$application" />
@@ -93,10 +96,11 @@
<div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in
<span class="font-bold" x-text="measure_finished_time()">0s</span>
@else
Running for
Running for <span class="font-bold" x-text="measure_since_started()">0s</span>
@endif
<span class="font-bold" x-text="measure_finished_time()">0s</span>
</div>
</div>
</div>
@@ -135,11 +139,15 @@
}
},
measure_finished_time() {
return this.finished_time;
if (this.finished_time > 2000) {
return 0;
} else {
return this.finished_time;
}
},
measure_since_started() {
return dayjs.utc(created_at).fromNow();
}
},
}))
</script>
@endif

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify
</x-slot>
<h1 class="py-0">Deployment</h1>
<livewire:project.shared.configuration-checker :resource="$application" />
<livewire:project.application.heading :application="$application" />

View File

@@ -38,7 +38,6 @@
</div>
@endif
@if ($application->build_pack === 'dockercompose')
@if (
!is_null($parsedServices) &&
count($parsedServices) > 0 &&
@@ -266,20 +265,24 @@
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.pre_deployment_command"
label="Pre-deployment Command"
helper="An optional script or command to execute in the existing container before the deployment begins." />
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.pre_deployment_command_container"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate"
id="application.pre_deployment_command" label="Pre-deployment "
helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose"
id="application.pre_deployment_command_container" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate"
id="application.post_deployment_command" label="Post-deployment Command"
helper="An optional script or command to execute in the newly built container after the deployment completes." />
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.post_deployment_command_container"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
id="application.post_deployment_command" label="Post-deployment "
helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose"
id="application.post_deployment_command_container" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
</div>
</form>

View File

@@ -8,6 +8,8 @@
<div class="flex flex-col gap-2 pb-4">
<x-forms.input id="application.preview_url_template" label="Preview URL Template"
helper="Templates:<span class='text-helper'>@@{{ random }}</span> to generate random sub-domain each time a PR is deployed, <span class='text-helper'>@@{{ pr_id }}</span> to use pull request ID as sub-domain or <span class='text-helper'>@@{{ domain }}</span> to replace the domain name with the application's domain name." />
<div class="">Domain Preview: {{ $preview_url_template }}</div>
@if ($preview_url_template)
<div class="">Domain Preview: {{ $preview_url_template }}</div>
@endif
</div>
</form>

View File

@@ -0,0 +1,7 @@
<form wire:submit="save" class="flex items-end gap-2">
<x-forms.input helper="One domain per preview." label="Domains for {{ str($serviceName)->headline() }}"
id="service.domain"></x-forms.input>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate">Generate
Domain</x-forms.button>
</form>

View File

@@ -39,10 +39,19 @@
<x-external-link />
</a>
</td>
<td>
<td class="flex flex-col gap-1 md:flex-row">
<x-forms.button
wire:click="add('{{ data_get($pull_request, 'number') }}', '{{ data_get($pull_request, 'html_url') }}')">
Configure
</x-forms.button>
<x-forms.button
wire:click="deploy('{{ data_get($pull_request, 'number') }}', '{{ data_get($pull_request, 'html_url') }}')">
Deploy
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>Deploy
</x-forms.button>
</td>
</tr>
@@ -54,10 +63,10 @@
</div>
</div>
@if ($application->previews->count() > 0)
<div class="pb-4">Previews</div>
<div class="flex flex-wrap gap-6">
@foreach ($application->previews as $preview)
<div class="flex flex-col p-4 dark:bg-coolgray-100">
<h3 class="py-4">Deployments</h3>
<div class="flex flex-wrap w-full gap-4">
@foreach (data_get($application, 'previews') as $previewName => $preview)
<div class="flex flex-col w-full p-4 border dark:border-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
@if (Str::of(data_get($preview, 'status'))->startsWith('running'))
<x-status.running :status="data_get($preview, 'status')" />
@@ -77,32 +86,96 @@
<x-external-link />
</a>
</div>
<div class="flex items-center gap-2 pt-6">
<x-forms.button class="dark:bg-coolgray-500"
wire:click="deploy({{ data_get($preview, 'pull_request_id') }})">
@if (data_get($preview, 'status') === 'exited')
Deploy
@if ($application->build_pack === 'dockercompose')
<div class="flex flex-col gap-4 pt-4">
@if (collect(json_decode($preview->docker_compose_domains))->count() === 0)
<form wire:submit="save_preview('{{ $preview->id }}')"
class="flex items-end gap-2 pt-4">
<x-forms.input label="Domain" helper="One domain per preview."
id="application.previews.{{ $previewName }}.fqdn"></x-forms.input>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
Domain</x-forms.button>
</form>
@else
Redeploy
@foreach (collect(json_decode($preview->docker_compose_domains)) as $serviceName => $service)
<livewire:project.application.previews-compose wire:key="{{ $preview->id }}"
:service="$service" :serviceName="$serviceName" :preview="$preview" />
@endforeach
@endif
</x-forms.button>
</div>
@else
<form wire:submit="save_preview('{{ $preview->id }}')" class="flex items-end gap-2 pt-4">
<x-forms.input label="Domain" helper="One domain per preview."
id="application.previews.{{ $previewName }}.fqdn"></x-forms.input>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
Domain</x-forms.button>
</form>
@endif
<div class="flex items-center gap-2 pt-6">
@if (count($parameters) > 0)
<a
href="{{ route('project.application.deployment.index', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
<x-forms.button class="dark:bg-coolgray-500">
<x-forms.button>
Deployment Logs
</x-forms.button>
</a>
<a
href="{{ route('project.application.logs', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
<x-forms.button class="dark:bg-coolgray-500">
<x-forms.button>
Application Logs
</x-forms.button>
</a>
@endif
<x-forms.button isError class="dark:bg-coolgray-500"
wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Delete
<div class="flex-1"></div>
<x-forms.button wire:click="deploy({{ data_get($preview, 'pull_request_id') }})">
@if (data_get($preview, 'status') === 'exited')
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Deploy
@else
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg> Redeploy
@endif
</x-forms.button>
@if (data_get($preview, 'status') !== 'exited')
<x-modal-confirmation isErrorButton
action="stop({{ data_get($preview, 'pull_request_id') }})">
<x-slot:customButton>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-slot:customButton>
This will stop the preview deployment. <br>Please think again.
</x-modal-confirmation>
@endif
<x-modal-confirmation isErrorButton
action="delete({{ data_get($preview, 'pull_request_id') }})" buttonTitle="Delete">
This will delete the preview deployment. <br>Please think again.
</x-modal-confirmation>
</div>
</div>
@endforeach

View File

@@ -1,4 +1,7 @@
<form>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > Clone | Coolify
</x-slot>
<div class="flex flex-col">
<h1>Clone</h1>
<div class="subtitle ">Quickly clone all resources to a new project or environment.</div>

View File

@@ -1,4 +1,4 @@
<div>
<div wire:init='refreshBackupExecutions'>
@isset($backup)
<div class="flex items-center gap-2">
<h3 class="py-4">Executions</h3>

View File

@@ -1,9 +1,12 @@
<div>
<x-slot:title>
{{ data_get_str($database, 'name')->limit(10) }} > Backup | Coolify
</x-slot>
<h1>Backups</h1>
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />
<div class="pt-6">
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<livewire:project.database.backup-executions :backup="$backup" :executions="$executions" />
<livewire:project.database.backup-executions :backup="$backup" />
</div>
</div>

View File

@@ -1,12 +1,15 @@
<div>
<x-slot:title>
{{ data_get_str($database, 'name')->limit(10) }} > Backups | Coolify
</x-slot>
<h1>Backups</h1>
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />
<div class="pt-6">
<div class="flex gap-2 ">
<div class="flex gap-2">
<h2 class="pb-4">Scheduled Backups</h2>
<x-modal-input buttonTitle="+ Add" title="New Scheduled Backup">
<livewire:project.database.create-scheduled-backup :database="$database" :s3s="$s3s" />
<livewire:project.database.create-scheduled-backup :database="$database" />
</x-modal-input>
</div>
<livewire:project.database.scheduled-backups :database="$database" />

View File

@@ -15,8 +15,8 @@
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Username" id="database.clickhouse_admin_user" placeholder="If empty: clickhouse"
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Username" id="database.clickhouse_admin_user"
placeholder="If empty: clickhouse" readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="database.clickhouse_admin_password" type="password" required
readonly helper="You can only change this in the database." />
</div>
@@ -34,9 +34,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="Clickhouse URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -47,6 +44,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
</form>
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($database, 'name')->limit(10) }} > Configuration | Coolify
</x-slot>
<h1>Configuration</h1>
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />

View File

@@ -16,9 +16,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="Dragonfly URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -29,6 +26,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
{{-- <x-forms.textarea
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
label="Custom Dragonfly Configuration" rows="10" id="database.keydb_conf" /> --}}

View File

@@ -17,9 +17,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="KeyDB URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -30,6 +27,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
label="Custom KeyDB Configuration" rows="10" id="database.keydb_conf" />

View File

@@ -12,23 +12,23 @@
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mariadb'>https://hub.docker.com/_/mariadb</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required readonly
helper="You can only change this in the database." />
<div class="flex flex-col gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
readonly helper="You can only change this in the database." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<div class="flex flex-col gap-2 pb-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required
@@ -45,9 +45,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="MariaDB URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -58,6 +55,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">

View File

@@ -12,21 +12,23 @@
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mongo'>https://hub.docker.com/_/mongo</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex gap-2">
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" readonly helper="You can only change this in the database." />
placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Password" id="database.mongo_initdb_root_password" type="password"
required readonly helper="You can only change this in the database." />
required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Database" id="database.mongo_initdb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<div class="flex flex-col gap-2 pb-2">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
@@ -39,9 +41,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="Mongo URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -52,6 +51,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">

View File

@@ -12,23 +12,23 @@
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mysql'>https://hub.docker.com/_/mysql</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" readonly
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required readonly
helper="You can only change this in the database." />
<div class="flex flex-col gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mysql_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
readonly helper="You can only change this in the database." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<div class="flex flex-col gap-4 pb-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required
@@ -45,9 +45,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="MySQL URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -58,6 +55,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="database.mysql_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">

View File

@@ -20,31 +20,30 @@
Save
</x-forms.button>
</div>
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/postgres'>https://hub.docker.com/_/postgres</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
automations (like backups) won't work.
</div>
@if ($database->started_at)
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
<x-forms.input label="Initial Username" id="database.postgres_user" placeholder="If empty: postgres"
readonly helper="You can only change this in the database." />
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required
readonly helper="You can only change this in the database." />
<div class="flex flex-col gap-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Password" id="database.postgres_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Database" id="database.postgres_db"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2 pb-8">
<div class="flex flex-col gap-2 pb-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.postgres_password" type="password" required />
<x-forms.input label="Database" id="database.postgres_db"
<x-forms.input label="Initial Database" id="database.postgres_db"
placeholder="If empty, it will be the same as Username." />
</div>
@endif
@@ -59,10 +58,8 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="Postgres URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
@@ -72,6 +69,23 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10" id="database.postgres_conf" />
</form>
<h3 class="pt-4">Advanced</h3>

View File

@@ -17,9 +17,6 @@
<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 dark: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="Make it publicly available" />
</div>
<x-forms.input label="Redis URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
@@ -30,8 +27,25 @@
type="password" readonly wire:model="db_url_public" />
@endif
</div>
<div>
<h3 class="py-2">Proxy</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
class="w-28">Proxy Logs</x-forms.button>
</x-slide-over>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
</div>
</div>
<x-forms.textarea
helper="<a target='_blank' class='dark:text-white underline' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>"
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>"
label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">

View File

@@ -1,5 +1,5 @@
<div>
<div class="flex flex-wrap gap-2">
<div class="flex flex-col gap-2">
@forelse($database->scheduledBackups as $backup)
@if ($type == 'database')
<a class="box"
@@ -32,8 +32,7 @@
<livewire:project.database.backup-edit wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:s3s="$s3s" :status="data_get($database, 'status')" />
<h3 class="py-4">Executions</h3>
<livewire:project.database.backup-executions wire:keykey="{{ $selectedBackup->id }}" :backup="$selectedBackup"
:executions="$selectedBackup->executions" />
<livewire:project.database.backup-executions wire:key="{{ $selectedBackup->id }}" :backup="$selectedBackup" />
</div>
@endif
</div>

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > Edit | Coolify
</x-slot>
<form wire:submit='submit' class="flex flex-col pb-10">
<div class="flex gap-2">
<h1>Project: {{ data_get($project, 'name') }}</h1>

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > Edit | Coolify
</x-slot>
<form wire:submit='submit' class="flex flex-col">
<div class="flex items-end gap-2">
<h1>Environment: {{ data_get($environment, 'name') }}</h1>
@@ -6,7 +9,7 @@
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
</div>
<nav class="flex pt-2 pb-10">
<ol class="flex items-center flex-wrap gap-y-1">
<ol class="flex flex-wrap items-center gap-y-1">
<li class="inline-flex items-center">
<div class="flex items-center">
<a class="text-xs truncate lg:text-sm"

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
Projects | Coolify
</x-slot>
<div class="flex gap-2">
<h1>Projects</h1>
<x-modal-input buttonTitle="+ Add" title="New Project">

View File

@@ -5,7 +5,8 @@
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2">
<div class="flex items-end gap-2">
<x-forms.input required id="repository_url" label="Repository URL (https://)" helper="{!! __('repository.url') !!}" />
<x-forms.input required id="repository_url" label="Repository URL (https://)"
helper="{!! __('repository.url') !!}" />
<x-forms.button type="submit">
Check repository
</x-forms.button>
@@ -58,6 +59,10 @@
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
</div>
@endif
@if ($build_pack === 'dockercompose' && isDev())
<div class="dark:text-warning">If you choose Docker Compose based deployments, you cannot change it afterwards.</div>
<x-forms.checkbox instantSave label="New Compose Services (only in dev mode)" id="new_compose_services"></x-forms.checkbox>
@endif
</div>
<x-forms.button wire:click.prevent='submit'>
Continue

View File

@@ -32,19 +32,20 @@
You can deploy public & private repositories through your GitHub Apps.
</x-slot>
<x-slot:logo>
<svg class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<g fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M64 1.512c-23.493 0-42.545 19.047-42.545 42.545 0 18.797 12.19 34.745 29.095 40.37 2.126.394 2.907-.923 2.907-2.047 0-1.014-.04-4.366-.058-7.92-11.837 2.573-14.334-5.02-14.334-5.02-1.935-4.918-4.724-6.226-4.724-6.226-3.86-2.64.29-2.586.29-2.586 4.273.3 6.523 4.385 6.523 4.385 3.794 6.504 9.953 4.623 12.38 3.536.383-2.75 1.485-4.628 2.702-5.69-9.45-1.075-19.384-4.724-19.384-21.026 0-4.645 1.662-8.44 4.384-11.42-.442-1.072-1.898-5.4.412-11.26 0 0 3.572-1.142 11.7 4.363 3.395-.943 7.035-1.416 10.65-1.432 3.616.017 7.258.49 10.658 1.432 8.12-5.504 11.688-4.362 11.688-4.362 2.316 5.86.86 10.187.418 11.26 2.728 2.978 4.378 6.774 4.378 11.42 0 16.34-9.953 19.938-19.427 20.99 1.526 1.32 2.886 3.91 2.886 7.88 0 5.692-.048 10.273-.048 11.674 0 1.13.766 2.458 2.922 2.04 16.896-5.632 29.07-21.574 29.07-40.365C106.545 20.56 87.497 1.512 64 1.512z" />
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<g fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M64 1.512c-23.493 0-42.545 19.047-42.545 42.545 0 18.797 12.19 34.745 29.095 40.37 2.126.394 2.907-.923 2.907-2.047 0-1.014-.04-4.366-.058-7.92-11.837 2.573-14.334-5.02-14.334-5.02-1.935-4.918-4.724-6.226-4.724-6.226-3.86-2.64.29-2.586.29-2.586 4.273.3 6.523 4.385 6.523 4.385 3.794 6.504 9.953 4.623 12.38 3.536.383-2.75 1.485-4.628 2.702-5.69-9.45-1.075-19.384-4.724-19.384-21.026 0-4.645 1.662-8.44 4.384-11.42-.442-1.072-1.898-5.4.412-11.26 0 0 3.572-1.142 11.7 4.363 3.395-.943 7.035-1.416 10.65-1.432 3.616.017 7.258.49 10.658 1.432 8.12-5.504 11.688-4.362 11.688-4.362 2.316 5.86.86 10.187.418 11.26 2.728 2.978 4.378 6.774 4.378 11.42 0 16.34-9.953 19.938-19.427 20.99 1.526 1.32 2.886 3.91 2.886 7.88 0 5.692-.048 10.273-.048 11.674 0 1.13.766 2.458 2.922 2.04 16.896-5.632 29.07-21.574 29.07-40.365C106.545 20.56 87.497 1.512 64 1.512z" />
<path
d="M37.57 62.596c-.095.212-.428.275-.73.13-.31-.14-.482-.427-.382-.64.09-.216.424-.277.733-.132.31.14.486.43.38.642zM39.293 64.52c-.203.187-.6.1-.87-.198-.278-.297-.33-.694-.124-.884.208-.188.593-.1.87.197.28.3.335.693.123.884zm1.677 2.448c-.26.182-.687.012-.95-.367-.262-.377-.262-.83.005-1.013.264-.182.684-.018.95.357.262.385.262.84-.005 1.024zm2.298 2.368c-.233.257-.73.188-1.093-.163-.372-.343-.475-.83-.242-1.087.237-.257.736-.185 1.102.163.37.342.482.83.233 1.086zm3.172 1.374c-.104.334-.582.485-1.064.344-.482-.146-.796-.536-.7-.872.1-.336.582-.493 1.067-.342.48.144.795.53.696.87zm3.48.255c.013.35-.396.642-.902.648-.508.012-.92-.272-.926-.618 0-.354.4-.642.908-.65.506-.01.92.272.92.62zm3.24-.551c.06.342-.29.694-.793.787-.494.092-.95-.12-1.014-.46-.06-.35.297-.7.79-.792.503-.088.953.118 1.017.466zm0 0" />
</g>
<path
d="M37.57 62.596c-.095.212-.428.275-.73.13-.31-.14-.482-.427-.382-.64.09-.216.424-.277.733-.132.31.14.486.43.38.642zM39.293 64.52c-.203.187-.6.1-.87-.198-.278-.297-.33-.694-.124-.884.208-.188.593-.1.87.197.28.3.335.693.123.884zm1.677 2.448c-.26.182-.687.012-.95-.367-.262-.377-.262-.83.005-1.013.264-.182.684-.018.95.357.262.385.262.84-.005 1.024zm2.298 2.368c-.233.257-.73.188-1.093-.163-.372-.343-.475-.83-.242-1.087.237-.257.736-.185 1.102.163.37.342.482.83.233 1.086zm3.172 1.374c-.104.334-.582.485-1.064.344-.482-.146-.796-.536-.7-.872.1-.336.582-.493 1.067-.342.48.144.795.53.696.87zm3.48.255c.013.35-.396.642-.902.648-.508.012-.92-.272-.926-.618 0-.354.4-.642.908-.65.506-.01.92.272.92.62zm3.24-.551c.06.342-.29.694-.793.787-.494.092-.95-.12-1.014-.46-.06-.35.297-.7.79-.792.503-.088.953.118 1.017.466zm0 0" />
</g>
<path
d="M24.855 108.302h-10.7a.5.5 0 00-.5.5v5.232a.5.5 0 00.5.5h4.173v6.5s-.937.32-3.53.32c-3.056 0-7.327-1.116-7.327-10.508 0-9.393 4.448-10.63 8.624-10.63 3.614 0 5.17.636 6.162.943.31.094.6-.216.6-.492l1.193-5.055a.468.468 0 00-.192-.39c-.403-.288-2.857-1.66-9.058-1.66-7.144 0-14.472 3.038-14.472 17.65 0 14.61 8.39 16.787 15.46 16.787 5.854 0 9.405-2.502 9.405-2.502.146-.08.162-.285.162-.38v-16.316a.5.5 0 00-.5-.5zM79.506 94.81H73.48a.5.5 0 00-.498.503l.002 11.644h-9.392V95.313a.5.5 0 00-.497-.503H57.07a.5.5 0 00-.498.503v31.53c0 .277.224.503.498.503h6.025a.5.5 0 00.497-.504v-13.486h9.392l-.016 13.486c0 .278.224.504.5.504h6.038a.5.5 0 00.497-.504v-31.53a.497.497 0 00-.497-.502zm-47.166.717c-2.144 0-3.884 1.753-3.884 3.923 0 2.167 1.74 3.925 3.884 3.925 2.146 0 3.885-1.758 3.885-3.925 0-2.17-1.74-3.923-3.885-3.923zm2.956 9.608H29.29c-.276 0-.522.284-.522.56v20.852c0 .613.382.795.876.795h5.41c.595 0 .74-.292.74-.805v-20.899a.5.5 0 00-.498-.502zm67.606.047h-5.98a.5.5 0 00-.496.504v15.46s-1.52 1.11-3.675 1.11-2.727-.977-2.727-3.088v-13.482a.5.5 0 00-.497-.504h-6.068a.502.502 0 00-.498.504v14.502c0 6.27 3.495 7.804 8.302 7.804 3.944 0 7.124-2.18 7.124-2.18s.15 1.15.22 1.285c.07.136.247.273.44.273l3.86-.017a.502.502 0 00.5-.504l-.003-21.166a.504.504 0 00-.5-.502zm16.342-.708c-3.396 0-5.706 1.515-5.706 1.515V95.312a.5.5 0 00-.497-.503H107a.5.5 0 00-.5.503v31.53a.5.5 0 00.5.503h4.192c.19 0 .332-.097.437-.268.103-.17.254-1.454.254-1.454s2.47 2.34 7.148 2.34c5.49 0 8.64-2.784 8.64-12.502s-5.03-10.988-8.428-10.988zm-2.36 17.764c-2.073-.063-3.48-1.004-3.48-1.004v-9.985s1.388-.85 3.09-1.004c2.153-.193 4.228.458 4.228 5.594 0 5.417-.935 6.486-3.837 6.398zm-63.689-.118c-.263 0-.937.107-1.63.107-2.22 0-2.973-1.032-2.973-2.368v-8.866h4.52a.5.5 0 00.5-.504v-4.856a.5.5 0 00-.5-.502h-4.52l-.007-5.97c0-.227-.116-.34-.378-.34h-6.16c-.238 0-.367.106-.367.335v6.17s-3.087.745-3.295.805a.5.5 0 00-.36.48v3.877a.5.5 0 00.497.503h3.158v9.328c0 6.93 4.86 7.61 8.14 7.61 1.497 0 3.29-.48 3.586-.59.18-.067.283-.252.283-.453l.004-4.265a.51.51 0 00-.5-.502z"
fill="currentColor" />
</svg>
d="M24.855 108.302h-10.7a.5.5 0 00-.5.5v5.232a.5.5 0 00.5.5h4.173v6.5s-.937.32-3.53.32c-3.056 0-7.327-1.116-7.327-10.508 0-9.393 4.448-10.63 8.624-10.63 3.614 0 5.17.636 6.162.943.31.094.6-.216.6-.492l1.193-5.055a.468.468 0 00-.192-.39c-.403-.288-2.857-1.66-9.058-1.66-7.144 0-14.472 3.038-14.472 17.65 0 14.61 8.39 16.787 15.46 16.787 5.854 0 9.405-2.502 9.405-2.502.146-.08.162-.285.162-.38v-16.316a.5.5 0 00-.5-.5zM79.506 94.81H73.48a.5.5 0 00-.498.503l.002 11.644h-9.392V95.313a.5.5 0 00-.497-.503H57.07a.5.5 0 00-.498.503v31.53c0 .277.224.503.498.503h6.025a.5.5 0 00.497-.504v-13.486h9.392l-.016 13.486c0 .278.224.504.5.504h6.038a.5.5 0 00.497-.504v-31.53a.497.497 0 00-.497-.502zm-47.166.717c-2.144 0-3.884 1.753-3.884 3.923 0 2.167 1.74 3.925 3.884 3.925 2.146 0 3.885-1.758 3.885-3.925 0-2.17-1.74-3.923-3.885-3.923zm2.956 9.608H29.29c-.276 0-.522.284-.522.56v20.852c0 .613.382.795.876.795h5.41c.595 0 .74-.292.74-.805v-20.899a.5.5 0 00-.498-.502zm67.606.047h-5.98a.5.5 0 00-.496.504v15.46s-1.52 1.11-3.675 1.11-2.727-.977-2.727-3.088v-13.482a.5.5 0 00-.497-.504h-6.068a.502.502 0 00-.498.504v14.502c0 6.27 3.495 7.804 8.302 7.804 3.944 0 7.124-2.18 7.124-2.18s.15 1.15.22 1.285c.07.136.247.273.44.273l3.86-.017a.502.502 0 00.5-.504l-.003-21.166a.504.504 0 00-.5-.502zm16.342-.708c-3.396 0-5.706 1.515-5.706 1.515V95.312a.5.5 0 00-.497-.503H107a.5.5 0 00-.5.503v31.53a.5.5 0 00.5.503h4.192c.19 0 .332-.097.437-.268.103-.17.254-1.454.254-1.454s2.47 2.34 7.148 2.34c5.49 0 8.64-2.784 8.64-12.502s-5.03-10.988-8.428-10.988zm-2.36 17.764c-2.073-.063-3.48-1.004-3.48-1.004v-9.985s1.388-.85 3.09-1.004c2.153-.193 4.228.458 4.228 5.594 0 5.417-.935 6.486-3.837 6.398zm-63.689-.118c-.263 0-.937.107-1.63.107-2.22 0-2.973-1.032-2.973-2.368v-8.866h4.52a.5.5 0 00.5-.504v-4.856a.5.5 0 00-.5-.502h-4.52l-.007-5.97c0-.227-.116-.34-.378-.34h-6.16c-.238 0-.367.106-.367.335v6.17s-3.087.745-3.295.805a.5.5 0 00-.36.48v3.877a.5.5 0 00.497.503h3.158v9.328c0 6.93 4.86 7.61 8.14 7.61 1.497 0 3.29-.48 3.586-.59.18-.067.283-.252.283-.453l.004-4.265a.51.51 0 00-.5-.502z"
fill="currentColor" />
</svg>
</div>
</x-slot:logo>
</x-resource-view>
<x-resource-view wire="setType('private-deploy-key')">
@@ -67,42 +68,43 @@
You can deploy a simple Dockerfile, without Git.
</x-slot>
<x-slot:logo>
<svg class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M20 96.9v-8.1c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.9 1.8 1.9v17c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5h-.8c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2.7.1 5.1 1.1 7.1 2.9zm-15.1 8.5c0 3 1.5 5.2 4.1 6.7 1.1.6 2.2.9 3.4.9 2.9 0 5.1-1.4 6.6-3.9.7-1.2 1-2.4 1-3.8 0-2.6-1.2-4.6-3.3-6.1-1.3-.9-2.7-1.4-4.2-1.4-3.2 0-5.5 1.6-6.9 4.5-.5 1-.7 2.1-.7 3.1zm32.2-11.3h.5c4.4 0 7.8 2.1 9.9 6 .9 1.5 1.3 3.2 1.3 5v.8c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5H37c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1.1 3.6-1.5 5.5-1.5zm-7.2 11.3c0 2.9 1.4 5 3.9 6.5 1.2.7 2.4 1 3.8 1 2.9 0 5-1.5 6.5-3.9.7-1.2 1-2.4 1-3.8 0-2.7-1.3-4.8-3.5-6.3-1.2-.8-2.6-1.2-4-1.2-3.2 0-5.5 1.6-6.9 4.5-.6 1.1-.8 2.2-.8 3.2zm34.8-7.2c-.6-.3-1.7-.4-2.3-.4-3.2-.1-5.5 1.7-6.9 4.5-.5 1-.7 2-.7 3.1 0 3.3 1.7 5.6 4.6 7 1.1.5 2.4.6 3.6.6 1 0 2.5-.6 3.4-1.1l.2-.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 2.3-4.3 2.9-5.9 3-5.7.4-10-2.7-11.6-8.2-.3-.9-.4-1.9-.4-2.9v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2 0 3.9.6 5.6 1.7l.1.1.1.1c.2.3.3.6.3 1v.4c0 1-.7 1.5-1.6 1.7H67c-.5 0-1.8-.6-2.3-.8zm12.4 2.6c1.5-1.5 3-3 4.5-4.4.4-.4 2-2.1 2.6-2.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 .6-.7 1.4-1.2 1.8l-2.7 2.7-4.6 4.7c2 2 4 4 5.9 6l1.6 1.7c.2.2.5.4.6.7.2.3.3.6.3.9v.5c-.2.9-.8 1.6-1.7 1.6h-.3c-.6 0-1.3-.7-1.8-1.1-.9-.8-1.8-1.7-2.6-2.6l-2.9-2.9v4.6c0 1.1-.7 1.9-1.8 1.9H75c-1.1 0-1.8-.9-1.8-1.9V88.9c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.8 1.8 1.9v11.9zm47.6-6.6h.4c1.1 0 1.9.8 1.9 1.9 0 1.6-1.5 2-2.8 2-1.7 0-3.4 1-4.5 2.2-1.5 1.5-2.1 3.3-2.1 5.4v9.2c0 1.1-.7 1.9-1.8 1.9h-.3c-1.1 0-1.8-.9-1.8-1.9v-9.8c0-3.8 1.8-6.8 4.9-9 1.8-1.2 3.9-1.9 6.1-1.9zm-27.1 18.3c1.4.5 3 .4 4.4.2.7-.3 2.6-1.1 3.3-1h.2c.4.2.8.5 1 .9.5 1 .3 2-.7 2.6l-.3.2c-3.6 2.1-7.5 1.8-11.1-.2-1.7-.9-3-2.3-4-4l-.2-.4c-2.3-4-2-8.3.6-12.1.9-1.3 2.1-2.3 3.5-3.1l.5-.3c3.4-2 7.1-1.8 10.6-.1 1.9.9 3.4 2.3 4.5 4.1l.2.3c.8 1.3-.2 2.5-1.2 3.3-1.2.9-2.4 2-3.5 3-2.7 2.2-5.3 4.4-7.8 6.6zm-3.3-2.3l8.5-7.3c1-.8 2-1.7 3-2.6-.8-1-2.1-1.7-3.1-2.1-2.2-.8-4.4-.6-6.4.6-2.6 1.5-3.8 4-3.7 7 0 1.2.4 2.3 1 3.4.2.4.4.7.7 1M73.7 33.7H85v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6V33.7h11.3V22.4h22.5V11.1h13.5v22.6z" />
<path fill="#00AADA"
d="M110.2 37.9c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7H18.4c-.6 6.2.5 11.9 3 16.8l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5 1.1-8.3 1.3h-.6c-1.3.1-2.7.1-4.2.1-1.6 0-3.1 0-4.9-.1 6 6.8 15.4 10.8 27.2 10.8 25 0 46.2-11.1 55.5-35.9 6.7.7 13.1-1 16-6.7-4.5-2.6-10.5-1.8-13.9-.1z" />
<path fill="#28B8EB"
d="M110.2 37.9c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-68c-.3 9.5 3.2 16.7 9.5 21 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4l-.1-.1c8.5 4.4 20.8 4.3 35-1.1 15.8-6.1 30.6-17.7 40.9-30.9-.2.1-.3.2-.5.2z" />
<path fill="#028BB8"
d="M18.5 54.6c.4 3.3 1.4 6.4 2.9 9.3l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4h-.4c-1.3.1-2.7.1-4.1.1-1.6 0-3.2 0-4.9-.1 6 6.8 15.5 10.8 27.3 10.8 21.4 0 40-8.1 50.8-26H18.5v-.1z" />
<path fill="#019BC6"
d="M23.3 54.6c1.3 5.8 4.3 10.4 8.8 13.5 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.6 1.4 8.5 4.4 20.8 4.3 34.9-1.1 8.5-3.3 16.8-8.2 24.2-14.1H23.3z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M28.2 35.5H38v9.8h-9.8v-9.8zm.8.9h.8v8.1H29v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H32v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm3.1-12.1h9.8V34h-9.8v-9.7zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M39.5 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M50.8 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1H53v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H56v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M50.8 24.3h9.8V34h-9.8v-9.7zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1H53v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H56v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zM62 35.5h9.8v9.8H62v-9.8zm.9.9h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M62 24.3h9.8V34H62v-9.7zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M62 13h9.8v9.8H62V13zm.9.8h.8V22h-.8v-8.2zm1.4 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2zm1.4 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M73.3 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H80v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D4EEF1"
d="M48.6 61.2c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7-1.5 0-2.7-1.2-2.7-2.7.1-1.5 1.3-2.7 2.7-2.7" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3A4D54"
d="M48.6 61.9c.2 0 .5 0 .7.1-.2.1-.4.4-.4.7 0 .4.4.8.8.8.3 0 .6-.2.7-.4.1.2.1.5.1.7 0 1.1-.9 1.9-1.9 1.9-1.1 0-1.9-.9-1.9-1.9 0-1 .9-1.9 1.9-1.9M1 55.6h125.3c-2.7-.7-8.6-1.6-7.7-5.2-5 5.7-16.9 4-20 1.2-3.4 4.9-23 3-24.3-.8-4.2 5-17.3 5-21.5 0-1.4 3.8-21 5.7-24.3.8-3 2.8-15 4.5-20-1.2 1.1 3.5-4.8 4.5-7.5 5.2" />
<path fill="#BFDBE0"
d="M55.8 80.6c-6.7-3.2-10.3-7.5-12.4-12.2-2.5.7-5.5 1.2-8.9 1.4-1.3.1-2.7.1-4.1.1-1.7 0-3.4 0-5.2-.1 6.1 6.1 13.7 10.8 27.6 10.9 1-.1 2-.1 3-.1z" />
<path fill="#D4EEF1"
d="M45.9 72.7c-.9-1.3-1.8-2.8-2.5-4.3-2.5.7-5.5 1.2-8.9 1.4 2.4 1.3 5.8 2.5 11.4 2.9z" />
</svg>
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M20 96.9v-8.1c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.9 1.8 1.9v17c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5h-.8c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2.7.1 5.1 1.1 7.1 2.9zm-15.1 8.5c0 3 1.5 5.2 4.1 6.7 1.1.6 2.2.9 3.4.9 2.9 0 5.1-1.4 6.6-3.9.7-1.2 1-2.4 1-3.8 0-2.6-1.2-4.6-3.3-6.1-1.3-.9-2.7-1.4-4.2-1.4-3.2 0-5.5 1.6-6.9 4.5-.5 1-.7 2.1-.7 3.1zm32.2-11.3h.5c4.4 0 7.8 2.1 9.9 6 .9 1.5 1.3 3.2 1.3 5v.8c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5H37c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1.1 3.6-1.5 5.5-1.5zm-7.2 11.3c0 2.9 1.4 5 3.9 6.5 1.2.7 2.4 1 3.8 1 2.9 0 5-1.5 6.5-3.9.7-1.2 1-2.4 1-3.8 0-2.7-1.3-4.8-3.5-6.3-1.2-.8-2.6-1.2-4-1.2-3.2 0-5.5 1.6-6.9 4.5-.6 1.1-.8 2.2-.8 3.2zm34.8-7.2c-.6-.3-1.7-.4-2.3-.4-3.2-.1-5.5 1.7-6.9 4.5-.5 1-.7 2-.7 3.1 0 3.3 1.7 5.6 4.6 7 1.1.5 2.4.6 3.6.6 1 0 2.5-.6 3.4-1.1l.2-.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 2.3-4.3 2.9-5.9 3-5.7.4-10-2.7-11.6-8.2-.3-.9-.4-1.9-.4-2.9v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2 0 3.9.6 5.6 1.7l.1.1.1.1c.2.3.3.6.3 1v.4c0 1-.7 1.5-1.6 1.7H67c-.5 0-1.8-.6-2.3-.8zm12.4 2.6c1.5-1.5 3-3 4.5-4.4.4-.4 2-2.1 2.6-2.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 .6-.7 1.4-1.2 1.8l-2.7 2.7-4.6 4.7c2 2 4 4 5.9 6l1.6 1.7c.2.2.5.4.6.7.2.3.3.6.3.9v.5c-.2.9-.8 1.6-1.7 1.6h-.3c-.6 0-1.3-.7-1.8-1.1-.9-.8-1.8-1.7-2.6-2.6l-2.9-2.9v4.6c0 1.1-.7 1.9-1.8 1.9H75c-1.1 0-1.8-.9-1.8-1.9V88.9c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.8 1.8 1.9v11.9zm47.6-6.6h.4c1.1 0 1.9.8 1.9 1.9 0 1.6-1.5 2-2.8 2-1.7 0-3.4 1-4.5 2.2-1.5 1.5-2.1 3.3-2.1 5.4v9.2c0 1.1-.7 1.9-1.8 1.9h-.3c-1.1 0-1.8-.9-1.8-1.9v-9.8c0-3.8 1.8-6.8 4.9-9 1.8-1.2 3.9-1.9 6.1-1.9zm-27.1 18.3c1.4.5 3 .4 4.4.2.7-.3 2.6-1.1 3.3-1h.2c.4.2.8.5 1 .9.5 1 .3 2-.7 2.6l-.3.2c-3.6 2.1-7.5 1.8-11.1-.2-1.7-.9-3-2.3-4-4l-.2-.4c-2.3-4-2-8.3.6-12.1.9-1.3 2.1-2.3 3.5-3.1l.5-.3c3.4-2 7.1-1.8 10.6-.1 1.9.9 3.4 2.3 4.5 4.1l.2.3c.8 1.3-.2 2.5-1.2 3.3-1.2.9-2.4 2-3.5 3-2.7 2.2-5.3 4.4-7.8 6.6zm-3.3-2.3l8.5-7.3c1-.8 2-1.7 3-2.6-.8-1-2.1-1.7-3.1-2.1-2.2-.8-4.4-.6-6.4.6-2.6 1.5-3.8 4-3.7 7 0 1.2.4 2.3 1 3.4.2.4.4.7.7 1M73.7 33.7H85v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6V33.7h11.3V22.4h22.5V11.1h13.5v22.6z" />
<path fill="#00AADA"
d="M110.2 37.9c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7H18.4c-.6 6.2.5 11.9 3 16.8l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5 1.1-8.3 1.3h-.6c-1.3.1-2.7.1-4.2.1-1.6 0-3.1 0-4.9-.1 6 6.8 15.4 10.8 27.2 10.8 25 0 46.2-11.1 55.5-35.9 6.7.7 13.1-1 16-6.7-4.5-2.6-10.5-1.8-13.9-.1z" />
<path fill="#28B8EB"
d="M110.2 37.9c.8-5.9-3.6-10.5-6.4-12.7-3.1 3.6-3.6 13.2 1.3 17.2-2.8 2.4-8.5 4.7-14.5 4.7h-68c-.3 9.5 3.2 16.7 9.5 21 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4l-.1-.1c8.5 4.4 20.8 4.3 35-1.1 15.8-6.1 30.6-17.7 40.9-30.9-.2.1-.3.2-.5.2z" />
<path fill="#028BB8"
d="M18.5 54.6c.4 3.3 1.4 6.4 2.9 9.3l.8 1.5c.5.9 1.1 1.7 1.7 2.6 3 .2 5.7.3 8.2.2 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.5 1.4h-.4c-1.3.1-2.7.1-4.1.1-1.6 0-3.2 0-4.9-.1 6 6.8 15.5 10.8 27.3 10.8 21.4 0 40-8.1 50.8-26H18.5v-.1z" />
<path fill="#019BC6"
d="M23.3 54.6c1.3 5.8 4.3 10.4 8.8 13.5 4.9-.1 8.9-.7 12-1.7.5-.2.9.1 1.1.5.2.5-.1.9-.5 1.1-.4.1-.8.3-1.3.4-2.4.7-5.2 1.2-8.6 1.4 8.5 4.4 20.8 4.3 34.9-1.1 8.5-3.3 16.8-8.2 24.2-14.1H23.3z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M28.2 35.5H38v9.8h-9.8v-9.8zm.8.9h.8v8.1H29v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H32v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm3.1-12.1h9.8V34h-9.8v-9.7zm.8.8h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M39.5 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M50.8 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1H53v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H56v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M50.8 24.3h9.8V34h-9.8v-9.7zm.8.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1H53v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H56v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zM62 35.5h9.8v9.8H62v-9.8zm.9.9h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M62 24.3h9.8V34H62v-9.7zm.9.8h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#23C2EE"
d="M62 13h9.8v9.8H62V13zm.9.8h.8V22h-.8v-8.2zm1.4 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2zm1.4 0h.8V22h-.8v-8.2zm1.5 0h.8V22h-.8v-8.2z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#00ACD3"
d="M73.3 35.5h9.8v9.8h-9.8v-9.8zm.8.9h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1h-.8v-8.1zm1.4 0h.8v8.1h-.8v-8.1zm1.5 0h.8v8.1H80v-8.1zm1.5 0h.8v8.1h-.8v-8.1z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D4EEF1"
d="M48.6 61.2c1.5 0 2.7 1.2 2.7 2.7 0 1.5-1.2 2.7-2.7 2.7-1.5 0-2.7-1.2-2.7-2.7.1-1.5 1.3-2.7 2.7-2.7" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3A4D54"
d="M48.6 61.9c.2 0 .5 0 .7.1-.2.1-.4.4-.4.7 0 .4.4.8.8.8.3 0 .6-.2.7-.4.1.2.1.5.1.7 0 1.1-.9 1.9-1.9 1.9-1.1 0-1.9-.9-1.9-1.9 0-1 .9-1.9 1.9-1.9M1 55.6h125.3c-2.7-.7-8.6-1.6-7.7-5.2-5 5.7-16.9 4-20 1.2-3.4 4.9-23 3-24.3-.8-4.2 5-17.3 5-21.5 0-1.4 3.8-21 5.7-24.3.8-3 2.8-15 4.5-20-1.2 1.1 3.5-4.8 4.5-7.5 5.2" />
<path fill="#BFDBE0"
d="M55.8 80.6c-6.7-3.2-10.3-7.5-12.4-12.2-2.5.7-5.5 1.2-8.9 1.4-1.3.1-2.7.1-4.1.1-1.7 0-3.4 0-5.2-.1 6.1 6.1 13.7 10.8 27.6 10.9 1-.1 2-.1 3-.1z" />
<path fill="#D4EEF1"
d="M45.9 72.7c-.9-1.3-1.8-2.8-2.5-4.3-2.5.7-5.5 1.2-8.9 1.4 2.4 1.3 5.8 2.5 11.4 2.9z" />
</svg>
</div>
</x-slot:logo>
</x-resource-view>
<x-resource-view wire="setType('docker-compose-empty')">
@@ -111,10 +113,9 @@
You can deploy complex application easily with Docker Compose, without Git.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M20 96.9v-8.1c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.9 1.8 1.9v17c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5h-.8c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2.7.1 5.1 1.1 7.1 2.9zm-15.1 8.5c0 3 1.5 5.2 4.1 6.7 1.1.6 2.2.9 3.4.9 2.9 0 5.1-1.4 6.6-3.9.7-1.2 1-2.4 1-3.8 0-2.6-1.2-4.6-3.3-6.1-1.3-.9-2.7-1.4-4.2-1.4-3.2 0-5.5 1.6-6.9 4.5-.5 1-.7 2.1-.7 3.1zm32.2-11.3h.5c4.4 0 7.8 2.1 9.9 6 .9 1.5 1.3 3.2 1.3 5v.8c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5H37c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1.1 3.6-1.5 5.5-1.5zm-7.2 11.3c0 2.9 1.4 5 3.9 6.5 1.2.7 2.4 1 3.8 1 2.9 0 5-1.5 6.5-3.9.7-1.2 1-2.4 1-3.8 0-2.7-1.3-4.8-3.5-6.3-1.2-.8-2.6-1.2-4-1.2-3.2 0-5.5 1.6-6.9 4.5-.6 1.1-.8 2.2-.8 3.2zm34.8-7.2c-.6-.3-1.7-.4-2.3-.4-3.2-.1-5.5 1.7-6.9 4.5-.5 1-.7 2-.7 3.1 0 3.3 1.7 5.6 4.6 7 1.1.5 2.4.6 3.6.6 1 0 2.5-.6 3.4-1.1l.2-.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 2.3-4.3 2.9-5.9 3-5.7.4-10-2.7-11.6-8.2-.3-.9-.4-1.9-.4-2.9v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2 0 3.9.6 5.6 1.7l.1.1.1.1c.2.3.3.6.3 1v.4c0 1-.7 1.5-1.6 1.7H67c-.5 0-1.8-.6-2.3-.8zm12.4 2.6c1.5-1.5 3-3 4.5-4.4.4-.4 2-2.1 2.6-2.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 .6-.7 1.4-1.2 1.8l-2.7 2.7-4.6 4.7c2 2 4 4 5.9 6l1.6 1.7c.2.2.5.4.6.7.2.3.3.6.3.9v.5c-.2.9-.8 1.6-1.7 1.6h-.3c-.6 0-1.3-.7-1.8-1.1-.9-.8-1.8-1.7-2.6-2.6l-2.9-2.9v4.6c0 1.1-.7 1.9-1.8 1.9H75c-1.1 0-1.8-.9-1.8-1.9V88.9c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.8 1.8 1.9v11.9zm47.6-6.6h.4c1.1 0 1.9.8 1.9 1.9 0 1.6-1.5 2-2.8 2-1.7 0-3.4 1-4.5 2.2-1.5 1.5-2.1 3.3-2.1 5.4v9.2c0 1.1-.7 1.9-1.8 1.9h-.3c-1.1 0-1.8-.9-1.8-1.9v-9.8c0-3.8 1.8-6.8 4.9-9 1.8-1.2 3.9-1.9 6.1-1.9zm-27.1 18.3c1.4.5 3 .4 4.4.2.7-.3 2.6-1.1 3.3-1h.2c.4.2.8.5 1 .9.5 1 .3 2-.7 2.6l-.3.2c-3.6 2.1-7.5 1.8-11.1-.2-1.7-.9-3-2.3-4-4l-.2-.4c-2.3-4-2-8.3.6-12.1.9-1.3 2.1-2.3 3.5-3.1l.5-.3c3.4-2 7.1-1.8 10.6-.1 1.9.9 3.4 2.3 4.5 4.1l.2.3c.8 1.3-.2 2.5-1.2 3.3-1.2.9-2.4 2-3.5 3-2.7 2.2-5.3 4.4-7.8 6.6zm-3.3-2.3l8.5-7.3c1-.8 2-1.7 3-2.6-.8-1-2.1-1.7-3.1-2.1-2.2-.8-4.4-.6-6.4.6-2.6 1.5-3.8 4-3.7 7 0 1.2.4 2.3 1 3.4.2.4.4.7.7 1M73.7 33.7H85v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6V33.7h11.3V22.4h22.5V11.1h13.5v22.6z" />
<path fill="#00AADA"
@@ -157,10 +158,9 @@
You can deploy an existing Docker Image from any Registry, without Git.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M20 96.9v-8.1c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.9 1.8 1.9v17c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5h-.8c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2.7.1 5.1 1.1 7.1 2.9zm-15.1 8.5c0 3 1.5 5.2 4.1 6.7 1.1.6 2.2.9 3.4.9 2.9 0 5.1-1.4 6.6-3.9.7-1.2 1-2.4 1-3.8 0-2.6-1.2-4.6-3.3-6.1-1.3-.9-2.7-1.4-4.2-1.4-3.2 0-5.5 1.6-6.9 4.5-.5 1-.7 2.1-.7 3.1zm32.2-11.3h.5c4.4 0 7.8 2.1 9.9 6 .9 1.5 1.3 3.2 1.3 5v.8c0 4.1-2 7.4-5.6 9.5-1.7 1-3.5 1.5-5.4 1.5H37c-4.1 0-7.4-2-9.5-5.6-1-1.7-1.5-3.5-1.5-5.4v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1.1 3.6-1.5 5.5-1.5zm-7.2 11.3c0 2.9 1.4 5 3.9 6.5 1.2.7 2.4 1 3.8 1 2.9 0 5-1.5 6.5-3.9.7-1.2 1-2.4 1-3.8 0-2.7-1.3-4.8-3.5-6.3-1.2-.8-2.6-1.2-4-1.2-3.2 0-5.5 1.6-6.9 4.5-.6 1.1-.8 2.2-.8 3.2zm34.8-7.2c-.6-.3-1.7-.4-2.3-.4-3.2-.1-5.5 1.7-6.9 4.5-.5 1-.7 2-.7 3.1 0 3.3 1.7 5.6 4.6 7 1.1.5 2.4.6 3.6.6 1 0 2.5-.6 3.4-1.1l.2-.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 2.3-4.3 2.9-5.9 3-5.7.4-10-2.7-11.6-8.2-.3-.9-.4-1.9-.4-2.9v-.8c0-4.1 2.1-7.4 5.6-9.5 1.7-1 3.5-1.5 5.4-1.5h.8c2 0 3.9.6 5.6 1.7l.1.1.1.1c.2.3.3.6.3 1v.4c0 1-.7 1.5-1.6 1.7H67c-.5 0-1.8-.6-2.3-.8zm12.4 2.6c1.5-1.5 3-3 4.5-4.4.4-.4 2-2.1 2.6-2.1h.8c.9.2 1.5.7 1.5 1.7v.4c0 .6-.7 1.4-1.2 1.8l-2.7 2.7-4.6 4.7c2 2 4 4 5.9 6l1.6 1.7c.2.2.5.4.6.7.2.3.3.6.3.9v.5c-.2.9-.8 1.6-1.7 1.6h-.3c-.6 0-1.3-.7-1.8-1.1-.9-.8-1.8-1.7-2.6-2.6l-2.9-2.9v4.6c0 1.1-.7 1.9-1.8 1.9H75c-1.1 0-1.8-.9-1.8-1.9V88.9c0-1.1.7-1.9 1.8-1.9h.3c1.1 0 1.8.8 1.8 1.9v11.9zm47.6-6.6h.4c1.1 0 1.9.8 1.9 1.9 0 1.6-1.5 2-2.8 2-1.7 0-3.4 1-4.5 2.2-1.5 1.5-2.1 3.3-2.1 5.4v9.2c0 1.1-.7 1.9-1.8 1.9h-.3c-1.1 0-1.8-.9-1.8-1.9v-9.8c0-3.8 1.8-6.8 4.9-9 1.8-1.2 3.9-1.9 6.1-1.9zm-27.1 18.3c1.4.5 3 .4 4.4.2.7-.3 2.6-1.1 3.3-1h.2c.4.2.8.5 1 .9.5 1 .3 2-.7 2.6l-.3.2c-3.6 2.1-7.5 1.8-11.1-.2-1.7-.9-3-2.3-4-4l-.2-.4c-2.3-4-2-8.3.6-12.1.9-1.3 2.1-2.3 3.5-3.1l.5-.3c3.4-2 7.1-1.8 10.6-.1 1.9.9 3.4 2.3 4.5 4.1l.2.3c.8 1.3-.2 2.5-1.2 3.3-1.2.9-2.4 2-3.5 3-2.7 2.2-5.3 4.4-7.8 6.6zm-3.3-2.3l8.5-7.3c1-.8 2-1.7 3-2.6-.8-1-2.1-1.7-3.1-2.1-2.2-.8-4.4-.6-6.4.6-2.6 1.5-3.8 4-3.7 7 0 1.2.4 2.3 1 3.4.2.4.4.7.7 1M73.7 33.7H85v11.5h5.7c2.6 0 5.3-.5 7.8-1.3 1.2-.4 2.6-1 3.8-1.7-1.6-2.1-2.4-4.7-2.6-7.3-.3-3.5.4-8.1 2.8-10.8l1.2-1.4 1.4 1.1c3.6 2.9 6.5 6.8 7.1 11.4 4.3-1.3 9.3-1 13.1 1.2l1.5.9-.8 1.6c-3.2 6.2-9.9 8.2-16.4 7.8-9.8 24.3-31 35.8-56.8 35.8-13.3 0-25.5-5-32.5-16.8l-.1-.2-1-2.1c-2.4-5.2-3.1-10.9-2.6-16.6l.2-1.7h9.6V33.7h11.3V22.4h22.5V11.1h13.5v22.6z" />
<path fill="#00AADA"
@@ -207,10 +207,9 @@
robustness, advanced features, and strong standards compliance.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill="#currentColor"
d="M85.988 76.075c.632-5.262.443-6.034 4.362-5.182l.995.088c3.014.137 6.957-.485 9.272-1.561 4.986-2.313 7.942-6.177 3.026-5.162-11.215 2.313-11.986-1.483-11.986-1.483C103.5 45.204 108.451 22.9 104.178 17.44 92.524 2.548 72.35 9.59 72.012 9.773l-.108.021c-2.216-.461-4.695-.735-7.481-.78-5.075-.083-8.926 1.331-11.847 3.546 0 0-35.989-14.827-34.315 18.646.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.444-9.531 8.444-9.531 2.061 1.369 4.528 2.067 7.116 1.816l.2-.17c-.062.641-.035 1.268.081 2.01-3.027 3.383-2.137 3.977-8.189 5.222-6.122 1.262-2.525 3.508-.178 4.095 2.848.713 9.433 1.722 13.884-4.509l-.177.711c1.188.95 1.107 6.827 1.275 11.026.168 4.199.45 8.117 1.306 10.429.856 2.31 1.866 8.261 9.819 6.557 6.646-1.426 11.727-3.476 12.19-22.545" />
<path fill="#currentColor"
@@ -235,10 +234,9 @@
broker.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path
d="M21.4 97.6c0 1.8-1.5 3.5-3.5 3.5-1.5 0-2.8.4-4 1.3-1.3.8-2.2 1.9-3 3.2-1.6 2.1-2.4 4.6-2.7 5.5v12.5c0 1.9-1.6 3.5-3.6 3.5-1.9 0-3.5-1.6-3.5-3.5v-26c0-1.9 1.6-3.4 3.5-3.4 2 0 3.6 1.5 3.6 3.4v.5c.4-.5.9-1 1.4-1.3 2.2-1.4 5-2.6 8.3-2.6 2 0 3.5 1.5 3.5 3.4zm-1.9 13c.1-9 7-16.5 16.1-16.5 8.6 0 15.3 6.4 15.9 15.3v.3c0 .1 0 .5-.1.6-.2 1.6-1.6 2.6-3.4 2.6H27c.3 1.5 1.1 3.2 2.2 4.3 1.4 1.6 4 2.8 6.3 3 2.4.2 5.2-.4 6.8-1.6 1.4-1.4 4.1-1.3 4.9-.2.9.9 1.5 2.9 0 4.3-3.2 3-7.1 4.3-11.8 4.3-8.9 0-15.9-7.5-15.9-16.4zm7.1-3.2h18.6c-.7-2.6-4-6.5-9.7-7-5.6.2-8.3 4.3-8.9 7zm58.3 16.1c0 1.9-1.6 3.6-3.6 3.6-1.8 0-3.2-1.3-3.5-2.8-2.5 1.7-5.7 2.8-9 2.8-8.9 0-16-7.5-16-16.4 0-9 7.1-16.5 16-16.5 3.2 0 6.4 1.1 8.8 2.8V84.5c0-1.9 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v26.2l.1 12.8zm-16-22.2c-2.4 0-4.5 1-6.2 2.7-1.6 1.6-2.6 4-2.6 6.6 0 2.5 1 4.9 2.6 6.5 1.6 1.7 3.8 2.7 6.2 2.7 2.4 0 4.5-1 6.2-2.7 1.6-1.6 2.6-4 2.6-6.5 0-2.6-1-5-2.6-6.6-1.6-1.7-3.7-2.7-6.2-2.7zm28.6-15.4c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6v-1.4c0-2 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v1.4zm0 11.9v25.7c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6V97.8c0-2.1 1.6-3.6 3.6-3.6 2.1 0 3.6 1.5 3.6 3.6zm4.5 19.8c1.2-1.6 3.5-1.8 4.9-.5 1.7 1.4 4.7 3 7.2 2.9 1.8 0 3.4-.6 4.5-1.3.9-.8 1.2-1.4 1.2-2 0-.3-.1-.5-.2-.7-.1-.2-.3-.5-.9-.8-.9-.7-2.9-1.4-5.3-1.8h-.1c-2-.4-4-.9-5.7-1.7-1.8-.9-3.4-2-4.5-3.8-.7-1.2-1.1-2.6-1.1-4.1 0-3 1.7-5.6 3.9-7.2 2.3-1.6 5.1-2.4 8.1-2.4 4.5 0 7.8 2.2 9.9 3.6 1.6 1.1 2 3.2 1.1 4.9-1.1 1.6-3.2 2-4.9.9-2.1-1.4-4-2.4-6.1-2.4-1.6 0-3.1.5-4 1.2-.9.6-1.1 1.2-1.1 1.5 0 .3 0 .3.1.5.1.1.3.4.7.7.9.6 2.6 1.2 4.8 1.6l.1.1h.1c2.2.4 4.2 1 6.1 1.9 1.8.8 3.6 2 4.7 3.9.8 1.3 1.3 2.8 1.3 4.3 0 3.2-1.8 5.9-4.1 7.6-2.4 1.6-5.3 2.6-8.6 2.6-5.1-.1-9.1-2.4-11.7-4.5-1.4-1.3-1.6-3.5-.4-5z"
fill="#currentColor" />
@@ -272,8 +270,7 @@
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg class="block dark:hidden" width="245" height="60" viewBox="0 0 210 50"
fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
@@ -300,8 +297,7 @@
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg version="1.1" width="180" height="60" id="svg1326" viewBox="0 0 544 182"
sodipodi:docname="keydb.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
@@ -363,9 +359,8 @@
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg width="200" height="90" viewBox="0 0 100 43" fill="currentColor"
class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg width="215" height="90" viewBox="0 0 100 43" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_378_10860)">
<rect x="2.70837" y="2.875" width="2.24992" height="20.2493" rx="0.236664"
@@ -390,10 +385,9 @@
optional schemas.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
d="M87.259 100.139c.169-.325.331-.612.469-.909.087-.19.221-.228.41-.223 1.133.032 2.266.067 3.4.078.963.01 1.928-.008 2.892-.019 1.086-.013 2.172-.07 3.257-.039 1.445.042 2.853.325 4.16.968 1.561.769 2.742 1.94 3.547 3.483a8.71 8.71 0 01.931 3.14c.172 1.608.059 3.179-.451 4.717-.632 1.906-1.832 3.365-3.499 4.458-1.283.841-2.69 1.338-4.198 1.622-1.596.301-3.197.204-4.798.209-1.756.007-3.511-.031-5.267-.051-.307-.003-.351-.061-.27-.354l.075-.27c.171-.538.263-.562.809-.652a2.83 2.83 0 001.087-.413c.184-.122.26-.44.332-.685.062-.214.065-.449.067-.675.025-3.425.051-6.849.065-10.272a44.077 44.077 0 00-.065-2.596c-.034-.605-.357-1.019-1.077-1.162-.56-.111-1.124-.197-1.687-.296l-.189-.059zm16.076 8.293c-.076-.682-.113-1.37-.235-2.042-.292-1.613-.998-3.018-2.238-4.119-2.005-1.779-4.419-2.053-6.949-1.841-.576.048-.7.245-.709.837-.014.84-.028 1.68-.029 2.52-.004 2.664-.004 5.328 0 7.992.001.758.009 1.516.031 2.272.024.774.305 1.429 1.063 1.729 1.195.473 2.452.529 3.706.336 2.003-.307 3.404-1.474 4.344-3.223.744-1.388.954-2.903 1.016-4.461zm4.869 9.071c-.024-.415.146-.758.356-1.073.057-.085.253-.081.388-.108l1.146-.227c.405-.086.618-.358.675-.755.038-.262.074-.527.077-.792a879.6 879.6 0 00.059-6.291c.01-2.1.002-4.2.002-6.3l-.009-.401c-.041-.675-.367-1.025-1.037-1.124l-1.453-.221c-.179-.024-.244-.11-.179-.269.112-.271.219-.552.377-.796.059-.09.258-.125.392-.122.694.01 1.388.062 2.082.061l6.041-.036c1.164-.001 2.288.202 3.332.759 1.149.612 1.792 1.559 1.976 2.849.192 1.355-.219 2.497-1.209 3.404-.407.374-.934.618-1.406.922l-.154.096c.438.161.855.3 1.261.466 1.188.487 2.133 1.248 2.633 2.463.395.959.395 1.959.161 2.953-.364 1.556-1.389 2.591-2.722 3.374-1.251.735-2.605 1.163-4.047 1.235-1.33.067-2.666.042-3.999.057l-.772.004a996.106 996.106 0 01-3.854-.096l-.117-.032zm5.537-6.089h.013c0 .658-.009 1.316.003 1.974.008.426-.007.864.085 1.274.138.613.418 1.166 1.106 1.342a6.671 6.671 0 002.818.124c1.177-.205 2.116-.795 2.631-1.916.382-.833.439-1.716.308-2.618-.174-1.188-.805-2.05-1.854-2.615-.688-.371-1.422-.598-2.204-.628-.876-.033-1.753-.035-2.629-.062-.246-.007-.28.118-.279.32.005.934.002 1.869.002 2.805zm1.865-4.475c.479-.024 1.021-.031 1.56-.085 1.032-.103 1.759-.622 2.138-1.609.193-.501.185-1.017.19-1.538.015-1.357-.777-2.469-2.066-2.929-.995-.355-2.021-.361-3.053-.333-.418.011-.605.194-.611.615l-.062 5.489c-.003.218.091.312.303.319l1.601.071z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor"
@@ -450,10 +444,9 @@
flexibility.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill="currentColor"
d="M0 91.313h4.242V74.566l6.566 14.598c.773 1.77 1.832 2.391 3.914 2.391s3.098-.621 3.871-2.391l6.566-14.598v16.746h4.242V74.594c0-1.633-.652-2.422-2-2.828-3.223-1.004-5.383-.137-6.363 2.039l-6.441 14.41-6.238-14.41c-.937-2.176-3.14-3.043-6.359-2.039-1.348.406-2 1.195-2 2.828zM32.93 77.68h4.238v9.227c-.039.5.16 1.676 2.484 1.715h9.223V77.633h4.25c.02 0-.008 14.984-.008 15.047.023 3.695-4.582 4.496-6.707 4.559H33.02v-2.852l13.414-.004c2.73-.285 2.406-1.645 2.406-2.098v-1.113h-9.012c-4.195-.039-6.863-1.871-6.898-3.977-.004-.191.09-9.422 0-9.516zm0 0" />
<path fill="currentColor"
@@ -471,10 +464,9 @@
replacement for MySQL.
</x-slot>
<x-slot:logo>
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<div>
<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill="currentColor"
d="M127.434 12.182a1.727 1.727 0 0 0-1.174-.392c-1.168 0-2.68.793-3.495 1.218l-.322.165a11.095 11.095 0 0 1-4.365 1.1c-1.554.049-2.892.14-4.635.322-10.327 1.06-14.933 8.975-19.37 16.63-2.416 4.164-4.91 8.489-8.33 11.793a22.472 22.472 0 0 1-2.252 1.913c-3.54 2.631-7.985 4.51-11.443 5.84-3.329 1.273-6.964 2.417-10.474 3.524-3.219 1.012-6.255 1.97-9.048 3.008a96.902 96.902 0 0 1-3.275 1.14c-2.545.825-4.378 1.458-7.06 3.304a45.386 45.386 0 0 0-2.804 2.066 29.585 29.585 0 0 0-5.597 5.894 34.802 34.802 0 0 1-4.701 5.642c-.566.554-1.57.826-3.074.826-1.76 0-3.895-.363-6.154-.747-2.33-.413-4.738-.805-6.803-.805-1.677 0-2.962.273-3.92.826 0 0-1.617.942-2.298 2.16l.67.302a13.718 13.718 0 0 1 2.859 2.065 14.342 14.342 0 0 0 2.973 2.115 2.553 2.553 0 0 1 .918.582c-.281.413-.694.946-1.129 1.516-2.384 3.119-3.774 5.09-2.977 6.163a2.507 2.507 0 0 0 1.239.28c5.196 0 7.989-1.35 11.52-3.06 1.024-.495 2.066-1.004 3.305-1.528 2.065-.896 4.288-2.325 6.647-3.838 3.084-2.01 6.31-4.076 9.442-5.072a25.734 25.734 0 0 1 7.943-1.115c3.305 0 6.783.441 10.138.872 2.499.322 5.089.652 7.63.805.986.057 1.9.086 2.787.086a32.307 32.307 0 0 0 3.557-.185l.284-.1c1.781-1.094 2.617-3.444 3.425-5.717.52-1.462.96-2.775 1.652-3.61a1.054 1.054 0 0 1 .132-.11.166.166 0 0 1 .202.032v.066c-.412 8.885-3.99 14.527-7.608 19.543l-2.416 2.59s3.383 0 5.307-.744c7.024-2.099 12.324-6.725 16.181-14.103a60.185 60.185 0 0 0 2.549-5.82c.065-.165.673-.47.616.384-.021.252-.038.533-.059.827 0 .173 0 .35-.033.528-.1 1.24-.392 3.859-.392 3.859l2.169-1.162c5.229-3.304 9.26-9.97 12.318-20.343 1.272-4.321 2.205-8.613 3.027-12.392.983-4.545 1.83-8.44 2.801-9.952 1.524-2.37 3.85-3.973 6.101-5.53.306-.211.616-.414.917-.637 2.83-1.986 5.643-4.279 6.263-8.555v-.095c.45-3.189.07-4.002-.364-4.373zm-7.283 103.727h-10.327V97.92h9.315c3.56 0 6.952.67 6.902 4.66 0 2.813-1.747 3.59-3.589 3.886 2.615.224 4.188 1.892 4.188 4.586.017 4.035-3.523 4.858-6.489 4.858zm-.772-10.14c3.565 0 4.362-1.372 4.362-3.115 0-2.619-1.595-3.214-4.362-3.214h-7.402v6.328zm.099 1.52h-7.501v7.1h7.823c2.194 0 4.511-.723 4.511-3.486 0-3.19-2.665-3.615-4.833-3.615zm-31.497-9.37h8.125c6.828 0 10.24 3.764 10.19 8.994.05 5.436-3.716 8.997-9.591 8.997H87.98zm2.242 1.596v14.825h6.197c5.432 0 7.501-3.665 7.501-7.477 0-4.309-2.59-7.348-7.5-7.348zm-10.838 5.357v-2.095h3.404v13.132h-3.392v-2.114c-.896 1.52-2.739 2.391-4.982 2.391-4.684 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.454.029 4.498.855 5.469 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.841 0 4.386-2.02 4.386-4.411 0-2.392-1.599-4.436-4.544-4.436-2.828 0-4.3 2.04-4.3 4.362zm-10.013-9.947a1.722 1.722 0 0 1 1.818-1.768 1.788 1.788 0 0 1 1.847 1.821 1.714 1.714 0 0 1-1.847 1.744 1.743 1.743 0 0 1-1.818-1.797zm.15 3.465h3.39v9.596c0 .595.125 1.02.62 1.02a3.657 3.657 0 0 0 .648-.073l.525 2.478a5.931 5.931 0 0 1-2.242.414c-1.421 0-2.942-.414-2.942-3.64zM52.15 115.91h-3.386v-13.132h3.386v2.942a5.197 5.197 0 0 1 4.735-3.218 6.13 6.13 0 0 1 2.119.347l-.723 2.479a7.435 7.435 0 0 0-1.793-.249c-2.445 0-4.338 1.843-4.338 4.545zm-11.037-11.037v-2.095h3.392v13.132h-3.392v-2.114c-.896 1.52-2.738 2.391-4.982 2.391-4.688 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.466.029 4.51.855 5.481 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.838 0 4.383-2.02 4.383-4.411 0-2.391-1.595-4.436-4.544-4.436-2.826 0-4.296 2.04-4.296 4.362zm-9.24-11.34 4.651 17.99h-3.51L21.24 102.95 15.4 115.91h-2.965l-5.808-12.883-3.19 12.883H0l4.61-17.99h3.04l6.28 13.93 6.253-13.93z" />
</svg>
@@ -517,8 +509,7 @@
</x-slot>
<x-slot:logo>
@if (data_get($service, 'logo'))
<img class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
<img class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100"
src="{{ asset(data_get($service, 'logo')) }}">
@endif
</x-slot:logo>
@@ -540,19 +531,9 @@
</x-slot>
<x-slot:logo>
@if (file_exists(public_path(data_get($service, 'logo'))))
<img class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 bg-neutral-300 dark:bg-transparent hover:bg-neutral-800"
<img class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 bg-neutral-300 dark:bg-transparent hover:bg-neutral-800"
src="{{ asset(data_get($service, 'logo')) }}">
@else
<div
class="w-[4.5rem]
aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:text-warning text-coollabs">
<svg width="50" height="50" viewBox="0 0 17 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M11.403 18.751v4.499c-.01.41-.34.74-.748.75H6.159a.768.768 0 0 1-.749-.748v-4.5c.01-.41.34-.739.749-.749h4.5c.41.01.74.34.75.749v.001zm5.923-11.247a6.306 6.306 0 0 1-.962 3.354l.015-.026a5.462 5.462 0 0 1-1.021 1.108l-.01.008c-.321.282-.672.55-1.042.794l-.036.022q-.413.253-1.144.665a3.71 3.71 0 0 0-1.275 1.204l-.009.014a2.535 2.535 0 0 0-.515 1.243l-.001.012a.978.978 0 0 1-.226.611l.001-.002a.652.652 0 0 1-.524.29h-4.5a.563.563 0 0 1-.479-.343l-.001-.004a1.394 1.394 0 0 1-.197-.702v-.845a4.356 4.356 0 0 1 1.219-2.935l-.001.001A7.945 7.945 0 0 1 9.251 9.96l.048-.02a4.627 4.627 0 0 0 1.574-1.049l.001-.001a2.094 2.094 0 0 0 .469-1.429v.005a1.695 1.695 0 0 0-.863-1.382l-.009-.004a3.436 3.436 0 0 0-2.018-.599h.003a3.53 3.53 0 0 0-2.039.552l.014-.009A12.825 12.825 0 0 0 4.45 8.149l-.025.035a.73.73 0 0 1-.581.3a.897.897 0 0 1-.472-.152l.003.002L.301 5.991a.732.732 0 0 1-.29-.464L.01 5.523a.747.747 0 0 1 .105-.527l-.002.003C1.77 2 4.912.003 8.522.003c.103 0 .205.002.307.005h-.015a8.362 8.362 0 0 1 3.074.602l-.057-.02a10.2 10.2 0 0 1 2.757 1.571l-.02-.016a7.838 7.838 0 0 1 1.966 2.349l.02.041c.483.857.768 1.881.769 2.971z" />
</svg>
</div>
@endif
</x-slot:logo>
<x-slot:documentation>

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > New | Coolify
</x-slot>
@if ($type === 'public')
<livewire:project.new.public-git-repository :type="$type" />
@elseif ($type === 'private-gh-app')

View File

@@ -1,4 +1,7 @@
<div>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > Resources | Coolify
</x-slot>
<div class="flex flex-col">
<div class="flex items-center gap-2">
<h1>Resources</h1>

View File

@@ -1,4 +1,7 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
<x-slot:title>
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
</x-slot>
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">

View File

@@ -19,12 +19,18 @@
</div>
<div class="w-full">
@isset($serviceApplication)
<x-slot:title>
{{ data_get_str($service, 'name')->limit(10) }} >
{{ data_get_str($serviceApplication, 'name')->limit(10) }} | Coolify
</x-slot>
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.service-application-view :application="$serviceApplication" />
</div>
@endisset
@isset($serviceDatabase)
<x-slot:title>
{{ data_get_str($service, 'name')->limit(10) }} > {{ data_get_str($serviceDatabase, 'name')->limit(10) }} | Coolify
</x-slot>
<div x-cloak x-show="activeTab === 'general'" class="h-full">
<livewire:project.service.database :database="$serviceDatabase" />
</div>

Some files were not shown because too many files have changed in this diff Show More