Compare commits

...

105 Commits

Author SHA1 Message Date
Andras Bacsai
1e0226c8ed Merge pull request #1274 from coollabsio/next
v4.0.0-beta.55
2023-10-01 17:40:31 +02:00
Andras Bacsai
5c45908087 fix: if app settings is not saved to db 2023-10-01 17:39:57 +02:00
Andras Bacsai
aefdc76805 fix: dockerfile expose is not overwritten 2023-10-01 17:27:12 +02:00
Andras Bacsai
b3c8c881b7 Revert "fix: services should have destination as well"
This reverts commit 9ab5a1f7bd.
2023-10-01 15:31:25 +02:00
Andras Bacsai
9ab5a1f7bd fix: services should have destination as well 2023-10-01 13:59:22 +02:00
Andras Bacsai
23968e7886 version++ 2023-10-01 13:28:33 +02:00
Andras Bacsai
390d24b6d7 Merge pull request #1273 from coollabsio/next
v4.0.0-beta.54
2023-10-01 12:36:10 +02:00
Andras Bacsai
e4296345b3 fix: public repo branch selection
fix: commit sha selection in source tabs
2023-10-01 12:29:50 +02:00
Andras Bacsai
bcffbe418b fix: preview deployments name, status etc 2023-10-01 12:02:44 +02:00
Andras Bacsai
bfbee4e78f version+ 2023-10-01 11:33:15 +02:00
Andras Bacsai
2bdb44dac3 Merge pull request #1272 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:55:24 +02:00
Andras Bacsai
4daa1b8c16 fix: coolify db backup 2023-09-30 20:54:05 +02:00
Andras Bacsai
febc399568 fix: not found base_branch in git webhooks 2023-09-30 20:47:07 +02:00
Andras Bacsai
9b9d4f9941 Merge pull request #1271 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:27:12 +02:00
Andras Bacsai
f49b87870c fix: backups are now working again 2023-09-30 20:26:42 +02:00
Andras Bacsai
ed49d4e3a0 Merge pull request #1264 from coollabsio/next
v4.0.0-beta.51
2023-09-30 16:02:09 +02:00
Andras Bacsai
7811c75139 fix: deploykey branch 2023-09-30 15:59:39 +02:00
Andras Bacsai
068a1b4bc4 fix: preselect branc on private repos 2023-09-30 15:57:30 +02:00
Andras Bacsai
c618d912db fix: if public repository does not have a main branch 2023-09-30 15:51:01 +02:00
Andras Bacsai
3d43f2127a fix: respect server fqdn 2023-09-30 15:39:40 +02:00
Andras Bacsai
79fde593a9 fix: service volume read from filesystem
fix: edit compose moved to dialog
2023-09-30 15:08:40 +02:00
Andras Bacsai
64ce41df0f fix: file/dir based volumes are now read from the server 2023-09-29 21:38:11 +02:00
Andras Bacsai
23e205b6cd fix: docker cleanup should be a job by server 2023-09-29 14:26:42 +02:00
Andras Bacsai
4161ea7eb6 fix: show source on all type of applications 2023-09-29 14:26:31 +02:00
Andras Bacsai
77037f8933 ui: fix previews to preview 2023-09-29 14:26:19 +02:00
Andras Bacsai
bdcc0c8de5 fix: only show manually added private keys on server view 2023-09-29 11:01:58 +02:00
Andras Bacsai
ac133875fa fix: remove private key in case you removed a github app 2023-09-29 11:01:40 +02:00
Andras Bacsai
f4819a849b remove plausible for now 2023-09-29 10:53:33 +02:00
Andras Bacsai
2cd1785a92 version++ 2023-09-29 10:53:28 +02:00
Andras Bacsai
797352a5db Merge pull request #1263 from coollabsio/next
v4.0.0-beta.50
2023-09-29 10:36:25 +02:00
Andras Bacsai
03a3405524 fix: localhost privatekey update 2023-09-29 10:32:32 +02:00
Andras Bacsai
925326b885 change sentry dsn 2023-09-29 10:32:23 +02:00
Andras Bacsai
ffdf51a490 version++ 2023-09-29 10:32:13 +02:00
Andras Bacsai
272d5547c8 Merge pull request #1262 from coollabsio/next
v4.0.0-beta.49
2023-09-29 10:21:54 +02:00
Andras Bacsai
fbaec769f0 fix: reporting handler 2023-09-29 10:21:11 +02:00
Andras Bacsai
d93640a8d9 Merge pull request #1261 from coollabsio/next
v4.0.0-beta.48
2023-09-29 09:51:51 +02:00
Andras Bacsai
3749970831 fix: update process if server has been renamed 2023-09-29 09:48:20 +02:00
Andras Bacsai
7d45bb1335 version check 2023-09-28 22:55:03 +02:00
Andras Bacsai
da04e0c027 fix: sync:bunny 2023-09-28 22:42:10 +02:00
Andras Bacsai
c637d2d90d version++ 2023-09-28 22:31:53 +02:00
Andras Bacsai
1bb8860f37 Merge pull request #1260 from coollabsio/next
v4.0.0-beta.47
2023-09-28 22:25:12 +02:00
Andras Bacsai
38a22dcf4d fix: service templates 2023-09-28 22:20:49 +02:00
Andras Bacsai
91e1eb7664 fix 2023-09-28 21:56:34 +02:00
Andras Bacsai
c7946e7551 fix: next helper image 2023-09-28 21:52:50 +02:00
Andras Bacsai
958645e37f Merge branch 'main' into next 2023-09-28 21:48:03 +02:00
Andras Bacsai
acdfa89ec1 Update coolify-helper.yml 2023-09-28 21:32:53 +02:00
Andras Bacsai
62ec85ee2e Update coolify-helper.yml 2023-09-28 21:31:50 +02:00
Andras Bacsai
8f24a66456 Update coolify-helper.yml 2023-09-28 21:26:55 +02:00
Andras Bacsai
0dd0888775 Update coolify-helper.yml 2023-09-28 21:23:25 +02:00
Andras Bacsai
5040ddea28 fix: sentry 4502634789 2023-09-28 13:46:53 +02:00
Andras Bacsai
1a04a57c01 fix: sentry 4504136641 2023-09-28 13:40:58 +02:00
Andras Bacsai
9b6c162224 fix: sentry 4510197209 2023-09-28 13:15:14 +02:00
Andras Bacsai
e22c5d22f5 fix: instance fqdn setting 2023-09-28 13:13:21 +02:00
Andras Bacsai
84e5b39830 fix: prevent sync version (it needs an option) 2023-09-28 13:07:52 +02:00
Andras Bacsai
cdb6964b0b fix: install script drops an error 2023-09-28 13:05:17 +02:00
Andras Bacsai
df4ecd47a7 fix: sync command 2023-09-28 11:54:20 +02:00
Andras Bacsai
4a84c7238a fix: cannot delete env with available services 2023-09-28 11:51:01 +02:00
Andras Bacsai
2b3057e1b4 version++ 2023-09-28 11:50:50 +02:00
Andras Bacsai
ae65172946 Merge pull request #1256 from coollabsio/next
v4.0.0-beta.46
2023-09-28 11:38:16 +02:00
Andras Bacsai
3a2d17bc05 fix: containerStatusJob 2023-09-28 11:33:16 +02:00
Andras Bacsai
4c1067cf36 baseurl for docs 2023-09-28 11:01:00 +02:00
Andras Bacsai
b046a3e9f7 fix: sslip for localhost 2023-09-28 10:53:00 +02:00
Andras Bacsai
199881c596 fixes 2023-09-28 09:54:21 +02:00
Andras Bacsai
99c8607ff4 fix: disable early updates 2023-09-27 22:43:32 +02:00
Andras Bacsai
e61fcc77f9 ui 2023-09-27 22:39:10 +02:00
Andras Bacsai
20dca179fb add umami 2023-09-27 22:35:15 +02:00
Andras Bacsai
0f542c65ae fix: manually create network for services 2023-09-27 22:35:10 +02:00
Andras Bacsai
d609fcaee1 fix: services 2023-09-27 21:51:06 +02:00
Andras Bacsai
fe8a7fc54f fix: services view 2023-09-27 21:14:13 +02:00
Andras Bacsai
398f122593 fix: aaaaaaaaaaaaaaaaa 2023-09-27 15:48:19 +02:00
Andras Bacsai
f0abdcc2da okay, now it is way better 2023-09-27 12:45:53 +02:00
Andras Bacsai
c9a278b750 update service templates 2023-09-26 16:32:05 +02:00
Andras Bacsai
6990c593a4 update 2023-09-26 16:27:48 +02:00
Andras Bacsai
8f54b51ecd update templates 2023-09-26 16:21:55 +02:00
Andras Bacsai
3eb628b773 fix: containerstatusjob 2023-09-26 15:07:33 +02:00
Andras Bacsai
fabb97330a puh, fixes 2023-09-26 14:45:52 +02:00
Andras Bacsai
03c9793d11 Merge pull request #1252 from khrj/patch-1
Remove space before ? from onboarding flow
2023-09-25 21:02:53 +02:00
Andras Bacsai
a4320b7cee cloud: marketing emails 2023-09-25 20:57:52 +02:00
Andras Bacsai
cbf9bc99ea ui: help 2023-09-25 18:07:20 +02:00
Andras Bacsai
0fbc382467 feat: image tag for services 2023-09-25 17:51:04 +02:00
Andras Bacsai
0f8ccac775 fix: new service template layout 2023-09-25 17:41:35 +02:00
Andras Bacsai
ee20c3339e fix: show real storage name on services 2023-09-25 17:14:19 +02:00
Andras Bacsai
0b11093d18 feat: services 2023-09-25 15:48:43 +02:00
Khushraj Rathod
de8118b59d Remove space before ? from onboarding flow 2023-09-25 19:04:11 +05:30
Andras Bacsai
58522b59b7 - package updates
- feat: service fixes
- ui: help menu
2023-09-25 12:49:55 +02:00
Andras Bacsai
356394c03d ui: update help modal 2023-09-25 11:15:35 +02:00
Andras Bacsai
3e4db2f5b2 ui: more visible feedback button 2023-09-25 09:34:32 +02:00
Andras Bacsai
872981b8b4 ui: a bit better error 2023-09-25 09:20:29 +02:00
Andras Bacsai
51c468ae0b fix: proxy configuration + starter 2023-09-25 09:17:42 +02:00
Andras Bacsai
80a797aec8 version++ 2023-09-24 21:56:57 +02:00
Andras Bacsai
cfdab13d77 Merge pull request #1250 from coollabsio/next
v4.0.0-beta.45
2023-09-24 21:41:58 +02:00
Andras Bacsai
4fc8988ff4 fix: put back build pack chooser 2023-09-24 21:38:11 +02:00
Andras Bacsai
ab5619292e Merge pull request #1249 from chz/fix/1240
ui: Fixed z-index for magicbar
2023-09-24 21:18:48 +02:00
Andras Bacsai
b02e5d3f27 fix: applications with port mappins do a normal update (not rolling update) 2023-09-24 21:15:43 +02:00
Andras Bacsai
5f0c9c3a31 ui: Add source button 2023-09-24 21:15:21 +02:00
Chingiz Mammadov
ea6ec07a45 ui: Fixed z-index for magicbar 2023-09-24 23:03:11 +04:00
Andras Bacsai
36be325d0d Merge pull request #1248 from chz/ui/fix-version-zindex
ui: Fixed z-index for version link.
2023-09-24 20:42:35 +02:00
Andras Bacsai
6138ddcac6 revert sending notification 2023-09-24 17:51:01 +02:00
Andras Bacsai
0509da6730 fix: also check docker socks permission on validation 2023-09-24 17:48:40 +02:00
Andras Bacsai
5b877b84c2 fix: add expose port for containers 2023-09-24 17:48:25 +02:00
Andras Bacsai
0c35726a8d fix: add traefik labels no matter if traefik is selected or not 2023-09-24 17:47:43 +02:00
Andras Bacsai
e74899611b fix: links with path 2023-09-24 17:39:12 +02:00
Andras Bacsai
e9149e534d temporary disable containerstatusjob failed 2023-09-24 17:31:41 +02:00
Chingiz Mammadov
7cded7a36d ui: Fixed z-index for version link. 2023-09-24 14:12:07 +04:00
Andras Bacsai
ba74d55b4c fix: report livewire errors 2023-09-24 12:10:36 +02:00
130 changed files with 2679 additions and 1472 deletions

View File

@@ -4,7 +4,7 @@ on:
push: push:
branches: [ "next" ] branches: [ "next" ]
paths: paths:
- .github/workflows/coolify-helper.yml - .github/workflows/coolify-helper-next.yml
- docker/coolify-helper/Dockerfile - docker/coolify-helper/Dockerfile
env: env:

View File

@@ -77,7 +77,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest - name: Create & publish manifest
run: | run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1 - uses: sarisia/actions-status-discord@v1
if: always() if: always()
with: with:

View File

@@ -22,6 +22,7 @@ class StartProxy
if (!$configuration) { if (!$configuration) {
throw new \Exception("Configuration is not synced"); throw new \Exception("Configuration is not synced");
} }
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
@@ -50,12 +51,15 @@ class StartProxy
"echo '####### Proxy installed successfully.'" "echo '####### Proxy installed successfully.'"
]); ]);
$commands = $commands->merge(connectProxyToNetworks($server)); $commands = $commands->merge(connectProxyToNetworks($server));
if (!$async) { if ($async) {
instant_remote_process($commands, $server);
return 'OK';
} else {
$activity = remote_process($commands, $server); $activity = remote_process($commands, $server);
return $activity; return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
return 'OK';
} }
} }
} }

View File

@@ -16,8 +16,7 @@ class UpdateCoolify
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost'; $this->server = Server::find(0)->first();
$this->server = Server::where('name', $localhost_name)->first();
if (!$this->server) { if (!$this->server) {
return; return;
} }

View File

@@ -10,22 +10,16 @@ class StartService
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
$workdir = service_configuration_dir() . "/{$service->uuid}"; $service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Creating Docker network.'";
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'"; $commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'"; $commands[] = "echo '####### Pulling images.'";
$commands[] = "mkdir -p $workdir"; $commands[] = "docker compose pull";
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($service->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
$envs = $service->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
}
$commands[] = "docker compose pull --quiet";
$commands[] = "echo '####### Starting containers.'"; $commands[] = "echo '####### Starting containers.'";
$commands[] = "docker compose up -d >/dev/null 2>&1"; $commands[] = "docker compose up -d --remove-orphans --force-recreate";
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true"; $commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
$activity = remote_process($commands, $service->server); $activity = remote_process($commands, $service->server);
return $activity; return $activity;

View File

@@ -21,5 +21,6 @@ class StopService
$db->update(['status' => 'exited']); $db->update(['status' => 'exited']);
} }
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false); instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
} }
} }

View File

@@ -56,6 +56,7 @@ class Emails extends Command
$type = select( $type = select(
'Which Email should be sent?', 'Which Email should be sent?',
options: [ options: [
'updates' => 'Send Update Email to all users',
'emails-test' => 'Test', 'emails-test' => 'Test',
'application-deployment-success' => 'Application - Deployment Success', 'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed', 'application-deployment-failed' => 'Application - Deployment Failed',
@@ -69,7 +70,7 @@ class Emails extends Command
'realusers-server-lost-connection' => 'REAL - Server Lost Connection', 'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
], ],
); );
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection']; $emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (!in_array($type, $emailsGathered)) { if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to'); $this->email = text('Email Address to send to');
} }
@@ -78,6 +79,38 @@ class Emails extends Command
$this->mail = new MailMessage(); $this->mail = new MailMessage();
$this->mail->subject("Test Email"); $this->mail->subject("Test Email");
switch ($type) { switch ($type) {
case 'updates':
$teams = Team::all();
if (!$teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL;
return;
}
$emails = [];
foreach ($teams as $team) {
foreach ($team->members as $member) {
if ($member->email && $member->marketing_emails) {
$emails[] = $member->email;
}
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) {
$this->info($email);
}
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->mail = new MailMessage();
$this->mail->subject('One-click Services, Docker Compose support');
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email),
]);
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
$this->sendEmail($email);
}
}
break;
case 'emails-test': case 'emails-test':
$this->mail = (new Test())->toMail(); $this->mail = (new Test())->toMail();
$this->sendEmail(); $this->sendEmail();
@@ -141,20 +174,20 @@ class Emails extends Command
$this->mail = (new BackupSuccess($backup, $db))->toMail(); $this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail(); $this->sendEmail();
break; break;
// case 'invitation-link': // case 'invitation-link':
// $user = User::all()->first(); // $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first(); // $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) { // if (!$invitation) {
// $invitation = TeamInvitation::create([ // $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(), // 'uuid' => Str::uuid(),
// 'email' => $user->email, // 'email' => $user->email,
// 'team_id' => 1, // 'team_id' => 1,
// 'link' => 'http://example.com', // 'link' => 'http://example.com',
// ]); // ]);
// } // }
// $this->mail = (new InvitationLink($user))->toMail(); // $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail(); // $this->sendEmail();
// break; // break;
case 'waitlist-invitation-link': case 'waitlist-invitation-link':
$this->mail = new MailMessage(); $this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [ $this->mail->view('emails.waitlist-invitation', [

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Pool; use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use function Laravel\Prompts\confirm;
class SyncBunny extends Command class SyncBunny extends Command
{ {
/** /**
@@ -14,7 +16,7 @@ class SyncBunny extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'sync:bunny'; protected $signature = 'sync:bunny {--only-template} {--only-version}';
/** /**
* The console command description. * The console command description.
@@ -28,6 +30,9 @@ class SyncBunny extends Command
*/ */
public function handle() public function handle()
{ {
$that = $this;
$only_template = $this->option('only-template');
$only_version = $this->option('only-version');
$bunny_cdn = "https://cdn.coollabs.io"; $bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = "coolify"; $bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = "coolcdn"; $bunny_cdn_storage_name = "coolcdn";
@@ -39,51 +44,71 @@ class SyncBunny extends Command
$install_script = "install.sh"; $install_script = "install.sh";
$upgrade_script = "upgrade.sh"; $upgrade_script = "upgrade.sh";
$production_env = ".env.production"; $production_env = ".env.production";
$service_template = "service-templates.json";
$versions = "versions.json"; $versions = "versions.json";
PendingRequest::macro('storage', function ($file) { PendingRequest::macro('storage', function ($fileName) use($that) {
$headers = [ $headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'), 'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream' 'Content-Type' => 'application/octet-stream'
]; ];
$fileStream = fopen($file, "r"); $fileStream = fopen($fileName, "r");
$file = fread($fileStream, filesize($file)); $file = fread($fileStream, filesize($fileName));
$that->info('Uploading: ' . $fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw(); return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
}); });
PendingRequest::macro('purge', function ($url) { PendingRequest::macro('purge', function ($url) use ($that) {
$headers = [ $headers = [
'AccessKey' => env('BUNNY_API_KEY'), 'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json', 'Accept' => 'application/json',
]; ];
ray('Purging: ' . $url); $that->info('Purging: ' . $url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [ return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url, "url" => $url,
"async" => false "async" => false
]); ]);
}); });
try { try {
$confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) {
return;
}
if ($only_template) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]);
$this->info('Service template uploaded & purged...');
return;
}
if ($only_version) {
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
$this->info('versions.json uploaded & purged...');
return;
}
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->storage(file: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(file: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(file: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"), $pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(file: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"), $pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(file: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"), $pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
$pool->storage(file: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
]); ]);
ray("{$bunny_cdn}/{$bunny_cdn_path}");
Http::pool(fn (Pool $pool) => [ Http::pool(fn (Pool $pool) => [
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$production_env"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]); ]);
echo "All files uploaded & purged...\n"; $this->info("All files uploaded & purged...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo $e->getMessage(); $this->error("Error: " . $e->getMessage());
} }
} }
} }

View File

@@ -27,21 +27,28 @@ class Kernel extends ConsoleKernel
// $this->instance_auto_update($schedule); // $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule); // $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->cleanup_servers($schedule);
} else { } else {
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer(); // $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$this->instance_auto_update($schedule); $this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->cleanup_servers($schedule);
}
}
private function cleanup_servers($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
} }
} }
private function check_resources($schedule) private function check_resources($schedule)
{ {
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true); $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
ray($servers);
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
} }

View File

@@ -55,10 +55,12 @@ class Handler extends ExceptionHandler
} }
app('sentry')->configureScope( app('sentry')->configureScope(
function (Scope $scope) { function (Scope $scope) {
$email = auth()?->user() ? auth()->user()->email : 'guest';
$instanceAdmin = User::find(0)->email ?? 'admin@localhost';
$scope->setUser( $scope->setUser(
[ [
'email' => auth()->user()->email, 'email' => $email,
'instanceAdmin' => User::find(0)->email 'instanceAdmin' => $instanceAdmin
] ]
); );
} }

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\EnvironmentVariable;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ProjectController extends Controller class ProjectController extends Controller
{ {
@@ -41,9 +45,10 @@ class ProjectController extends Controller
public function new() public function new()
{ {
$type = request()->query('type'); $services = getServiceTemplates();
$type = Str::of(request()->query('type'));
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$server = requesT()->query('server'); $server_id = request()->query('server_id');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) { if (!$project) {
@@ -61,8 +66,71 @@ class ProjectController extends Controller
'database_uuid' => $standalone_postgresql->uuid, 'database_uuid' => $standalone_postgresql->uuid,
]); ]);
} }
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
ray($oneClickServiceName);
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
}
if ($oneClickService) {
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = Str::before($value, '=');
$value = Str::of(Str::after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);
break;
case 'PASSWORD_64':
$generatedValue = Str::password(length: 64, symbols: false);
break;
case 'BASE64_64':
$generatedValue = Str::random(64);
break;
case 'BASE64_128':
$generatedValue = Str::random(128);
break;
case 'BASE64':
$generatedValue = Str::random(32);
break;
case 'USER':
$generatedValue = Str::random(16);
break;
}
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
}
return view('project.new', [ return view('project.new', [
'type' => $type 'type' => $type->value()
]); ]);
} }

View File

@@ -129,6 +129,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function selectExistingPrivateKey() public function selectExistingPrivateKey()
{ {
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey); $this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server'; $this->currentState = 'create-server';
} }
public function createNewServer() public function createNewServer()

View File

@@ -4,8 +4,8 @@ namespace App\Http\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting; use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Route;
use Livewire\Component; use Livewire\Component;
use Route;
class Help extends Component class Help extends Component
{ {
@@ -28,7 +28,7 @@ class Help extends Component
public function submit() public function submit()
{ {
try { try {
$this->rateLimit(1, 60); $this->rateLimit(3, 60);
$this->validate(); $this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free'; $subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}"; $debug = "Route: {$this->path}";
@@ -42,7 +42,7 @@ class Help extends Component
); );
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}"); $mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io'); send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.'); $this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -22,9 +22,6 @@ class General extends Component
public string $git_branch; public string $git_branch;
public string|null $git_commit_sha; public string|null $git_commit_sha;
public string $build_pack; public string $build_pack;
public string|null $wildcard_domain = null;
public string|null $server_wildcard_domain = null;
public string|null $global_wildcard_domain = null;
public bool $is_static; public bool $is_static;
public bool $is_git_submodules_enabled; public bool $is_git_submodules_enabled;
@@ -91,52 +88,31 @@ class General extends Component
$this->application->settings->save(); $this->application->settings->save();
$this->application->save(); $this->application->save();
$this->application->refresh(); $this->application->refresh();
$this->checkWildCardDomain();
$this->emit('success', 'Application settings updated!'); $this->emit('success', 'Application settings updated!');
} }
protected function checkWildCardDomain() public function getWildcardDomain() {
{ $server = data_get($this->application, 'destination.server');
$coolify_instance_settings = InstanceSettings::get(); if ($server) {
$this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain'); $fqdn = generateFqdn($server, $this->application->uuid);
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain'); ray($fqdn);
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null; $this->application->fqdn = $fqdn;
} $this->application->save();
$this->emit('success', 'Application settings updated!');
}
}
public function mount() public function mount()
{ {
$this->is_static = $this->application->settings->is_static; if (data_get($this->application,'settings')) {
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled; $this->is_static = $this->application->settings->is_static;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled; $this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled; $this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; $this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->checkWildCardDomain(); $this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
} }
public function generateGlobalRandomDomain()
{
// Set wildcard domain based on Global wildcard domain
$url = Url::fromString($this->global_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function generateServerRandomDomain()
{
// Set wildcard domain based on Server wildcard domain
$url = Url::fromString($this->server_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
} }
public function submit() public function submit()

View File

@@ -72,8 +72,7 @@ class Previews extends Component
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
{ {
try { try {
$container_name = generateApplicationContainerName($this->application); $container_name = generateApplicationContainerName($this->application, $pull_request_id);
ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();

View File

@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
public $database; public $database;
public $frequency; public $frequency;
public bool $enabled = true; public bool $enabled = true;
public bool $save_s3 = true; public bool $save_s3 = false;
public $s3_storage_id; public $s3_storage_id;
public $s3s; public $s3s;

View File

@@ -2,73 +2,142 @@
namespace App\Http\Livewire\Project\New; namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project; use App\Models\Project;
use App\Models\Service; use App\Models\Service;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component class DockerCompose extends Component
{ {
public string $dockercompose = ''; public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters; public array $parameters;
public array $query; public array $query;
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
if (isDev()) { if (isDev()) {
$this->dockercompose = 'services: $this->dockerComposeRaw = 'services:
ghost: ghost:
documentation: https://ghost.org/docs/config image: ghost:5
image: ghost:5 volumes:
volumes: - ~/configs:/etc/configs/:ro
- ghost-content-data:/var/lib/ghost/content - ./var/lib/ghost/content:/tmp/ghost2/content:ro
environment: - /var/lib/ghost/content:/tmp/ghost/content:rw
- url=$SERVICE_FQDN_GHOST - ghost-content-data:/var/lib/ghost/content
- database__client=mysql - type: volume
- database__connection__host=mysql source: mydata
- database__connection__user=$SERVICE_USER_MYSQL target: /data
- database__connection__password=$SERVICE_PASSWORD_MYSQL volume:
- database__connection__database=${MYSQL_DATABASE-ghost} nocopy: true
depends_on: - type: bind
- mysql source: ./var/lib/ghost/data
mysql: target: /data
documentation: https://hub.docker.com/_/mysql - type: bind
image: mysql:8.0 source: /tmp
volumes: target: /tmp
- ghost-mysql-data:/var/lib/mysql labels:
environment: - "test.label=true"
- MYSQL_USER=${SERVICE_USER_MYSQL} ports:
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} - "3000"
- MYSQL_DATABASE=${MYSQL_DATABASE} - "3000-3005"
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT} - "8000:8000"
'; - "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "127.0.0.1::5000"
- "6060:6060/udp"
- "12400-12500:1240"
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- some-network
- other-network
environment:
- database__client=${DATABASE_CLIENT:-mysql}
- database__connection__database=${MYSQL_DATABASE:-ghost}
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
- test=${TEST:?true}
- url=$SERVICE_FQDN_GHOST
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
depends_on:
- mysql
mysql:
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=$MYSQL_DATABASE
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
- SESSION_SECRET
minio:
image: minio/minio
environment:
RACK_ENV: development
A: $A
SHOW: ${SHOW}
SHOW1: ${SHOW2-show1}
SHOW2: ${SHOW3:-show2}
SHOW3: ${SHOW4?show3}
SHOW4: ${SHOW5:?show4}
SHOW5: ${SERVICE_USER_MINIO}
SHOW6: ${SERVICE_PASSWORD_MINIO}
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
SHOW8: ${SERVICE_BASE64_64_MINIO}
SHOW9: ${SERVICE_BASE64_128_MINIO}
SHOW10: ${SERVICE_BASE64_MINIO}
SHOW11:
';
} }
} }
public function submit() public function submit()
{ {
$this->validate([ try {
'dockercompose' => 'required' $this->validate([
]); 'dockerComposeRaw' => 'required'
$server_id = $this->query['server_id']; ]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'is_build_time' => false,
'is_preview' => false,
'service_id' => $service->id,
]);
}
$service->name = "service-$service->uuid";
$service = Service::create([ $service->parse(isNew: true);
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockercompose,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$service->name = "service-$service->uuid";
$service->parse(isNew: true); return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
return redirect()->route('project.service', [ 'environment_name' => $environment->name,
'service_uuid' => $service->uuid, 'project_uuid' => $project->uuid,
'environment_name' => $environment->name, ]);
'project_uuid' => $project->uuid, } catch (\Throwable $e) {
]); return handleError($e, $this);
}
} }
} }

View File

@@ -11,6 +11,7 @@ use App\Traits\SaveFromRedirect;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Livewire\Component; use Livewire\Component;
use Spatie\Url\Url;
class GithubPrivateRepository extends Component class GithubPrivateRepository extends Component
{ {
@@ -95,6 +96,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage(); $this->loadBranchByPage();
} }
} }
$this->selected_branch_name = data_get($this->branches,'0.name');
} }
protected function loadBranchByPage() protected function loadBranchByPage()
@@ -144,10 +146,9 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
$application->settings->save(); $application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io"; $fqdn = generateFqdn($destination->server, $application->uuid);
if (isDev()) { $application->fqdn = $fqdn;
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid); $application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save(); $application->save();

View File

@@ -46,7 +46,6 @@ class GithubPrivateRepositoryDeployKey extends Component
private GithubApp|GitlabApp|null $git_source = null; private GithubApp|GitlabApp|null $git_source = null;
private string $git_host; private string $git_host;
private string $git_repository; private string $git_repository;
private string $git_branch;
public function mount() public function mount()
{ {
@@ -96,7 +95,7 @@ class GithubPrivateRepositoryDeployKey extends Component
$application_init = [ $application_init = [
'name' => generate_random_name(), 'name' => generate_random_name(),
'git_repository' => $this->git_repository, 'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch, 'git_branch' => $this->branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git", 'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',
'ports_exposes' => $this->port, 'ports_exposes' => $this->port,
@@ -112,10 +111,8 @@ class GithubPrivateRepositoryDeployKey extends Component
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
$application->settings->save(); $application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io"; $fqdn = generateFqdn($destination->server, $application->uuid);
if (isDev()) { $application->fqdn = $fqdn;
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$application->name = generate_random_name($application->uuid); $application->name = generate_random_name($application->uuid);
$application->save(); $application->save();
@@ -134,11 +131,6 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->repository_url_parsed = Url::fromString($this->repository_url); $this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost(); $this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
}
if ($this->git_host == 'github.com') { if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first(); $this->git_source = GithubApp::where('name', 'Public GitHub')->first();

View File

@@ -26,6 +26,10 @@ class PublicGitRepository extends Component
public string $git_branch = 'main'; public string $git_branch = 'main';
public int $rate_limit_remaining = 0; public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0; public $rate_limit_reset = 0;
private object $repository_url_parsed;
public GithubApp|GitlabApp|null $git_source = null;
public string $git_host;
public string $git_repository;
protected $rules = [ protected $rules = [
'repository_url' => 'required|url', 'repository_url' => 'required|url',
'port' => 'required|numeric', 'port' => 'required|numeric',
@@ -38,10 +42,6 @@ class PublicGitRepository extends Component
'is_static' => 'static', 'is_static' => 'static',
'publish_directory' => 'publish directory', 'publish_directory' => 'publish directory',
]; ];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private string $git_repository;
public function mount() public function mount()
{ {
@@ -76,13 +76,15 @@ class PublicGitRepository extends Component
$this->get_branch(); $this->get_branch();
$this->selected_branch = $this->git_branch; $this->selected_branch = $this->git_branch;
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); ray($e->getMessage());
} if (!$this->branch_found && $this->git_branch == 'main') {
if (!$this->branch_found && $this->git_branch == 'main') { try {
try { $this->git_branch = 'master';
$this->git_branch = 'master'; $this->get_branch();
$this->get_branch(); } catch (\Throwable $e) {
} catch (\Throwable $e) { return handleError($e, $this);
}
} else {
return handleError($e, $this); return handleError($e, $this);
} }
} }
@@ -122,9 +124,6 @@ class PublicGitRepository extends Component
$project_uuid = $this->parameters['project_uuid']; $project_uuid = $this->parameters['project_uuid'];
$environment_name = $this->parameters['environment_name']; $environment_name = $this->parameters['environment_name'];
$this->get_git_source();
$this->git_branch = $this->selected_branch ?? $this->git_branch;
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); $destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) { if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first(); $destination = SwarmDocker::where('uuid', $destination_uuid)->first();
@@ -156,10 +155,8 @@ class PublicGitRepository extends Component
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
$application->settings->save(); $application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io"; $fqdn = generateFqdn($destination->server, $application->uuid);
if (isDev()) { $application->fqdn = $fqdn;
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid); $application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$application->save(); $application->save();

View File

@@ -3,12 +3,12 @@
namespace App\Http\Livewire\Project\New; namespace App\Http\Livewire\Project\New;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Countable; use Countable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Livewire\Component; use Livewire\Component;
use Route;
class Select extends Component class Select extends Component
{ {
@@ -21,12 +21,16 @@ class Select extends Component
public Collection|array $standaloneDockers = []; public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = []; public Collection|array $swarmDockers = [];
public array $parameters; public array $parameters;
public Collection|array $services = [];
public bool $loadingServices = true;
public bool $loading = false;
public ?string $existingPostgresqlUrl = null; public ?string $existingPostgresqlUrl = null;
protected $queryString = [ protected $queryString = [
'server', 'server',
]; ];
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
@@ -44,9 +48,31 @@ class Select extends Component
// return handleError($e, $this); // return handleError($e, $this);
// } // }
// } // }
public function loadThings()
{
$this->loadServices();
$this->loadServers();
}
public function loadServices(bool $forceReload = false)
{
try {
if ($forceReload) {
Cache::forget('services');
}
$this->services = getServiceTemplates();
$this->emit('success', 'Successfully loaded services.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->loadingServices = false;
}
}
public function setType(string $type) public function setType(string $type)
{ {
$this->type = $type; $this->type = $type;
if ($this->loading) return;
$this->loading = true;
if ($type === "existing-postgresql") { if ($type === "existing-postgresql") {
$this->current_step = $type; $this->current_step = $type;
return; return;
@@ -87,7 +113,7 @@ class Select extends Component
]); ]);
} }
public function load_servers() public function loadServers()
{ {
$this->servers = Server::isUsable()->get(); $this->servers = Server::isUsable()->get();
} }

View File

@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$port = get_port_from_dockerfile($this->dockerfile); $port = get_port_from_dockerfile($this->dockerfile);
if (!$port) {
$port = 80;
}
$application = Application::create([ $application = Application::create([
'name' => 'dockerfile-' . new Cuid2(7), 'name' => 'dockerfile-' . new Cuid2(7),
'repository_project_id' => 0, 'repository_project_id' => 0,
@@ -60,10 +63,7 @@ CMD ["nginx", "-g", "daemon off;"]
'source_type' => GithubApp::class 'source_type' => GithubApp::class
]); ]);
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io"; $fqdn = generateFqdn($destination->server, $application->uuid);
if (isDev()) {
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$application->update([ $application->update([
'name' => 'dockerfile-' . $application->uuid, 'name' => 'dockerfile-' . $application->uuid,
'fqdn' => $fqdn 'fqdn' => $fqdn

View File

@@ -8,23 +8,47 @@ use Livewire\Component;
class Application extends Component class Application extends Component
{ {
public ServiceApplication $application; public ServiceApplication $application;
public $parameters;
protected $rules = [ protected $rules = [
'application.human_name' => 'nullable', 'application.human_name' => 'nullable',
'application.description' => 'nullable', 'application.description' => 'nullable',
'application.fqdn' => 'nullable', 'application.fqdn' => 'nullable',
'application.image' => 'required',
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
]; ];
public function render() public function render()
{ {
ray($this->application->fileStorages()->get());
return view('livewire.project.service.application'); return view('livewire.project.service.application');
} }
public function instantSave()
{
$this->submit();
}
public function delete()
{
try {
$this->application->delete();
$this->emit('success', 'Application deleted successfully.');
return redirect()->route('project.service', $this->parameters);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();
}
public function submit() public function submit()
{ {
try { try {
$this->validate(); $this->validate();
$this->application->save(); $this->application->save();
updateCompose($this->application);
$this->emit('success', 'Application saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); return handleError($e, $this);
} finally { } finally {
$this->emit('generateDockerCompose'); $this->emit('generateDockerCompose');
} }

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class ComposeModal extends Component
{
public string $raw;
public string $actual;
public function render()
{
return view('livewire.project.service.compose-modal');
}
public function submit() {
$this->emit('warning', "Saving new docker compose...");
$this->emit('saveCompose', $this->raw);
}
}

View File

@@ -2,26 +2,41 @@
namespace App\Http\Livewire\Project\Service; namespace App\Http\Livewire\Project\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use Livewire\Component; use Livewire\Component;
class Database extends Component class Database extends Component
{ {
public ServiceDatabase $database; public ServiceDatabase $database;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [ protected $rules = [
'database.human_name' => 'nullable', 'database.human_name' => 'nullable',
'database.description' => 'nullable', 'database.description' => 'nullable',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
]; ];
public function render() public function render()
{ {
return view('livewire.project.service.database'); return view('livewire.project.service.database');
} }
public function mount() {
$this->refreshFileStorages();
}
public function instantSave() {
$this->submit();
}
public function refreshFileStorages()
{
$this->fileStorages = $this->database->fileStorages()->get();
}
public function submit() public function submit()
{ {
try { try {
$this->validate(); $this->validate();
$this->database->save(); $this->database->save();
updateCompose($this->database);
$this->emit('success', 'Database saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
} finally { } finally {

View File

@@ -3,17 +3,56 @@
namespace App\Http\Livewire\Project\Service; namespace App\Http\Livewire\Project\Service;
use App\Models\LocalFileVolume; use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component class FileStorage extends Component
{ {
public LocalFileVolume $fileStorage; public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase $service;
public string $fs_path;
public ?string $workdir = null;
protected $rules = [ protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required', 'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required', 'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable', 'fileStorage.content' => 'nullable',
]; ];
public function mount()
{
$this->service = $this->fileStorage->service;
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->service->service->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();
try {
$this->validate();
if ($this->fileStorage->is_directory) {
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer($this->service);
$this->emit('success', 'File updated successfully.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);
$this->fileStorage->save();
return handleError($e, $this);
}
}
public function instantSave()
{
$this->submit();
}
public function render() public function render()
{ {
return view('livewire.project.service.file-storage'); return view('livewire.project.service.file-storage');

View File

@@ -2,45 +2,71 @@
namespace App\Http\Livewire\Project\Service; namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service; use App\Models\Service;
use Livewire\Component; use Livewire\Component;
class Index extends Component class Index extends Component
{ {
public Service $service; public Service $service;
public $applications;
public $databases;
public array $parameters; public array $parameters;
public array $query; public array $query;
protected $rules = [ protected $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required', 'service.docker_compose' => 'required',
'service.name' => 'required', 'service.name' => 'required',
'service.description' => 'required', 'service.description' => 'nullable',
]; ];
protected $listeners = ["saveCompose"];
public function render()
{
return view('livewire.project.service.index');
}
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->refreshStack();
} }
public function render() public function saveCompose($raw)
{ {
return view('livewire.project.service.index'); $this->service->docker_compose_raw = $raw;
$this->submit();
} }
public function save() { public function checkStatus()
$this->service->save(); {
$this->service->parse(); dispatch_sync(new ContainerStatusJob($this->service->server));
$this->service->refresh(); $this->refreshStack();
$this->emit('refreshEnvs');
} }
public function submit() { public function refreshStack()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
}
public function submit()
{
try { try {
$this->validate(); $this->validate();
$this->service->save(); $this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->refreshStack();
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -39,5 +39,6 @@ class Navbar extends Component
{ {
StopService::run($this->service); StopService::run($this->service);
$this->service->refresh(); $this->service->refresh();
$this->emit('success', 'Service stopped successfully.');
} }
} }

View File

@@ -11,8 +11,8 @@ use Livewire\Component;
class Show extends Component class Show extends Component
{ {
public Service $service; public Service $service;
public ServiceApplication $serviceApplication; public ?ServiceApplication $serviceApplication = null;
public ServiceDatabase $serviceDatabase; public ?ServiceDatabase $serviceDatabase = null;
public array $parameters; public array $parameters;
public array $query; public array $query;
public Collection $services; public Collection $services;
@@ -20,16 +20,26 @@ class Show extends Component
public function mount() public function mount()
{ {
$this->services = collect([]); try {
$this->parameters = get_route_parameters(); $this->services = collect([]);
$this->query = request()->query(); $this->parameters = get_route_parameters();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); $this->query = request()->query();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first(); $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
if ($service) { $service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
$this->serviceApplication = $service; if ($service) {
} else { $this->serviceApplication = $service;
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first(); $this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
if (is_null($service)) {
throw new \Exception("Service not found.");
}
} catch(\Throwable $e) {
return handleError($e, $this);
} }
} }
public function generateDockerCompose() public function generateDockerCompose()
{ {

View File

@@ -20,6 +20,7 @@ class Danger extends Component
public function delete() public function delete()
{ {
// Should be queued
try { try {
if ($this->resource->type() === 'service') { if ($this->resource->type() === 'service') {
$server = $this->resource->server; $server = $this->resource->server;

View File

@@ -9,13 +9,13 @@ class Add extends Component
public $parameters; public $parameters;
public bool $is_preview = false; public bool $is_preview = false;
public string $key; public string $key;
public string $value; public ?string $value = null;
public bool $is_build_time = false; public bool $is_build_time = false;
protected $listeners = ['clearAddEnv' => 'clear']; protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [ protected $rules = [
'key' => 'required|string', 'key' => 'required|string',
'value' => 'required|string', 'value' => 'nullable',
'is_build_time' => 'required|boolean', 'is_build_time' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -32,6 +32,7 @@ class Add extends Component
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
ray($this->key, $this->value, $this->is_build_time);
$this->emitUp('submit', [ $this->emitUp('submit', [
'key' => $this->key, 'key' => $this->key,
'value' => $this->value, 'value' => $this->value,

View File

@@ -53,7 +53,6 @@ class All extends Component
$this->resource->environment_variables_preview()->delete(); $this->resource->environment_variables_preview()->delete();
} else { } else {
$variables = parseEnvFormatToArray($this->variables); $variables = parseEnvFormatToArray($this->variables);
ray($variables);
$existingVariables = $this->resource->environment_variables(); $existingVariables = $this->resource->environment_variables();
$this->resource->environment_variables()->delete(); $this->resource->environment_variables()->delete();
} }
@@ -110,11 +109,16 @@ class All extends Component
$environment->is_build_time = $data['is_build_time']; $environment->is_build_time = $data['is_build_time'];
$environment->is_preview = $data['is_preview']; $environment->is_preview = $data['is_preview'];
if ($this->resource->type() === 'application') { switch ($this->resource->type()) {
$environment->application_id = $this->resource->id; case 'application':
} $environment->application_id = $this->resource->id;
if ($this->resource->type() === 'standalone-postgresql') { break;
$environment->standalone_postgresql_id = $this->resource->id; case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
} }
$environment->save(); $environment->save();
$this->refreshEnvs(); $this->refreshEnvs();

View File

@@ -5,17 +5,19 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class Show extends Component class Show extends Component
{ {
public $parameters; public $parameters;
public ModelsEnvironmentVariable $env; public ModelsEnvironmentVariable $env;
public ?string $modalId = null; public ?string $modalId = null;
public bool $isDisabled = false;
public string $type; public string $type;
protected $rules = [ protected $rules = [
'env.key' => 'required|string', 'env.key' => 'required|string',
'env.value' => 'required|string', 'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean', 'env.is_build_time' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -26,6 +28,10 @@ class Show extends Component
public function mount() public function mount()
{ {
$this->isDisabled = false;
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
$this->modalId = new Cuid2(7); $this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
} }

View File

@@ -11,6 +11,7 @@ class Show extends Component
public LocalPersistentVolume $storage; public LocalPersistentVolume $storage;
public bool $isReadOnly = false; public bool $isReadOnly = false;
public ?string $modalId = null; public ?string $modalId = null;
public ?string $realName = null;
protected $rules = [ protected $rules = [
'storage.name' => 'required|string', 'storage.name' => 'required|string',
@@ -25,6 +26,11 @@ class Show extends Component
public function mount() public function mount()
{ {
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
} else {
$this->realName = $this->storage->name;
}
$this->modalId = new Cuid2(7); $this->modalId = new Cuid2(7);
} }

View File

@@ -38,8 +38,8 @@ class Proxy extends Component
public function select_proxy($proxy_type) public function select_proxy($proxy_type)
{ {
$this->server->proxy->type = $proxy_type; $this->server->proxy->set('status', 'exited');
$this->server->proxy->status = 'exited'; $this->server->proxy->set('type', $proxy_type);
$this->server->save(); $this->server->save();
$this->selectedProxy = $this->server->proxy->type; $this->selectedProxy = $this->server->proxy->type;
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');

View File

@@ -11,6 +11,8 @@ class Modal extends Component
public function proxyStatusUpdated() public function proxyStatusUpdated()
{ {
$this->server->proxy->set('status', 'running');
$this->server->save();
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
} }
} }

View File

@@ -18,10 +18,8 @@ class Status extends Component
public function getProxyStatus() public function getProxyStatus()
{ {
try { try {
if ($this->server->isFunctional()) { dispatch_sync(new ContainerStatusJob($this->server));
dispatch_sync(new ContainerStatusJob($this->server)); $this->emit('proxyStatusUpdated');
$this->emit('proxyStatusUpdated');
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -14,8 +14,8 @@ class ShowPrivateKey extends Component
public function setPrivateKey($newPrivateKeyId) public function setPrivateKey($newPrivateKeyId)
{ {
try { try {
refresh_server_connection($this->server->privateKey);
$oldPrivateKeyId = $this->server->private_key_id; $oldPrivateKeyId = $this->server->private_key_id;
refresh_server_connection($this->server->privateKey);
$this->server->update([ $this->server->update([
'private_key_id' => $newPrivateKeyId 'private_key_id' => $newPrivateKeyId
]); ]);

View File

@@ -45,7 +45,12 @@ class Configuration extends Component
$this->settings->do_not_track = $this->do_not_track; $this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled; $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled; $this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->next_channel = $this->next_channel; if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
} else {
$this->settings->next_channel = $this->next_channel;
}
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings updated!'); $this->emit('success', 'Settings updated!');
} }
@@ -68,7 +73,7 @@ class Configuration extends Component
{ {
$file = "$this->dynamic_config_path/coolify.yaml"; $file = "$this->dynamic_config_path/coolify.yaml";
if (empty($this->settings->fqdn)) { if (empty($this->settings->fqdn)) {
remote_process([ instant_remote_process([
"rm -f $file", "rm -f $file",
], $this->server); ], $this->server);
} else { } else {
@@ -124,7 +129,6 @@ class Configuration extends Component
]; ];
} }
$this->save_configuration_to_disk($traefik_dynamic_conf, $file); $this->save_configuration_to_disk($traefik_dynamic_conf, $file);
dispatch(new ContainerStatusJob($this->server));
} }
} }
@@ -137,7 +141,7 @@ class Configuration extends Component
$yaml; $yaml;
$base64 = base64_encode($yaml); $base64 = base64_encode($yaml);
remote_process([ instant_remote_process([
"mkdir -p $this->dynamic_config_path", "mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file", "echo '$base64' | base64 -d > $file",
], $this->server); ], $this->server);

View File

@@ -89,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/'); $this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application); $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server); savePrivateKeyToFs($this->server);
$this->saved_outputs = collect(); $this->saved_outputs = collect();
@@ -97,7 +97,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) { if ($this->application->fqdn) {
$preview_fqdn = getOnlyFqdn(data_get($this->preview, 'fqdn')); if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
$template = $this->application->preview_url_template; $template = $this->application->preview_url_template;
$url = Url::fromString($this->application->fqdn); $url = Url::fromString($this->application->fqdn);
$host = $url->getHost(); $host = $url->getHost();
@@ -284,9 +286,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update() private function rolling_update()
{ {
$this->start_by_compose_file(); if (count($this->application->ports_mappings_array) > 0) {
$this->health_check(); $this->execute_remote_command(
$this->stop_running_container(); ["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
);
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
$this->execute_remote_command(
["echo -n 'Rolling update started.'"],
);
$this->start_by_compose_file();
$this->health_check();
$this->stop_running_container();
}
} }
private function health_check() private function health_check()
{ {
@@ -388,7 +401,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{ {
$this->execute_remote_command( $this->execute_remote_command(
[ [
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '" "echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->workdir}. '"
], ],
[ [
$this->importing_git_repository() $this->importing_git_repository()
@@ -529,7 +542,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'environment' => $environment_variables, 'environment' => $environment_variables,
'labels' => generateLabelsApplication($this->application, $this->preview), 'labels' => generateLabelsApplication($this->application, $this->preview),
// 'expose' => $ports, 'expose' => $ports,
'networks' => [ 'networks' => [
$this->destination->network, $this->destination->network,
], ],
@@ -704,10 +717,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
} }
private function stop_running_container() private function stop_running_container(bool $force = false)
{ {
if ($this->currently_running_container_name) { if ($this->currently_running_container_name) {
if ($this->newVersionIsHealthy) { if ($this->newVersionIsHealthy || $force) {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Removing old version of your application.'"], ["echo -n 'Removing old version of your application.'"],
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
@@ -724,7 +737,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function start_by_compose_file() private function start_by_compose_file()
{ {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Rolling update started.'"], ["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
); );
} }

View File

@@ -28,11 +28,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
$this->handle();
} }
public function middleware(): array public function middleware(): array
{ {
return [new WithoutOverlapping($this->server->uuid)]; return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
} }
public function uniqueId(): string public function uniqueId(): string
@@ -74,14 +75,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications(); $applications = $this->server->applications();
$databases = $this->server->databases(); $databases = $this->server->databases();
$services = $this->server->services(); $services = $this->server->services()->get();
$previews = $this->server->previews(); $previews = $this->server->previews();
$this->server->proxyType();
/// Check if proxy is running /// Check if proxy is running
$foundProxyContainer = $containers->filter(function ($value, $key) { $foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy'; return data_get($value, 'Name') === '/coolify-proxy';
})->first(); })->first();
if (!$foundProxyContainer) { if (!$foundProxyContainer) {
ray('Proxy not found, starting it...');
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
StartProxy::run($this->server, false); StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server)); $this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
@@ -99,16 +101,16 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
foreach ($containers as $container) { foreach ($containers as $container) {
$containerStatus = data_get($container, 'State.Status'); $containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status','unhealthy'); $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)"; $containerStatus = "$containerStatus ($containerHealth)";
$labels = data_get($container, 'Config.Labels'); $labels = data_get($container, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels)); $labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId'); $labelId = data_get($labels, 'coolify.applicationId');
if ($labelId) { if ($labelId) {
if (str_contains($labelId, '-pr-')) { if (str_contains($labelId, '-pr-')) {
$previewId = (int) Str::after($labelId, '-pr-'); $pullRequestId = data_get($labels, 'coolify.pullRequestId');
$applicationId = (int) Str::before($labelId, '-pr-'); $applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first(); $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) { if ($preview) {
$foundApplicationPreviews[] = $preview->id; $foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status; $statusFromDb = $preview->status;
@@ -147,25 +149,29 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
$serviceLabelId = data_get($labels, 'coolify.serviceId'); $serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) { if ($serviceLabelId) {
$coolifyName = data_get($labels, 'coolify.name'); $subType = data_get($labels, 'coolify.service.subType');
$serviceName = Str::of($coolifyName)->before('-'); $subId = data_get($labels, 'coolify.service.subId');
$serviceUuid = Str::of($coolifyName)->after('-'); $service = $services->where('id', $serviceLabelId)->first();
$service = $services->where('uuid', $serviceUuid)->first(); if (!$service) {
continue;
}
if ($subType === 'application') {
$service = $service->applications()->where('id', $subId)->first();
} else {
$service = $service->databases()->where('id', $subId)->first();
}
if ($service) { if ($service) {
$foundService = $service->byName($serviceName); $foundServices[] = "$service->id-$service->name";
if ($foundService) { $statusFromDb = $service->status;
$foundServices[] = "$foundService->id-$serviceName"; if ($statusFromDb !== $containerStatus) {
$statusFromDb = $foundService->status; // ray('Updating status: ' . $containerStatus);
if ($statusFromDb !== $containerStatus) { $service->update(['status' => $containerStatus]);
// ray('Updating status: ' . $containerStatus);
$foundService->update(['status' => $containerStatus]);
}
} }
} }
} }
} }
$exitedServices = collect([]); $exitedServices = collect([]);
foreach ($services->get() as $service) { foreach ($services as $service) {
$apps = $service->applications()->get(); $apps = $service->applications()->get();
$dbs = $service->databases()->get(); $dbs = $service->databases()->get();
foreach ($apps as $app) { foreach ($apps as $app) {

View File

@@ -61,7 +61,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void public function handle(): void
{ {
try { try {
if (data_get($this->database, 'status') !== 'running') { $status = Str::of(data_get($this->database, 'status'));
if (!$status->startsWith('running') && $this->database->id !== 0) {
ray('database not running'); ray('database not running');
return; return;
} }
@@ -89,7 +90,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$this->upload_to_s3(); $this->upload_to_s3();
} }
$this->save_backup_logs(); $this->save_backup_logs();
// TODO: Notify user
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); ray($e->getMessage());
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());

View File

@@ -16,65 +16,66 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 500; public $timeout = 1000;
public ?string $dockerRootFilesystem = null; public ?string $dockerRootFilesystem = null;
public ?int $usageBefore = null; public ?int $usageBefore = null;
public function middleware(): array public function middleware(): array
{ {
return [ return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
(new WithoutOverlapping("dockerimagejobs"))->shared(),
];
} }
public function __construct()
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{ {
} }
public function handle(): void public function handle(): void
{ {
$queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get(); $queuedCount = 0;
if ($queue->count() > 0) { $this->server->applications()->each(function ($application) use ($queuedCount) {
$count = data_get($application->deployments(), 'count', 0);
$queuedCount += $count;
});
if ($queuedCount > 0) {
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange'); ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
return; return;
} }
try { try {
// ray()->showQueries()->color('orange'); if (!$this->server->isFunctional()) {
$servers = Server::all(); return;
foreach ($servers as $server) { }
if ( if (isDev()) {
!$server->isFunctional() $this->dockerRootFilesystem = "/";
) { } else {
continue; $this->dockerRootFilesystem = instant_remote_process(
} [
if (isDev()) { "stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
$this->dockerRootFilesystem = "/"; ],
$this->server,
false
);
}
if (!$this->dockerRootFilesystem) {
return;
}
$this->usageBefore = $this->getFilesystemUsage();
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name)->color('orange');
instant_remote_process(['docker image prune -af'], $this->server);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
instant_remote_process(['docker builder prune -af'], $this->server);
$usageAfter = $this->getFilesystemUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name)->color('orange');
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
} else { } else {
$this->dockerRootFilesystem = instant_remote_process( ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange');
[
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
],
$server,
false
);
}
if (!$this->dockerRootFilesystem) {
continue;
}
$this->usageBefore = $this->getFilesystemUsage($server);
if ($this->usageBefore >= $server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $server->name)->color('orange');
instant_remote_process(['docker image prune -af'], $server);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server);
instant_remote_process(['docker builder prune -af'], $server);
$usageAfter = $this->getFilesystemUsage($server);
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name)->color('orange');
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name);
} else {
ray('DockerCleanupJob failed to save disk space on ' . $server->name)->color('orange');
}
} else {
ray('No need to clean up ' . $server->name)->color('orange');
} }
} else {
ray('No need to clean up ' . $this->server->name)->color('orange');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage()); send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
@@ -83,8 +84,8 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
private function getFilesystemUsage(Server $server) private function getFilesystemUsage()
{ {
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $server, false); return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false);
} }
} }

View File

@@ -212,13 +212,9 @@ class Application extends BaseModel
{ {
if (data_get($this, 'private_key_id')) { if (data_get($this, 'private_key_id')) {
return 'deploy_key'; return 'deploy_key';
} } else if (data_get($this, 'source')) {
if (data_get($this, 'source')) {
return 'source'; return 'source';
} }
if (data_get($this, 'private_key_id')) {
return 'deploy_key';
}
throw new \Exception('No deployment type found'); throw new \Exception('No deployment type found');
} }
public function could_set_build_commands(): bool public function could_set_build_commands(): bool
@@ -230,7 +226,7 @@ class Application extends BaseModel
} }
public function git_based(): bool public function git_based(): bool
{ {
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->dockercompose || $this->build_pack === 'dockercompose') { if ($this->dockerfile) {
return false; return false;
} }
return true; return true;

View File

@@ -14,7 +14,7 @@ class Environment extends Model
public function can_delete_environment() public function can_delete_environment()
{ {
return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0; return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
} }
public function applications() public function applications()

View File

@@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
class GithubApp extends BaseModel class GithubApp extends BaseModel
{ {
protected $guarded = []; protected $guarded = [];
protected $appends = ['type']; protected $appends = ['type'];
protected $casts = [ protected $casts = [
@@ -17,6 +18,7 @@ class GithubApp extends BaseModel
'webhook_secret', 'webhook_secret',
]; ];
static public function public() static public function public()
{ {
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get(); return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
@@ -34,6 +36,7 @@ class GithubApp extends BaseModel
if ($applications_count > 0) { if ($applications_count > 0) {
throw new \Exception('You cannot delete this GitHub App because it is in use by ' . $applications_count . ' application(s). Delete them first.'); throw new \Exception('You cannot delete this GitHub App because it is in use by ' . $applications_count . ' application(s). Delete them first.');
} }
$github_app->privateKey()->delete();
}); });
} }

View File

@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
class LocalFileVolume extends BaseModel class LocalFileVolume extends BaseModel
{ {
@@ -11,6 +12,42 @@ class LocalFileVolume extends BaseModel
public function service() public function service()
{ {
return $this->morphTo(); return $this->morphTo('resource');
}
public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service)
{
$workdir = $service->service->workdir();
$server = $service->service->server;
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",
"cd $workdir"
]);
$fileVolume = $this;
$path = Str::of(data_get($fileVolume, 'fs_path'));
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir . $path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
ray($isFile);
if ($isFile == 'OK' && $fileVolume->is_directory) {
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
}
if (($isFile == 'NOK' && !$fileVolume->is_directory) || $isFile == 'OK') {
$rootDir = Str::of($path)->dirname();
$commands->push("mkdir -p $rootDir > /dev/null 2>&1 || true");
$commands->push("touch $path > /dev/null 2>&1 || true");
if ($content) {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d > $path");
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}
return instant_remote_process($commands, $server);
} }
} }

View File

@@ -12,16 +12,19 @@ class LocalPersistentVolume extends Model
public function application() public function application()
{ {
return $this->morphTo(); return $this->morphTo('resource');
} }
public function service() public function service()
{ {
return $this->morphTo(); return $this->morphTo('resource');
}
public function database()
{
return $this->morphTo('resource');
} }
public function standalone_postgresql() public function standalone_postgresql()
{ {
return $this->morphTo(); return $this->morphTo('resource');
} }
protected function name(): Attribute protected function name(): Attribute

View File

@@ -78,7 +78,8 @@ class Server extends BaseModel
return $this->hasOne(ServerSetting::class); return $this->hasOne(ServerSetting::class);
} }
public function proxyType() { public function proxyType()
{
$type = $this->proxy->get('type'); $type = $this->proxy->get('type');
if (is_null($type)) { if (is_null($type)) {
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value; $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
@@ -115,11 +116,13 @@ class Server extends BaseModel
return $standaloneDocker->applications; return $standaloneDocker->applications;
})->flatten(); })->flatten();
} }
public function services() { public function services()
{
return $this->hasMany(Service::class); return $this->hasMany(Service::class);
} }
public function previews() { public function previews()
{
return $this->destinations()->map(function ($standaloneDocker) { return $this->destinations()->map(function ($standaloneDocker) {
return $standaloneDocker->applications->map(function ($application) { return $standaloneDocker->applications->map(function ($application) {
return $application->previews; return $application->previews;
@@ -161,6 +164,9 @@ class Server extends BaseModel
public function isProxyShouldRun() public function isProxyShouldRun()
{ {
$shouldRun = false; $shouldRun = false;
if ($this->proxyType() === ProxyTypes::NONE->value) {
return false;
}
foreach ($this->applications() as $application) { foreach ($this->applications() as $application) {
if (data_get($application, 'fqdn')) { if (data_get($application, 'fqdn')) {
$shouldRun = true; $shouldRun = true;
@@ -175,7 +181,8 @@ class Server extends BaseModel
} }
return $shouldRun; return $shouldRun;
} }
public function isFunctional() { public function isFunctional()
{
return $this->settings->is_reachable && $this->settings->is_usable; return $this->settings->is_reachable && $this->settings->is_usable;
} }
} }

View File

@@ -2,10 +2,10 @@
namespace App\Models; namespace App\Models;
use App\Enums\ProxyTypes;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Url\Url; use Spatie\Url\Url;
@@ -41,6 +41,7 @@ class Service extends BaseModel
instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false); instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false);
}); });
} }
instant_remote_process(["docker network rm {$service->uuid}"], $service->server, false);
}); });
} }
public function type() public function type()
@@ -48,6 +49,12 @@ class Service extends BaseModel
return 'service'; return 'service';
} }
public function documentation()
{
$services = Cache::get('services', []);
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
return data_get($service, 'documentation', config('constants.docs.base_url'));
}
public function applications() public function applications()
{ {
return $this->hasMany(ServiceApplication::class); return $this->hasMany(ServiceApplication::class);
@@ -80,9 +87,31 @@ class Service extends BaseModel
{ {
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc'); return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
} }
public function workdir()
{
return service_configuration_dir() . "/{$this->uuid}";
}
public function saveComposeConfigs()
{
$workdir = $this->workdir();
$commands[] = "mkdir -p $workdir";
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
$envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
}
if ($envs->count() === 0) {
$commands[] = "touch .env";
}
instant_remote_process($commands, $this->server);
}
public function parse(bool $isNew = false): Collection public function parse(bool $isNew = false): Collection
{ {
ray('parsing');
// ray()->clearAll(); // ray()->clearAll();
if ($this->docker_compose_raw) { if ($this->docker_compose_raw) {
try { try {
@@ -91,73 +120,101 @@ class Service extends BaseModel
throw new \Exception($e->getMessage()); throw new \Exception($e->getMessage());
} }
$composeVolumes = collect(data_get($yaml, 'volumes', [])); $topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$composeNetworks = collect(data_get($yaml, 'networks', [])); $topLevelNetworks = collect(data_get($yaml, 'networks', []));
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
$services = data_get($yaml, 'services'); $services = data_get($yaml, 'services');
$definedNetwork = $this->uuid; $definedNetwork = $this->uuid;
$volumes = collect([]); $generatedServiceFQDNS = collect([]);
$envs = collect([]);
$ports = collect([]);
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) { $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
$container_name = "$serviceName-{$this->uuid}"; $serviceVolumes = collect(data_get($service, 'volumes', []));
$isDatabase = false; $servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
$serviceVariables = collect(data_get($service, 'environment', [])); $serviceVariables = collect(data_get($service, 'environment', []));
$serviceLabels = collect(data_get($service, 'labels', []));
$containerName = "$serviceName-{$this->uuid}";
// Decide if the service is a database // Decide if the service is a database
$image = data_get($service, 'image'); $isDatabase = false;
if ($image) { $image = data_get_str($service, 'image');
$imageName = Str::of($image)->before(':'); if ($image->contains(':')) {
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { $image = Str::of($image);
$isDatabase = true; } else {
data_set($service, 'is_database', true); $image = Str::of($image)->append(':latest');
}
$imageName = $image->before(':');
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
$isDatabase = true;
}
data_set($service, 'is_database', $isDatabase);
// Create new serviceApplication or serviceDatabase
if ($isDatabase) {
if ($isNew) {
$savedService = ServiceDatabase::create([
'name' => $serviceName,
'image' => $image,
'service_id' => $this->id
]);
} else {
$savedService = ServiceDatabase::where([
'name' => $serviceName,
'service_id' => $this->id
])->first();
}
} else {
if ($isNew) {
$savedService = ServiceApplication::create([
'name' => $serviceName,
'image' => $image,
'service_id' => $this->id
]);
} else {
$savedService = ServiceApplication::where([
'name' => $serviceName,
'service_id' => $this->id
])->first();
} }
} }
if ($isNew) { if (is_null($savedService)) {
if ($isDatabase) { if ($isDatabase) {
$savedService = ServiceDatabase::create([ $savedService = ServiceDatabase::create([
'name' => $serviceName, 'name' => $serviceName,
'image' => $image,
'service_id' => $this->id 'service_id' => $this->id
]); ]);
} else { } else {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
if (isDev()) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
}
$savedService = ServiceApplication::create([ $savedService = ServiceApplication::create([
'name' => $serviceName, 'name' => $serviceName,
'fqdn' => $defaultUsableFqdn, 'image' => $image,
'service_id' => $this->id 'service_id' => $this->id
]); ]);
} }
} else { }
if ($isDatabase) {
$savedService = $this->databases()->whereName($serviceName)->first(); // Check if image changed
} else { if ($savedService->image !== $image) {
$savedService = $this->applications()->whereName($serviceName)->first(); $savedService->image = $image;
if (data_get($savedService, 'fqdn')) { $savedService->save();
$defaultUsableFqdn = data_get($savedService, 'fqdn', null); }
} else {
if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) { // Collect/create/update networks
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io"; if ($serviceNetworks->count() > 0) {
if (isDev()) { foreach ($serviceNetworks as $networkName => $networkDetails) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io"; $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
} return $value == $networkName || $key == $networkName;
} });
if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null);
} }
$savedService->fqdn = $defaultUsableFqdn;
$savedService->save();
} }
} }
$fqdns = data_get($savedService, 'fqdn');
if ($fqdns) { // Collect/create/update ports
$fqdns = collect(Str::of($fqdns)->explode(','));
}
// Collect ports
$servicePorts = collect(data_get($service, 'ports', []));
$ports->put($serviceName, $servicePorts);
$collectedPorts = collect([]); $collectedPorts = collect([]);
if ($servicePorts->count() > 0) { if ($servicePorts->count() > 0) {
foreach ($servicePorts as $sport) { foreach ($servicePorts as $sport) {
@@ -167,279 +224,286 @@ class Service extends BaseModel
if (is_array($sport)) { if (is_array($sport)) {
$target = data_get($sport, 'target'); $target = data_get($sport, 'target');
$published = data_get($sport, 'published'); $published = data_get($sport, 'published');
$collectedPorts->push("$target:$published"); $protocol = data_get($sport, 'protocol');
$collectedPorts->push("$target:$published/$protocol");
} }
} }
} }
$savedService->ports = $collectedPorts->implode(','); $savedService->ports = $collectedPorts->implode(',');
$savedService->save(); $savedService->save();
// Collect volumes
$serviceVolumes = collect(data_get($service, 'volumes', []));
if ($serviceVolumes->count() > 0) {
LocalPersistentVolume::whereResourceId($savedService->id)->whereResourceType(get_class($savedService))->delete();
foreach ($serviceVolumes as $volume) {
if (is_string($volume) && Str::startsWith($volume, './')) {
// Local file
$fsPath = Str::before($volume, ':');
$volumePath = Str::of($volume)->after(':')->beforeLast(':');
ray($fsPath, $volumePath);
LocalFileVolume::updateOrCreate(
[
'mount_path' => $volumePath,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
],
[
'fs_path' => $fsPath,
'mount_path' => $volumePath,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
]
);
continue;
}
if (is_string($volume)) {
$volumeName = Str::before($volume, ':');
$volumePath = Str::after($volume, ':');
}
if (is_array($volume)) {
$volumeName = data_get($volume, 'source');
$volumePath = data_get($volume, 'target');
}
$volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) {
return $key == $volumeName;
});
if (!$volumeExists) {
if (Str::startsWith($volumeName, '/')) {
$volumes->put($volumeName, $volumePath);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $volumePath,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
],
[
'name' => Str::slug($volumeName, '-'),
'mount_path' => $volumePath,
'host_path' => $volumeName,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
]
);
} else {
$composeVolumes->put($volumeName, null);
LocalPersistentVolume::updateOrCreate(
[
'name' => $volumeName,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
],
[
'name' => $volumeName,
'mount_path' => $volumePath,
'host_path' => null,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
]
);
}
}
}
}
// Collect and add networks
$serviceNetworks = collect(data_get($service, 'networks', []));
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
$networkExists = $composeNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (!$networkExists) {
$composeNetworks->put($networkDetails, null);
}
}
}
// Add Coolify specific networks // Add Coolify specific networks
$definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) { $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork; return $value == $definedNetwork;
}); });
if (!$definedNetworkExists) { if (!$definedNetworkExists) {
$composeNetworks->put($definedNetwork, [ $topLevelNetworks->put($definedNetwork, [
'name' => $definedNetwork, 'name' => $definedNetwork,
'external' => false 'external' => true
]); ]);
} }
$networks = $serviceNetworks->toArray(); $networks = $serviceNetworks->toArray();
$networks = array_merge($networks, [$definedNetwork]); $networks = array_merge($networks, [$definedNetwork]);
data_set($service, 'networks', $networks); data_set($service, 'networks', $networks);
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
foreach ($serviceVolumes as $volume) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = Str::of('bind');
} else {
$type = Str::of('volume');
}
} else if (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$content = data_get($foundConfig, 'content');
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
}
}
if ($type->value() === 'bind') {
if ($source->value() === "/var/run/docker.sock") {
continue;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
continue;
}
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
]
);
} else if ($type->value() === 'volume') {
$topLevelVolumes->put($source->value(), null);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
],
[
'name' => Str::slug($source, '-'),
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService)
]
);
}
$savedService->getFilesFromServer();
}
}
// Add env_file with at least .env to the service
// $envFile = collect(data_get($service, 'env_file', []));
// if ($envFile->count() > 0) {
// if (!$envFile->contains('.env')) {
// $envFile->push('.env');
// }
// } else {
// $envFile = collect(['.env']);
// }
// data_set($service, 'env_file', $envFile->toArray());
// Get variables from the service // Get variables from the service
foreach ($serviceVariables as $variable) { foreach ($serviceVariables as $variableName => $variable) {
$value = Str::after($variable, '='); if (is_numeric($variableName)) {
if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) { $variable = Str::of($variable);
$value = Str::of(replaceVariables(Str::of($value))); if ($variable->contains('=')) {
if ($value->contains(':')) { // - SESSION_SECRET=123
$nakedName = $value->before(':'); // - SESSION_SECRET=
$nakedValue = $value->after(':'); $key = $variable->before('=');
} else if ($value->contains('-')) { $value = $variable->after('=');
$nakedName = $value->before('-');
$nakedValue = $value->after('-');
} else if ($value->contains('+')) {
$nakedName = $value->before('+');
$nakedValue = $value->after('+');
} else { } else {
$nakedName = $value; // - SESSION_SECRET
$key = $variable;
$value = null;
} }
if (isset($nakedName)) { } else {
if (isset($nakedValue)) { // SESSION_SECRET: 123
if ($nakedValue->startsWith('-')) { // SESSION_SECRET:
$nakedValue = Str::of($nakedValue)->after('-'); $key = Str::of($variableName);
$value = Str::of($variable);
}
if ($key->startsWith('SERVICE_FQDN')) {
if (is_null(data_get($savedService, 'fqdn'))) {
$fqdn = generateFqdn($this->server, $containerName);
if (substr_count($key->value(), '_') === 2 && $key->contains("=")) {
$path = $value->value();
if ($generatedServiceFQDNS->count() > 0) {
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
if ($alreadyGenerated) {
$fqdn = $generatedServiceFQDNS->get($key->value());
} else {
$generatedServiceFQDNS->put($key->value(), $fqdn);
}
} else {
$generatedServiceFQDNS->put($key->value(), $fqdn);
} }
if ($nakedValue->startsWith('+')) { $fqdn = "$fqdn$path";
$nakedValue = Str::of($nakedValue)->after('+'); }
} if (!$isDatabase) {
if (!$envs->has($nakedName->value())) { $savedService->fqdn = $fqdn;
$envs->put($nakedName->value(), $nakedValue->value()); $savedService->save();
EnvironmentVariable::updateOrCreate([ }
'key' => $nakedName->value(), }
'service_id' => $this->id, continue;
], [ }
'value' => $nakedValue->value(), if ($value?->startsWith('$')) {
$value = Str::of(replaceVariables($value));
$key = $value;
$foundEnv = EnvironmentVariable::where([
'key' => $key,
'service_id' => $this->id,
])->first();
if ($value->startsWith('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$forService = $value->afterLast('_');
$generatedValue = null;
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
$fqdn = generateFqdn($this->server, $containerName);
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
'is_build_time' => false, 'is_build_time' => false,
'service_id' => $this->id, 'service_id' => $this->id,
'is_preview' => false, 'is_preview' => false,
]); ]);
} }
if (!$isDatabase) {
$savedService->fqdn = $fqdn;
$savedService->save();
}
} else { } else {
if (!$envs->has($nakedName->value())) { switch ($command) {
$envs->put($nakedName->value(), null); case 'PASSWORD':
$envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists(); $generatedValue = Str::password(symbols: false);
if (!$envExists) { break;
EnvironmentVariable::create([ case 'PASSWORD_64':
'key' => $nakedName->value(), $generatedValue = Str::password(length: 64, symbols: false);
'value' => null, break;
'service_id' => $this->id, case 'BASE64_64':
'is_build_time' => false, $generatedValue = Str::random(64);
'is_preview' => false, break;
]); case 'BASE64_128':
} $generatedValue = Str::random(128);
break;
case 'BASE64':
$generatedValue = Str::random(32);
break;
case 'USER':
$generatedValue = Str::random(16);
break;
}
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
} }
} }
} } else {
} else { if ($value->contains(':-')) {
$variableName = Str::of(replaceVariables(Str::of($value))); $key = $value->before(':');
$generatedValue = null; $defaultValue = $value->after(':-');
if ($variableName->startsWith('SERVICE_USER')) { } else if ($value->contains('-')) {
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first(); $key = $value->before('-');
if (!$variableDefined) { $defaultValue = $value->after('-');
$generatedValue = Str::random(10); } else if ($value->contains(':?')) {
$key = $value->before(':');
$defaultValue = $value->after(':?');
} else if ($value->contains('?')) {
$key = $value->before('?');
$defaultValue = $value->after('?');
} else { } else {
$generatedValue = $variableDefined->value; $key = $value;
$defaultValue = null;
} }
if (!$envs->has($variableName->value())) { if ($foundEnv) {
$envs->put($variableName->value(), $generatedValue); $defaultValue = data_get($foundEnv, 'value');
EnvironmentVariable::updateOrCreate([
'key' => $variableName->value(),
'service_id' => $this->id,
], [
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
} else if ($variableName->startsWith('SERVICE_PASSWORD')) {
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
if (!$variableDefined) {
$generatedValue = Str::password(symbols: false);
} else {
$generatedValue = $variableDefined->value;
}
if (!$envs->has($variableName->value())) {
$envs->put($variableName->value(), $generatedValue);
EnvironmentVariable::updateOrCreate([
'key' => $variableName->value(),
'service_id' => $this->id,
], [
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
} else if ($variableName->startsWith('SERVICE_FQDN')) {
if ($fqdns) {
$number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value();
if (is_numeric($number)) {
$number = (int) $number - 1;
} else {
$number = 0;
}
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
$environments = collect(data_get($service, 'environment'));
$environments = $environments->map(function ($envValue) use ($value, $fqdn) {
$envValue = Str::of($envValue)->replace($value, $fqdn);
return $envValue->value();
});
$service['environment'] = $environments->toArray();
}
} else if ($variableName->startsWith('SERVICE_URL')) {
if ($fqdns) {
$number = Str::of($variableName)->after('SERVICE_URL')->afterLast('_')->value();
if (is_numeric($number)) {
$number = (int) $number - 1;
} else {
$number = 0;
}
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
$url = Url::fromString($fqdn)->getHost();
$environments = collect(data_get($service, 'environment'));
$environments = $environments->map(function ($envValue) use ($value, $url) {
$envValue = Str::of($envValue)->replace($value, $url);
return $envValue->value();
});
$service['environment'] = $environments->toArray();
} }
EnvironmentVariable::updateOrCreate([
'key' => $key,
'service_id' => $this->id,
], [
'value' => $defaultValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
} }
} }
} }
if ($this->server->proxyType() === ProxyTypes::TRAEFIK_V2->value) {
$labels = collect(data_get($service, 'labels', [])); // Add labels to the service
$labels = collect([]); $fqdns = collect(data_get($savedService, 'fqdns'));
$labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service')); $defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
if (!$isDatabase) { $serviceLabels = $serviceLabels->merge($defaultLabels);
if ($fqdns) { if (!$isDatabase && $fqdns->count() > 0) {
$labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true)); if ($fqdns) {
} $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true));
} }
data_set($service, 'labels', $labels->toArray());
} }
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database'); data_forget($service, 'is_database');
data_set($service, 'restart', RESTART_MODE); data_set($service, 'restart', RESTART_MODE);
data_set($service, 'container_name', $container_name); data_set($service, 'container_name', $containerName);
data_forget($service, 'documentation'); data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
// Remove unnecessary variables from service.environment
$withoutServiceEnvs = collect([]);
collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
if (!Str::of($key)->startsWith('$SERVICE_')) {
$withoutServiceEnvs->put($key, $value);
}
});
data_set($service, 'environment', $withoutServiceEnvs->toArray());
return $service; return $service;
}); });
$finalServices = [ $finalServices = [
'version' => $dockerComposeVersion, 'version' => $dockerComposeVersion,
'services' => $services->toArray(), 'services' => $services->toArray(),
'volumes' => $composeVolumes->toArray(), 'volumes' => $topLevelVolumes->toArray(),
'networks' => $composeNetworks->toArray(), 'networks' => $topLevelNetworks->toArray(),
]; ];
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2); $this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->save(); $this->save();
$shouldBeDefined = collect([ $this->saveComposeConfigs();
'envs' => $envs, return collect([]);
'volumes' => $volumes,
'ports' => $ports
]);
$parsedCompose = collect([
'dockerCompose' => $finalServices,
'shouldBeDefined' => $shouldBeDefined
]);
return $parsedCompose;
} else { } else {
return collect([]); return collect([]);
} }

View File

@@ -4,7 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Symfony\Component\Yaml\Yaml; use Illuminate\Support\Facades\Cache;
class ServiceApplication extends BaseModel class ServiceApplication extends BaseModel
{ {
@@ -15,10 +15,6 @@ class ServiceApplication extends BaseModel
{ {
return 'service'; return 'service';
} }
public function documentation()
{
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
}
public function service() public function service()
{ {
return $this->belongsTo(Service::class); return $this->belongsTo(Service::class);
@@ -31,4 +27,17 @@ class ServiceApplication extends BaseModel
{ {
return $this->morphMany(LocalFileVolume::class, 'resource'); return $this->morphMany(LocalFileVolume::class, 'resource');
} }
public function fqdns(): Attribute
{
return Attribute::make(
get: fn () => is_null($this->fqdn)
? []
: explode(',', $this->fqdn),
);
}
public function getFilesFromServer()
{
getFilesystemVolumesFromServer($this);
}
} }

View File

@@ -14,10 +14,6 @@ class ServiceDatabase extends BaseModel
{ {
return 'service'; return 'service';
} }
public function documentation()
{
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
}
public function service() public function service()
{ {
return $this->belongsTo(Service::class); return $this->belongsTo(Service::class);
@@ -26,4 +22,12 @@ class ServiceDatabase extends BaseModel
{ {
return $this->morphMany(LocalPersistentVolume::class, 'resource'); return $this->morphMany(LocalPersistentVolume::class, 'resource');
} }
public function fileStorages()
{
return $this->morphMany(LocalFileVolume::class, 'resource');
}
public function getFilesFromServer()
{
getFilesystemVolumesFromServer($this);
}
} }

View File

@@ -12,7 +12,7 @@ class GeneralNotification extends Notification implements ShouldQueue
{ {
use Queueable; use Queueable;
public $tries = 5; public $tries = 1;
public function __construct(public string $message) public function __construct(public string $message)
{ {
} }

View File

@@ -2,9 +2,6 @@
namespace App\Notifications; namespace App\Notifications;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;

View File

@@ -50,5 +50,8 @@ class RouteServiceProvider extends ServiceProvider
} }
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
}); });
RateLimiter::for('5', function (Request $request) {
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
});
} }
} }

View File

@@ -19,7 +19,7 @@ class Links extends Component
if ($application->fqdn) { if ($application->fqdn) {
$fqdns = collect(Str::of($application->fqdn)->explode(',')); $fqdns = collect(Str::of($application->fqdn)->explode(','));
$fqdns->map(function ($fqdn) { $fqdns->map(function ($fqdn) {
$this->links->push(getOnlyFqdn($fqdn)); $this->links->push(getFqdnWithoutPort($fqdn));
}); });
} }
if ($application->ports) { if ($application->ports) {

View File

@@ -21,5 +21,5 @@ const DATABASE_DOCKER_IMAGES = [
'couchdb', 'couchdb',
'neo4j', 'neo4j',
'influxdb', 'influxdb',
'clickhouse' 'clickhouse/clickhouse-server'
]; ];

View File

@@ -104,16 +104,16 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
return data_get($container[0], 'State.Status', 'exited'); return data_get($container[0], 'State.Status', 'exited');
} }
function generateApplicationContainerName(Application $application) function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{ {
$now = now()->format('Hisu'); $now = now()->format('Hisu');
if ($application->pull_request_id !== 0 && $application->pull_request_id !== null) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $application->uuid . '-pr-' . $application->pull_request_id; return $application->uuid . '-pr-' . $pull_request_id;
} else { } else {
return $application->uuid . '-' . $now; return $application->uuid . '-' . $now;
} }
} }
function get_port_from_dockerfile($dockerfile): int function get_port_from_dockerfile($dockerfile): int|null
{ {
$dockerfile_array = explode("\n", $dockerfile); $dockerfile_array = explode("\n", $dockerfile);
$found_exposed_port = null; $found_exposed_port = null;
@@ -127,10 +127,10 @@ function get_port_from_dockerfile($dockerfile): int
if ($found_exposed_port) { if ($found_exposed_port) {
return (int)$found_exposed_port->value(); return (int)$found_exposed_port->value();
} }
return 80; return null;
} }
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application') function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application', $subType = null, $subId = null)
{ {
$labels = collect([]); $labels = collect([]);
$labels->push('coolify.managed=true'); $labels->push('coolify.managed=true');
@@ -141,13 +141,17 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$labels->push('coolify.pullRequestId=' . $pull_request_id); $labels->push('coolify.pullRequestId=' . $pull_request_id);
} }
if ($type === 'service') {
$labels->push('coolify.service.subId=' . $subId);
$labels->push('coolify.service.subType=' . $subType);
}
return $labels; return $labels;
} }
function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled) function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled)
{ {
$labels = collect([]); $labels = collect([]);
$labels->push('traefik.enable=true'); $labels->push('traefik.enable=true');
foreach($domains as $domain) { foreach ($domains as $domain) {
$url = Url::fromString($domain); $url = Url::fromString($domain);
$host = $url->getHost(); $host = $url->getHost();
$path = $url->getPath(); $path = $url->getPath();
@@ -203,10 +207,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
{ {
$pull_request_id = data_get($preview, 'pull_request_id', 0); $pull_request_id = data_get($preview, 'pull_request_id', 0);
$container_name = generateApplicationContainerName($application); $container_name = generateApplicationContainerName($application, $pull_request_id);
$appId = $application->id; $appId = $application->id;
if ($pull_request_id !== 0) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
$appId = $appId . '-pr-' . $application->pull_request_id; $appId = $appId . '-pr-' . $pull_request_id;
} }
$labels = collect([]); $labels = collect([]);
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id)); $labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
@@ -216,9 +220,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
} else { } else {
$domains = Str::of(data_get($application, 'fqdn'))->explode(','); $domains = Str::of(data_get($application, 'fqdn'))->explode(',');
} }
if ($application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) { // Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled)); $labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
}
} }
return $labels->all(); return $labels->all();
} }

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Actions\Proxy\SaveConfiguration;
use App\Models\Server; use App\Models\Server;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -10,7 +11,8 @@ function get_proxy_path()
$proxy_path = "$base_path/proxy"; $proxy_path = "$base_path/proxy";
return $proxy_path; return $proxy_path;
} }
function connectProxyToNetworks(Server $server) { function connectProxyToNetworks(Server $server)
{
$networks = collect($server->standaloneDockers)->map(function ($docker) { $networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network']; return $docker['network'];
})->unique(); })->unique();
@@ -20,7 +22,7 @@ function connectProxyToNetworks(Server $server) {
$commands = $networks->map(function ($network) { $commands = $networks->map(function ($network) {
return [ return [
"echo '####### Connecting coolify-proxy to $network network...'", "echo '####### Connecting coolify-proxy to $network network...'",
"docker network ls --format '{{.Name}}' | grep '^$network$' || docker network create --attachable $network >/dev/null", "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true", "docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
]; ];
}); });
@@ -101,7 +103,9 @@ function generate_default_proxy_configuration(Server $server)
if (isDev()) { if (isDev()) {
$config['services']['traefik']['command'][] = "--log.level=debug"; $config['services']['traefik']['command'][] = "--log.level=debug";
} }
return Yaml::dump($config, 4, 2); $config = Yaml::dump($config, 4, 2);
SaveConfiguration::run($server, $config);
return $config;
} }
function setup_default_redirect_404(string|null $redirect_url, Server $server) function setup_default_redirect_404(string|null $redirect_url, Server $server)

View File

@@ -189,7 +189,7 @@ function validateServer(Server $server, bool $throwError = false)
]; ];
} }
$server->settings->is_reachable = true; $server->settings->is_reachable = true;
instant_remote_process(["docker ps"], $server, $throwError);
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError); $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
if (!$dockerVersion) { if (!$dockerVersion) {
$dockerVersion = null; $dockerVersion = null;

View File

@@ -1,7 +1,12 @@
<?php <?php
use App\Models\EnvironmentVariable;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
function replaceRegex(?string $name = null) function replaceRegex(?string $name = null)
{ {
@@ -20,27 +25,115 @@ function serviceStatus(Service $service)
{ {
$foundRunning = false; $foundRunning = false;
$isDegraded = false; $isDegraded = false;
$foundRestaring = false;
$applications = $service->applications; $applications = $service->applications;
$databases = $service->databases; $databases = $service->databases;
foreach ($applications as $application) { foreach ($applications as $application) {
if ($application->exclude_from_status) {
continue;
}
if (Str::of($application->status)->startsWith('running')) { if (Str::of($application->status)->startsWith('running')) {
$foundRunning = true; $foundRunning = true;
} else if (Str::of($application->status)->startsWith('restarting')) {
$foundRestaring = true;
} else { } else {
$isDegraded = true; $isDegraded = true;
} }
} }
foreach ($databases as $database) { foreach ($databases as $database) {
if ($database->exclude_from_status) {
continue;
}
if (Str::of($database->status)->startsWith('running')) { if (Str::of($database->status)->startsWith('running')) {
$foundRunning = true; $foundRunning = true;
} else if (Str::of($database->status)->startsWith('restarting')) {
$foundRestaring = true;
} else { } else {
$isDegraded = true; $isDegraded = true;
} }
} }
if ($foundRestaring) {
return 'degraded';
}
if ($foundRunning && !$isDegraded) { if ($foundRunning && !$isDegraded) {
return 'running'; return 'running';
} else if ($foundRunning && $isDegraded) { } else if ($foundRunning && $isDegraded) {
return 'degraded'; return 'degraded';
} else if (!$foundRunning && $isDegraded) { } else if (!$foundRunning && !$isDegraded) {
return 'exited'; return 'exited';
} }
return 'exited';
}
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService)
{
// TODO: make this async
try {
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
$fileVolumes = $oneService->fileStorages()->get();
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",
"cd "
]);
instant_remote_process($commands, $server);
foreach ($fileVolumes as $fileVolume) {
$path = Str::of(data_get($fileVolume, 'fs_path'));
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) {
$path = $path->after('.');
$fileLocation = $workdir . $path;
} else {
$fileLocation = $path;
}
$isFile = instant_remote_process(["test -f $fileLocation && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && !$fileVolume->is_directory) {
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
if (base64_encode($filesystemContent) != base64_encode($content)) {
$fileVolume->content = $filesystemContent;
$fileVolume->save();
}
} else {
if ($isDir == 'OK') {
$fileVolume->content = null;
$fileVolume->is_directory = true;
$fileVolume->save();
} else {
$fileVolume->content = null;
$fileVolume->is_directory = false;
$fileVolume->save();
}
}
}
} catch (\Throwable $e) {
return handleError($e);
}
}
function updateCompose($resource)
{
try {
$name = data_get($resource, 'name');
$dockerComposeRaw = data_get($resource, 'service.docker_compose_raw');
$dockerCompose = Yaml::parse($dockerComposeRaw);
// Switch Image
$image = data_get($resource, 'image');
data_set($dockerCompose, "services.{$name}.image", $image);
// Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
ray($variableName);
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $resource->fqdn;
$generatedEnv->save();
}
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
$resource->service->docker_compose_raw = $dockerComposeRaw;
$resource->service->save();
} catch (\Throwable $e) {
return handleError($e);
}
} }

View File

@@ -1,6 +1,7 @@
<?php <?php
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Team; use App\Models\Team;
use App\Models\User; use App\Models\User;
use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\DiscordChannel;
@@ -11,11 +12,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
use Nubs\RandomNameGenerator\All; use Nubs\RandomNameGenerator\All;
use Poliander\Cron\CronExpression; use Poliander\Cron\CronExpression;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@@ -240,11 +243,13 @@ function base_ip(): string
} }
return "localhost"; return "localhost";
} }
function getOnlyFqdn(String $fqdn) { function getFqdnWithoutPort(String $fqdn)
{
$url = Url::fromString($fqdn); $url = Url::fromString($fqdn);
$host = $url->getHost(); $host = $url->getHost();
$scheme = $url->getScheme(); $scheme = $url->getScheme();
return "$scheme://$host"; $path = $url->getPath();
return "$scheme://$host$path";
} }
/** /**
* If fqdn is set, return it, otherwise return public ip. * If fqdn is set, return it, otherwise return public ip.
@@ -383,3 +388,49 @@ function parseEnvFormatToArray($env_file_contents)
} }
return $env_array; return $env_array;
} }
function data_get_str($data, $key, $default = null): Stringable
{
$str = data_get($data, $key, $default) ?? $default;
return Str::of($str);
}
function generateFqdn(Server $server, string $random)
{
$wildcard = data_get($server, 'settings.wildcard_domain');
if (is_null($wildcard) || $wildcard === '') {
$wildcard = sslip($server);
}
$url = Url::fromString($wildcard);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$finalFqdn = "$scheme://{$random}.$host$path" ;
return $finalFqdn;
}
function sslip(Server $server)
{
if (isDev()) {
return "http://127.0.0.1.sslip.io";
}
if ($server->ip === 'host.docker.internal') {
$baseIp = base_ip();
return "http://$baseIp.sslip.io";
}
return "http://{$server->ip}.sslip.io";
}
function getServiceTemplates()
{
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
} else {
$services = Http::get(config('constants.services.official'));
if ($services->failed()) {
throw new \Exception($services->body());
}
$services = collect($services->json())->sortKeys();
}
return $services;
}

725
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,10 @@
<?php <?php
return [ return [
'ssh' =>[ 'docs' => [
'base_url' => 'https://coolify.io/docs',
'contact' => 'https://coolify.io/docs/contact',
],
'ssh' => [
'connection_timeout' => 10, 'connection_timeout' => 10,
'server_interval' => 20, 'server_interval' => 20,
'command_timeout' => 7200, 'command_timeout' => 7200,
@@ -14,8 +18,11 @@ return [
'expiration' => 10, 'expiration' => 10,
], ],
], ],
'services' => [
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
],
'limits' => [ 'limits' => [
'trial_period'=> 7, 'trial_period' => 7,
'server' => [ 'server' => [
'zero' => 0, 'zero' => 0,
'self-hosted' => 999999999999, 'self-hosted' => 999999999999,

View File

@@ -3,11 +3,11 @@
return [ return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/ // @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => 'https://62de992090e4e0cb28f18231835ea006@o1082494.ingest.sentry.io/4505347448045568', 'dsn' => 'https://72f02655749d5d687297b6b9f078b8b9@o1082494.ingest.sentry.io/4505347448045568',
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.44', 'release' => '4.0.0-beta.55',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@@ -30,7 +30,7 @@ return [
* *
* Minimum: 3000 (in milliseconds) * Minimum: 3000 (in milliseconds)
*/ */
'duration' => 3000, 'duration' => 5000,
/** /**
* The horizontal position of each toast. * The horizontal position of each toast.

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.44'; return '4.0.0-beta.55';

View File

@@ -0,0 +1,30 @@
<?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->dropColumn('destination_type');
$table->dropColumn('destination_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('services', function (Blueprint $table) {
$table->morphs('destination');
});
}
};

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('service_applications', function (Blueprint $table) {
$table->boolean('exclude_from_status')->default(false);
$table->boolean('required_fqdn')->default(false);
$table->string('image')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('service_applications', function (Blueprint $table) {
$table->dropColumn('exclude_from_status');
$table->dropColumn('required_fqdn');
$table->dropColumn('image');
});
}
};

View File

@@ -0,0 +1,30 @@
<?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('service_databases', function (Blueprint $table) {
$table->boolean('exclude_from_status')->default(false);
$table->string('image')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('service_databases', function (Blueprint $table) {
$table->dropColumn('exclude_from_status');
$table->dropColumn('image');
});
}
};

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('users', function (Blueprint $table) {
$table->boolean('marketing_emails')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('marketing_emails');
});
}
};

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('local_file_volumes', function (Blueprint $table) {
$table->boolean('is_directory')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->dropColumn('is_directory');
});
}
};

View File

@@ -69,11 +69,12 @@ class ProductionSeeder extends Seeder
PrivateKey::updateOrCreate( PrivateKey::updateOrCreate(
[ [
'id' => 0, 'id' => 0,
'name' => 'localhost\'s key',
'description' => 'The private key for the Coolify host machine (localhost).',
'team_id' => 0, 'team_id' => 0,
], ],
['private_key' => $coolify_key] [
'name' => 'localhost\'s key',
'description' => 'The private key for the Coolify host machine (localhost).', 'private_key' => $coolify_key
]
); );
} else { } else {
echo "No SSH key found for the Coolify host machine (localhost).\n"; echo "No SSH key found for the Coolify host machine (localhost).\n";

View File

@@ -0,0 +1,27 @@
services:
fider:
image: getfider/fider:stable
environment:
BASE_URL: $SERVICE_FQDN_FIDER
DATABASE_URL: postgres://$SERVICE_USER_MYSQL:$SERVICE_PASSWORD_MYSQL@database:5432/fider?sslmode=disable
JWT_SECRET: $SERVICE_PASSWORD64_FIDER
EMAIL_NOREPLY: ${EMAIL_NOREPLY:-noreply@example.com}
EMAIL_MAILGUN_API: $EMAIL_MAILGUN_API
EMAIL_MAILGUN_DOMAIN: $EMAIL_MAILGUN_DOMAIN
EMAIL_MAILGUN_REGION: $EMAIL_MAILGUN_REGION
EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.mailgun.com}
EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-587}
EMAIL_SMTP_USERNAME: ${EMAIL_SMTP_USERNAME:-postmaster@mailgun.com}
EMAIL_SMTP_PASSWORD: $EMAIL_SMTP_PASSWORD
EMAIL_SMTP_ENABLE_STARTTLS: $EMAIL_SMTP_ENABLE_STARTTLS
EMAIL_AWSSES_REGION: $EMAIL_AWSSES_REGION
EMAIL_AWSSES_ACCESS_KEY_ID: $EMAIL_AWSSES_ACCESS_KEY_ID
EMAIL_AWSSES_SECRET_ACCESS_KEY: $EMAIL_AWSSES_SECRET_ACCESS_KEY
database:
image: postgres:12
volumes:
- pg_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: $SERVICE_USER_MYSQL
POSTGRES_PASSWORD: $SERVICE_PASSWORD_MYSQL
POSTGRES_DB: ${POSTGRES_DB:-fider}

View File

@@ -1,14 +1,8 @@
services: services:
ghost: ghost:
documentation: https://ghost.org/docs/config
image: ghost:5 image: ghost:5
volumes: volumes:
- ghost-content-data:/var/lib/ghost/content - ghost-content-data:/var/lib/ghost/content
- type: volume
source: /data/g
target: /data
volume:
nocopy: true
environment: environment:
- url=$SERVICE_FQDN_GHOST - url=$SERVICE_FQDN_GHOST
- database__client=mysql - database__client=mysql
@@ -16,24 +10,9 @@ services:
- database__connection__user=$SERVICE_USER_MYSQL - database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL - database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE-ghost} - database__connection__database=${MYSQL_DATABASE-ghost}
networks:
default:
aliases:
- alias1
- alias3
ipv4_address: 172.16.238.10
ipv6_address: 2001:3984:3989::10
ports:
- "2368"
- 1234:2368
- target: 2368
published: 1234
protocol: tcp
mode: host
depends_on: depends_on:
- mysql - mysql
mysql: mysql:
documentation: https://hub.docker.com/_/mysql
image: mysql:8.0 image: mysql:8.0
volumes: volumes:
- ghost-mysql-data:/var/lib/mysql - ghost-mysql-data:/var/lib/mysql
@@ -42,10 +21,3 @@ services:
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL} - MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT} - MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
networks:
default:
ipam:
driver: default
config:
- subnet: "172.16.238.0/24"
- subnet: "2001:3984:3989::/64"

View File

@@ -0,0 +1,52 @@
version: "3.3"
services:
plausible:
image: plausible/analytics:v2.0
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
depends_on:
- plausible_db
- plausible_events_db
- mail
mail:
image: bytemark/smtp
plausible_db:
image: postgres:14-alpine
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=plausible
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine
volumes:
- type: volume
source: event-data
target: /var/lib/clickhouse
- type: bind
source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml
read_only: true
content: >-
<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>
- type: bind
source: ./clickhouse/clickhouse-user-config.xml
target: /etc/clickhouse-server/users.d/logging.xml
read_only: true
content: >-
<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log
remove="remove"/><query_log remove="remove"/><text_log
remove="remove"/><trace_log remove="remove"/><metric_log
remove="remove"/><asynchronous_metric_log
remove="remove"/><session_log remove="remove"/><part_log
remove="remove"/></clickhouse>
ulimits:
nofile:
soft: 262144
hard: 262144

View File

@@ -0,0 +1,15 @@
services:
postgres:
image: postgres
command: 'postgres -c config_file=/etc/postgresql/postgresql.conf'
volumes:
- type: bind
source: ./postgresql.conf
target: /etc/postgresql/postgresql.conf
- type: bind
source: ./docker-entrypoint-initdb.d
target: /docker-entrypoint-initdb.d/
isDirectory: true
environment:
POSTGRES_USER: $SERVICE_USER_POSTGRES
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES

View File

@@ -0,0 +1,5 @@
services:
uptime-kuma:
image: louislam/uptime-kuma:1
volumes:
- uptime-kuma:/app/data

View File

@@ -0,0 +1,76 @@
services:
ghost:
image: ghost:5
volumes:
- ~/configs:/etc/configs/:ro
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
- /var/lib/ghost/content:/tmp/ghost/content:rw
- ghost-content-data:/var/lib/ghost/content
- type: volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind
source: ./var/lib/ghost/data
target: /data
- type: bind
source: /tmp
target: /tmp
labels:
- "test.label=true"
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "127.0.0.1::5000"
- "6060:6060/udp"
- "12400-12500:1240"
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- some-network
- other-network
environment:
- database__client=${DATABASE_CLIENT:-mysql}
- database__connection__database=${MYSQL_DATABASE:-ghost}
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
- test=${TEST:?true}
- url=$SERVICE_FQDN_GHOST
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
depends_on:
- mysql
mysql:
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=$MYSQL_DATABASE
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
- SESSION_SECRET
minio:
image: minio/minio
environment:
RACK_ENV: development
A: $A
SHOW: ${SHOW}
SHOW1: ${SHOW2-show1}
SHOW2: ${SHOW3:-show2}
SHOW3: ${SHOW4?show3}
SHOW4: ${SHOW5:?show4}
SHOW5: ${SERVICE_USER_MINIO}
SHOW6: ${SERVICE_PASSWORD_MINIO}
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
SHOW8: ${SERVICE_BASE64_64_MINIO}
SHOW9: ${SERVICE_BASE64_128_MINIO}
SHOW10: ${SERVICE_BASE64_MINIO}
SHOW11:

352
package-lock.json generated
View File

@@ -5,19 +5,19 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@tailwindcss/typography": "0.5.9", "@tailwindcss/typography": "0.5.10",
"alpinejs": "3.12.2", "alpinejs": "3.13.0",
"daisyui": "3.2.1", "daisyui": "3.7.7",
"tailwindcss-scrollbar": "0.1.0" "tailwindcss-scrollbar": "0.1.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "4.2.3", "@vitejs/plugin-vue": "4.3.4",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.16",
"axios": "1.4.0", "axios": "1.5.0",
"laravel-vite-plugin": "0.7.8", "laravel-vite-plugin": "0.8.0",
"postcss": "8.4.24", "postcss": "8.4.30",
"tailwindcss": "3.3.2", "tailwindcss": "3.3.3",
"vite": "4.3.9", "vite": "4.4.9",
"vue": "3.3.4" "vue": "3.3.4"
} }
}, },
@@ -45,9 +45,9 @@
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-E/sgkvwoIfj4aMAPL2e35VnUJspzVYl7+M1B2cqeubdBhADV4uPon0KCc8p2G+LqSJ6i8ocYPCqY3A4GGq0zkQ==", "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -61,9 +61,9 @@
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-WQ9p5oiXXYJ33F2EkE3r0FRDFVpEdcDiwNX3u7Xaibxfx6vQE0Sb8ytrfQsA5WO6kDn6mDfKLh6KrPBjvkk7xA==", "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -77,9 +77,9 @@
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-m4OsaCr5gT+se25rFPHKQXARMyAehHTQAz4XX1Vk3d27VtqiX0ALMBPoXZsGaB6JYryCLfgGwUslMqTfqeLU0w==", "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -93,9 +93,9 @@
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-O3GCZghRIx+RAN0NDPhyyhRgwa19MoKlzGonIb5hgTj78krqp9XZbYCvFr9N1eUxg0ZQEpiiZ4QvsOQwBpP+lg==", "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -109,9 +109,9 @@
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-5D48jM3tW27h1qjaD9UNRuN+4v0zvksqZSPZqeSWggfMlsVdAhH3pwSfQIFJwcs9QJ9BRibPS4ViZgs3d2wsCA==", "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -125,9 +125,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-OWvHzmLNTdF1erSvrfoEBGlN94IE6vCEaGEkEH29uo/VoONqPnoDFfShi41Ew+yKimx4vrmmAJEGNoyyP+OgOQ==", "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -141,9 +141,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-A0Xg5CZv8MU9xh4a+7NUpi5VHBKh1RaGJKqjxe4KG87X+mTjDE6ZvlJqpWoeJxgfXHT7IMP9tDFu7IZ03OtJAw==", "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -157,9 +157,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-WsHyJ7b7vzHdJ1fv67Yf++2dz3D726oO3QCu8iNYik4fb5YuuReOI9OtA+n7Mk0xyQivNTPbl181s+5oZ38gyA==", "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -173,9 +173,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-cK3AjkEc+8v8YG02hYLQIQlOznW+v9N+OI9BAFuyqkfQFR+DnDLhEM5N8QRxAUz99cJTo1rLNXqRrvY15gbQUg==", "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -189,9 +189,9 @@
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-jdOBXJqcgHlah/nYHnj3Hrnl9l63RjtQ4vn9+bohjQPI2QafASB5MtHAoEv0JQHVb/xYQTFOeuHnNYE1zF7tYw==", "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -205,9 +205,9 @@
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-GTOEtj8h9qPKXCyiBBnHconSCV9LwFyx/gv3Phw0pa25qPYjVuuGZ4Dk14bGCfGX3qKF0+ceeQvwmtI+aYBbVA==", "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -221,9 +221,9 @@
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-o8CIhfBwKcxmEENOH9RwmUejs5jFiNoDw7YgS0EJTF6kgPgcqLFjgoc5kDey5cMHRVCIWc6kK2ShUePOcc7RbA==", "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
@@ -237,9 +237,9 @@
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-biMLH6NR/GR4z+ap0oJYb877LdBpGac8KfZoEnDiBKd7MD/xt8eaw1SFfYRUeMVx519kVkAOL2GExdFmYnZx3A==", "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -253,9 +253,9 @@
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-jkphYUiO38wZGeWlfIBMB72auOllNA2sLfiZPGDtOBb1ELN8lmqBrlMiucgL8awBw1zBXN69PmZM6g4yTX84TA==", "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -269,9 +269,9 @@
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-j3ucLdeY9HBcvODhCY4b+Ds3hWGO8t+SAidtmWu/ukfLLG/oYDMaA+dnugTVAg5fnUOGNbIYL9TOjhWgQB8W5g==", "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -285,9 +285,9 @@
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-uo5JL3cgaEGotaqSaJdRfFNSCUJOIliKLnDGWaVCgIKkHxwhYMm95pfMbWZ9l7GeW9kDg0tSxcy9NYdEtjwwmA==", "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -301,9 +301,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-DNdoRg8JX+gGsbqt2gPgkgb00mqOgOO27KnrWZtdABl6yWTST30aibGJ6geBq3WM2TIeW6COs5AScnC7GwtGPg==", "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -317,9 +317,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-aVsENlr7B64w8I1lhHShND5o8cW6sB9n9MUtLumFlPhG3elhNWtE7M1TFpj3m7lT3sKQUMkGFjTQBrvDDO1YWA==", "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -333,9 +333,9 @@
} }
}, },
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-qbHGVQdKSwi0JQJuZznS4SyY27tYXYF0mrgthbxXrZI3AHKuRvU+Eqbg/F0rmLDpW/jkIZBlCO1XfHUBMNJ1pg==", "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -349,9 +349,9 @@
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-zsCp8Ql+96xXTVTmm6ffvoTSZSV2B/LzzkUXAY33F/76EajNw1m+jZ9zPfNJlJ3Rh4EzOszNDHsmG/fZOhtqDg==", "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -365,9 +365,9 @@
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-FfrFjR4id7wcFYOdqbDfDET3tjxCozUgbqdkOABsSFzoZGFC92UK7mg4JKRc/B3NNEf1s2WHxJ7VfTdVDPN3ng==", "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -381,9 +381,9 @@
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==", "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -477,9 +477,9 @@
} }
}, },
"node_modules/@tailwindcss/typography": { "node_modules/@tailwindcss/typography": {
"version": "0.5.9", "version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==", "integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
"dependencies": { "dependencies": {
"lodash.castarray": "^4.4.0", "lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6", "lodash.isplainobject": "^4.0.6",
@@ -503,9 +503,9 @@
} }
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "4.2.3", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
"integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", "integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0" "node": "^14.18.0 || >=16.0.0"
@@ -683,9 +683,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
}, },
"node_modules/alpinejs": { "node_modules/alpinejs": {
"version": "3.12.2", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.2.tgz", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.0.tgz",
"integrity": "sha512-wrUZULm9w6DYwWcUtB/anFLgRaF4riSuPgIJ3gcPUS8st9luAJnAxoIgro/Al97d2McSSz/JypWg/NlmKFIJJA==", "integrity": "sha512-7FYR1Yz3evIjlJD1mZ3SYWSw+jlOmQGeQ1QiSufSQ6J84XMQFkzxm6OobiZ928SfqhGdoIp2SsABNsS4rXMMJw==",
"dependencies": { "dependencies": {
"@vue/reactivity": "~3.1.1" "@vue/reactivity": "~3.1.1"
} }
@@ -719,9 +719,9 @@
"dev": true "dev": true
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.14", "version": "10.4.16",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
"integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -731,12 +731,16 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer" "url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
], ],
"dependencies": { "dependencies": {
"browserslist": "^4.21.5", "browserslist": "^4.21.10",
"caniuse-lite": "^1.0.30001464", "caniuse-lite": "^1.0.30001538",
"fraction.js": "^4.2.0", "fraction.js": "^4.3.6",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
@@ -752,9 +756,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.4.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
@@ -796,9 +800,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.21.5", "version": "4.21.11",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz",
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -808,13 +812,17 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist" "url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001449", "caniuse-lite": "^1.0.30001538",
"electron-to-chromium": "^1.4.284", "electron-to-chromium": "^1.4.526",
"node-releases": "^2.0.8", "node-releases": "^2.0.13",
"update-browserslist-db": "^1.0.10" "update-browserslist-db": "^1.0.13"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@@ -832,9 +840,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001467", "version": "1.0.30001539",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001467.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
"integrity": "sha512-cEdN/5e+RPikvl9AHm4uuLXxeCNq8rFsQ+lPHTfe/OtypP3WwnVVbjn+6uBV7PaFL6xUFzTh+sSCOz1rKhcO+Q==", "integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -844,6 +852,10 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite" "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
] ]
}, },
@@ -941,9 +953,9 @@
"dev": true "dev": true
}, },
"node_modules/daisyui": { "node_modules/daisyui": {
"version": "3.2.1", "version": "3.7.7",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.2.1.tgz", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.7.7.tgz",
"integrity": "sha512-gIqE6wiqoJt9G8+n3R/SwLeUnpNCE2eDhT73rP0yZYVaM7o6zVcakBH3aEW5RGpx3UkonPiLuvcgxRcb2lE8TA==", "integrity": "sha512-2/nFdW/6R9MMnR8tTm07jPVyPaZwpUSkVsFAADb7Oq8N2Ynbls57laDdNqxTCUmn0QvcZi01TKl8zQbAwRfw1w==",
"dependencies": { "dependencies": {
"colord": "^2.9", "colord": "^2.9",
"css-selector-tokenizer": "^0.8", "css-selector-tokenizer": "^0.8",
@@ -979,15 +991,15 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.332", "version": "1.4.528",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.332.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz",
"integrity": "sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==", "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==",
"dev": true "dev": true
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.17.12", "version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.12.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-bX/zHl7Gn2CpQwcMtRogTTBf9l1nl+H6R8nUbjk+RuKqAE3+8FDulLA+pHvX7aA7Xe07Iwa+CWvy9I8Y2qqPKQ==", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
@@ -997,28 +1009,28 @@
"node": ">=12" "node": ">=12"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/android-arm": "0.17.12", "@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.17.12", "@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.17.12", "@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.17.12", "@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.17.12", "@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.17.12", "@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.17.12", "@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.17.12", "@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.17.12", "@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.17.12", "@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.17.12", "@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.17.12", "@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.17.12", "@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.17.12", "@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.17.12", "@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.17.12", "@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.17.12", "@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.17.12", "@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.17.12", "@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.17.12", "@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.17.12", "@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.17.12" "@esbuild/win32-x64": "0.18.20"
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
@@ -1121,16 +1133,16 @@
} }
}, },
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "4.2.0", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "*" "node": "*"
}, },
"funding": { "funding": {
"type": "patreon", "type": "patreon",
"url": "https://www.patreon.com/infusion" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
@@ -1277,9 +1289,9 @@
} }
}, },
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "0.7.8", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.0.tgz",
"integrity": "sha512-HWYqpQYHR3kEQ1LsHX7gHJoNNf0bz5z5mDaHBLzS+PGLCTmYqlU5/SZyeEgObV7z7bC/cnStYcY9H1DI1D5Udg==", "integrity": "sha512-6VjLI+azBpeK6rWBiKcb/En5GnTdYpL0U4zS8gXYvb2/VSq4mlau5H3NWpSktUDBMM1b97LLgICx5zevi8IY0w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
@@ -1412,9 +1424,9 @@
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.10", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
"dev": true "dev": true
}, },
"node_modules/normalize-path": { "node_modules/normalize-path": {
@@ -1504,9 +1516,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.24", "version": "8.4.30",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -1697,9 +1709,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.21.0", "version": "3.29.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.3.tgz",
"integrity": "sha512-ANPhVcyeHvYdQMUyCbczy33nbLzI7RzrBje4uvNiTDJGIMtlKoOStmympwr9OtS1LZxiDmE2wvxHyVhoLtf1KQ==", "integrity": "sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@@ -1794,9 +1806,9 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.3.2", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -1818,7 +1830,6 @@
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1", "postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11", "postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2", "resolve": "^1.22.2",
"sucrase": "^3.32.0" "sucrase": "^3.32.0"
}, },
@@ -1874,9 +1885,9 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.10", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -1886,6 +1897,10 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist" "url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
], ],
"dependencies": { "dependencies": {
@@ -1893,7 +1908,7 @@
"picocolors": "^1.0.0" "picocolors": "^1.0.0"
}, },
"bin": { "bin": {
"browserslist-lint": "cli.js" "update-browserslist-db": "cli.js"
}, },
"peerDependencies": { "peerDependencies": {
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
@@ -1905,14 +1920,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.3.9", "version": "4.4.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.17.5", "esbuild": "^0.18.10",
"postcss": "^8.4.23", "postcss": "^8.4.27",
"rollup": "^3.21.0" "rollup": "^3.27.1"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@@ -1920,12 +1935,16 @@
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0" "node": "^14.18.0 || >=16.0.0"
}, },
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": { "optionalDependencies": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
}, },
"peerDependencies": { "peerDependencies": {
"@types/node": ">= 14", "@types/node": ">= 14",
"less": "*", "less": "*",
"lightningcss": "^1.21.0",
"sass": "*", "sass": "*",
"stylus": "*", "stylus": "*",
"sugarss": "*", "sugarss": "*",
@@ -1938,6 +1957,9 @@
"less": { "less": {
"optional": true "optional": true
}, },
"lightningcss": {
"optional": true
},
"sass": { "sass": {
"optional": true "optional": true
}, },

View File

@@ -6,19 +6,19 @@
"build": "vite build" "build": "vite build"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "4.2.3", "@vitejs/plugin-vue": "4.3.4",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.16",
"axios": "1.4.0", "axios": "1.5.0",
"laravel-vite-plugin": "0.7.8", "laravel-vite-plugin": "0.8.0",
"postcss": "8.4.24", "postcss": "8.4.30",
"tailwindcss": "3.3.2", "tailwindcss": "3.3.3",
"vite": "4.3.9", "vite": "4.4.9",
"vue": "3.3.4" "vue": "3.3.4"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/typography": "0.5.9", "@tailwindcss/typography": "0.5.10",
"alpinejs": "3.12.2", "alpinejs": "3.13.0",
"daisyui": "3.2.1", "daisyui": "3.7.7",
"tailwindcss-scrollbar": "0.1.0" "tailwindcss-scrollbar": "0.1.0"
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{ {
"/app.js": "/app.js?id=7e1968acfd75b8dc843675097962e3ce", "/app.js": "/app.js?id=ff1533ec4a7afad65c5bd7bcc2cc7d7b",
"/app-dark.css": "/app-dark.css?id=15c72df05e2b1147fa3e4b0670cfb435", "/app-dark.css": "/app-dark.css?id=15c72df05e2b1147fa3e4b0670cfb435",
"/app.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5", "/app.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f", "/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",

View File

@@ -1,7 +1,7 @@
<template> <template>
<Transition name="fade"> <Transition name="fade">
<div> <div >
<div class="flex items-center p-1 px-2 mt-1 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200" <div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
@click="showCommandPalette = true"> @click="showCommandPalette = true">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -15,14 +15,13 @@
@if (is_transactional_emails_active()) @if (is_transactional_emails_active())
<form action="/forgot-password" method="POST" class="flex flex-col gap-2"> <form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf @csrf
<x-forms.input required type="email" name="email" <x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
label="{{ __('input.email') }}" autofocus />
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button> <x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form> </form>
@else @else
<div>Transactional emails are not active on this instance.</div> <div>Transactional emails are not active on this instance.</div>
<div>See how to set it in our <a class="text-white" target="_blank" <div>See how to set it in our <a class="text-white" target="_blank"
href="https://docs.coollabs.io/coolify">docs</a>, or how to href="{{ config('constants.docs.base_url') }}">docs</a>, or how to
manually reset password. manually reset password.
</div> </div>
@endif @endif

View File

@@ -3,7 +3,7 @@
<x-chevron-down /> <x-chevron-down />
</label> </label>
<div class="absolute hidden group-hover:block"> <div class="absolute z-50 hidden group-hover:block">
<ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200"> <ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
@if (data_get($application, 'gitBrancLocation')) @if (data_get($application, 'gitBrancLocation'))
<li> <li>
@@ -19,7 +19,7 @@
@foreach (Str::of(data_get($application, 'fqdn'))->explode(',') as $fqdn) @foreach (Str::of(data_get($application, 'fqdn'))->explode(',') as $fqdn)
<li> <li>
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white" <a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank" href="{{ getOnlyFqdn($fqdn) }}"> target="_blank" href="{{ getFqdnWithoutPort($fqdn) }}">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" <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-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">
@@ -28,7 +28,7 @@
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
<path <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" /> 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>{{ getOnlyFqdn($fqdn) }} </svg>{{ getFqdnWithoutPort($fqdn) }}
</a> </a>
</li> </li>
@endforeach @endforeach
@@ -38,7 +38,7 @@
@if (data_get($preview, 'fqdn')) @if (data_get($preview, 'fqdn'))
<li> <li>
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white" <a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
target="_blank" href="{{ getOnlyFqdn(data_get($preview, 'fqdn')) }}"> 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" <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-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">

View File

@@ -0,0 +1,12 @@
@isset($title, $action)
<div tabindex="0" x-data="{ open: false }"
class="transition border rounded cursor-pointer collapse collapse-arrow border-coolgray-200"
:class="open ? 'collapse-open' : 'collapse-close'">
<div class="flex flex-col justify-center text-sm select-text collapse-title" x-on:click="open = !open">
{{ $title }}
</div>
<div class="collapse-content">
{{ $action }}
</div>
</div>
@endisset

View File

@@ -1,5 +1,6 @@
@auth @auth
<nav class="fixed h-full overflow-hidden overflow-y-auto scrollbar"> <nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
<ul class="flex flex-col h-full gap-4 menu flex-nowrap"> <ul class="flex flex-col h-full gap-4 menu flex-nowrap">
<li title="Dashboard"> <li title="Dashboard">
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif> <a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
@@ -10,16 +11,13 @@
</svg> </svg>
</a> </a>
</li> </li>
<li title="Help" class="mt-auto"> <li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
<div class="justify-center icons" wire:click="help" onclick="help.showModal()"> <div class="justify-center" wire:click="help" onclick="help.showModal()">
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" <svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor"
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M22 5.5H9c-1.1 0-2 .9-2 2v9a2 2 0 0 0 2 2h13c1.11 0 2-.89 2-2v-9a2 2 0 0 0-2-2m0 11H9V9.17l6.5 3.33L22 9.17v7.33m-6.5-5.69L9 7.5h13l-6.5 3.31M5 16.5c0 .17.03.33.05.5H1c-.552 0-1-.45-1-1s.448-1 1-1h4v1.5M3 7h2.05c-.02.17-.05.33-.05.5V9H3c-.55 0-1-.45-1-1s.45-1 1-1m-2 5c0-.55.45-1 1-1h3v2H2c-.55 0-1-.45-1-1Z" />
stroke-width="2">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
</g>
</svg> </svg>
Feedback
</div> </div>
</li> </li>
<li class="pb-6" title="Logout"> <li class="pb-6" title="Logout">

View File

@@ -1,5 +1,6 @@
@auth @auth
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar"> <nav class="fixed h-full overflow-hidden overflow-y-auto pt-28 scrollbar">
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
<ul class="flex flex-col h-full gap-4 menu flex-nowrap"> <ul class="flex flex-col h-full gap-4 menu flex-nowrap">
<li title="Dashboard"> <li title="Dashboard">
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif> <a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
@@ -114,16 +115,13 @@
</li> </li>
@endif @endif
@if (isSubscriptionActive() || isDev()) @if (isSubscriptionActive() || isDev())
<li title="Help" class="mt-auto"> <li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
<div class="justify-center icons" wire:click="help" onclick="help.showModal()"> <div class="justify-center" wire:click="help" onclick="help.showModal()">
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24" <svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor"
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M22 5.5H9c-1.1 0-2 .9-2 2v9a2 2 0 0 0 2 2h13c1.11 0 2-.89 2-2v-9a2 2 0 0 0-2-2m0 11H9V9.17l6.5 3.33L22 9.17v7.33m-6.5-5.69L9 7.5h13l-6.5 3.31M5 16.5c0 .17.03.33.05.5H1c-.552 0-1-.45-1-1s.448-1 1-1h4v1.5M3 7h2.05c-.02.17-.05.33-.05.5V9H3c-.55 0-1-.45-1-1s.45-1 1-1m-2 5c0-.55.45-1 1-1h3v2H2c-.55 0-1-.45-1-1Z" />
stroke-width="2">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
</g>
</svg> </svg>
Feedback
</div> </div>
</li> </li>
@endif @endif

View File

@@ -1,28 +1,31 @@
<div class="group"> @if ($links->count() > 0)
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application <div class="group">
<x-chevron-down /> <label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
</label> <x-chevron-down />
</label>
<div class="absolute hidden group-hover:block"> <div class="absolute z-50 hidden group-hover:block">
<ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200"> <ul tabindex="0"
@if ($links->count() > 0) class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
@foreach ($links as $link) @if ($links->count() > 0)
<li> @foreach ($links as $link)
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white" <li>
target="_blank" href="{{ $link }}"> <a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" target="_blank" href="{{ $link }}">
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
stroke-linejoin="round"> stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> stroke-linejoin="round">
<path d="M9 15l6 -6" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M9 15l6 -6" />
<path <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
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" /> <path
</svg>{{ $link }} 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" />
</a> </svg>{{ $link }}
</li> </a>
@endforeach </li>
@endif @endforeach
</ul> @endif
</ul>
</div>
</div> </div>
</div> @endif

View File

@@ -1,27 +1,7 @@
<div class="navbar-main"> <div class="navbar-main">
<x-services.links :service="$service" /> <x-services.links :service="$service" />
<div class="flex-1"></div> <div class="flex-1"></div>
@if (serviceStatus($service) === 'running') @if (serviceStatus($service) === 'degraded')
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<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
</button>
@elseif(serviceStatus($service) === 'exited')
<button wire:click='deploy' onclick="startService.showModal()"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 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
</button>
@elseif (serviceStatus($service) === 'degraded')
<button wire:click='deploy' onclick="startService.showModal()" <button wire:click='deploy' onclick="startService.showModal()"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@@ -42,4 +22,34 @@
Stop Stop
</button> </button>
@endif @endif
@if (serviceStatus($service) === 'running')
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<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
</button>
@endif
@if (serviceStatus($service) === 'exited')
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
<path fill="red"
d="M17.003 20a4.895 4.895 0 0 0-2.404-4.173L22 3l-1.73-1l-7.577 13.126a5.699 5.699 0 0 0-5.243 1.503C3.706 20.24 3.996 28.682 4.01 29.04a1 1 0 0 0 1 .96h14.991a1 1 0 0 0 .6-1.8c-3.54-2.656-3.598-8.146-3.598-8.2Zm-5.073-3.003A3.11 3.11 0 0 1 15.004 20c0 .038.002.208.017.469l-5.9-2.624a3.8 3.8 0 0 1 2.809-.848ZM15.45 28A5.2 5.2 0 0 1 14 25h-2a6.5 6.5 0 0 0 .968 3h-2.223A16.617 16.617 0 0 1 10 24H8a17.342 17.342 0 0 0 .665 4H6c.031-1.836.29-5.892 1.803-8.553l7.533 3.35A13.025 13.025 0 0 0 17.596 28Z" />
</svg>
Force cleanup containers
</button>
<button wire:click='deploy' onclick="startService.showModal()"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 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
</button>
@endif
</div> </div>

View File

@@ -1,2 +1,2 @@
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white']) }} <a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white z-50']) }}
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a> href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>

View File

@@ -0,0 +1,5 @@
<x-emails.layout>
<br><br>
If you do not like to receive these emails, you can unsubscribe [here]({{$unsubscribeUrl}}).
</x-emails.layout>

View File

@@ -7,8 +7,8 @@
<p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it. <p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it.
</p> </p>
@if ($exception->getMessage() !== '') @if ($exception->getMessage() !== '')
<p class="mt-6 text-xs leading-7 text-left text-red-500">Error: {{ $exception->getMessage() }} <code class="mt-6 text-xs text-left text-red-500">Error: {{ $exception->getMessage() }}
</p> </code>
@endif @endif
<div class="flex items-center justify-center mt-10 gap-x-6"> <div class="flex items-center justify-center mt-10 gap-x-6">
<a href="/"> <a href="/">

View File

@@ -2,7 +2,7 @@
@section('body') @section('body')
@parent @parent
<x-navbar /> <x-navbar />
<div class="fixed top-3 left-4" id="vue"> <div class="fixed z-50 top-[4.5rem] left-4" id="vue">
<magic-bar></magic-bar> <magic-bar></magic-bar>
</div> </div>
<main class="main max-w-screen-2xl"> <main class="main max-w-screen-2xl">

View File

@@ -94,6 +94,7 @@
}) })
}, 2000); }, 2000);
} }
function copyToClipboard(text) { function copyToClipboard(text) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
Livewire.emit('success', 'Copied to clipboard.'); Livewire.emit('success', 'Copied to clipboard.');

View File

@@ -2,7 +2,7 @@
@section('body') @section('body')
@parent @parent
@if (isSubscriptionOnGracePeriod()) @if (isSubscriptionOnGracePeriod())
<div class="fixed top-3 left-4" id="vue"> <div class="fixed top-[4.5rem] left-4 z-50" id="vue">
<magic-bar></magic-bar> <magic-bar></magic-bar>
</div> </div>
<x-navbar /> <x-navbar />

View File

@@ -10,7 +10,7 @@
</div> </div>
@endif @endif
<div <div
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 text-xs text-white"> class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
<pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.2000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($this->activity) }}</pre> <pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.2000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($this->activity) }}</pre>
</div> </div>

View File

@@ -40,7 +40,7 @@
<x-boarding-step title="Server"> <x-boarding-step title="Server">
<x-slot:question> <x-slot:question>
Do you want to deploy your resources on your <x-highlighted text="Localhost" /> Do you want to deploy your resources on your <x-highlighted text="Localhost" />
or on a <x-highlighted text="Remote Server" />? or on a<x-highlighted text="Remote Server" />?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')" <x-forms.button class="justify-center box" wire:target="setServerType('localhost')"

View File

@@ -1,10 +1,11 @@
<div class="flex flex-col gap-2 rounded modal-box"> <div class="flex flex-col w-11/12 max-w-5xl gap-2 modal-box">
<h3>How can we help?</h3> <h3>How can we help?</h3>
<div>You can report bug about the current page (details will be included automatically), or send us general feedback.</div> <div>Your feedback helps us to improve Coolify. Thank you! 💜</div>
<form wire:submit.prevent="submit" class="flex flex-col gap-4 pt-4"> <form wire:submit.prevent="submit" class="flex flex-col gap-4 pt-4">
<x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input> <x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input>
<x-forms.textarea id="description" label="Message" <x-forms.textarea rows="10" id="description" label="Description"
placeholder="Please provide as much information as possible."></x-forms.textarea> placeholder="Please provide as much information as possible."></x-forms.textarea>
<x-forms.button class="w-full mt-4" type="submit">Send Request</x-forms.button> <div></div>
<x-forms.button class="w-full mt-4" type="submit" onclick="help.close()">Send Email</x-forms.button>
</form> </form>
</div> </div>

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