Compare commits

...

92 Commits

Author SHA1 Message Date
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
127 changed files with 2634 additions and 1434 deletions

View File

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

View File

@@ -77,7 +77,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
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
if: always()
with:

View File

@@ -22,6 +22,7 @@ class StartProxy
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
@@ -50,12 +51,15 @@ class StartProxy
"echo '####### Proxy installed successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
if (!$async) {
instant_remote_process($commands, $server);
return 'OK';
} else {
if ($async) {
$activity = remote_process($commands, $server);
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 {
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$localhost_name = 'localhost';
$this->server = Server::where('name', $localhost_name)->first();
$this->server = Server::find(0)->first();
if (!$this->server) {
return;
}

View File

@@ -10,22 +10,16 @@ class StartService
use AsAction;
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 '####### Pulling images.'";
$commands[] = "mkdir -p $workdir";
$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[] = "docker compose pull";
$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";
$activity = remote_process($commands, $service->server);
return $activity;

View File

@@ -21,5 +21,6 @@ class StopService
$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 rm {$service->uuid} 2>/dev/null"], $service->server, false);
}
}

View File

@@ -56,6 +56,7 @@ class Emails extends Command
$type = select(
'Which Email should be sent?',
options: [
'updates' => 'Send Update Email to all users',
'emails-test' => 'Test',
'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed',
@@ -69,7 +70,7 @@ class Emails extends Command
'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)) {
$this->email = text('Email Address to send to');
}
@@ -78,6 +79,38 @@ class Emails extends Command
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
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':
$this->mail = (new Test())->toMail();
$this->sendEmail();
@@ -141,20 +174,20 @@ class Emails extends Command
$this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail();
break;
// case 'invitation-link':
// $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) {
// $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(),
// 'email' => $user->email,
// 'team_id' => 1,
// 'link' => 'http://example.com',
// ]);
// }
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
// case 'invitation-link':
// $user = User::all()->first();
// $invitation = TeamInvitation::whereEmail($user->email)->first();
// if (!$invitation) {
// $invitation = TeamInvitation::create([
// 'uuid' => Str::uuid(),
// 'email' => $user->email,
// 'team_id' => 1,
// 'link' => 'http://example.com',
// ]);
// }
// $this->mail = (new InvitationLink($user))->toMail();
// $this->sendEmail();
// break;
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
use function Laravel\Prompts\confirm;
class SyncBunny extends Command
{
/**
@@ -14,7 +16,7 @@ class SyncBunny extends Command
*
* @var string
*/
protected $signature = 'sync:bunny';
protected $signature = 'sync:bunny {--only-template} {--only-version}';
/**
* The console command description.
@@ -28,6 +30,9 @@ class SyncBunny extends Command
*/
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_path = "coolify";
$bunny_cdn_storage_name = "coolcdn";
@@ -39,51 +44,71 @@ class SyncBunny extends Command
$install_script = "install.sh";
$upgrade_script = "upgrade.sh";
$production_env = ".env.production";
$service_template = "service-templates.json";
$versions = "versions.json";
PendingRequest::macro('storage', function ($file) {
PendingRequest::macro('storage', function ($fileName) use($that) {
$headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream'
];
$fileStream = fopen($file, "r");
$file = fread($fileStream, filesize($file));
$fileStream = fopen($fileName, "r");
$file = fread($fileStream, filesize($fileName));
$that->info('Uploading: ' . $fileName);
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 = [
'AccessKey' => env('BUNNY_API_KEY'),
'Accept' => 'application/json',
];
ray('Purging: ' . $url);
$that->info('Purging: ' . $url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url,
"async" => false
]);
});
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) => [
$pool->storage(file: "$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(file: "$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(file: "$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"),
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
$pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"),
$pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"),
$pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
]);
ray("{$bunny_cdn}/{$bunny_cdn_path}");
Http::pool(fn (Pool $pool) => [
$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/$production_env"),
$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/$versions"),
]);
echo "All files uploaded & purged...\n";
$this->info("All files uploaded & purged...");
} 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->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->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->check_scheduled_backups($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)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
ray($servers);
foreach ($servers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}

View File

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

View File

@@ -2,8 +2,12 @@
namespace App\Http\Controllers;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ProjectController extends Controller
{
@@ -41,9 +45,10 @@ class ProjectController extends Controller
public function new()
{
$type = request()->query('type');
$services = getServiceTemplates();
$type = Str::of(request()->query('type'));
$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();
if (!$project) {
@@ -61,8 +66,71 @@ class ProjectController extends Controller
'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', [
'type' => $type
'type' => $type->value()
]);
}

View File

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

View File

@@ -4,8 +4,8 @@ namespace App\Http\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Route;
class Help extends Component
{
@@ -28,7 +28,7 @@ class Help extends Component
public function submit()
{
try {
$this->rateLimit(1, 60);
$this->rateLimit(3, 60);
$this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}";
@@ -42,7 +42,7 @@ class Help extends Component
);
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
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) {
return handleError($e, $this);
}

View File

@@ -22,9 +22,6 @@ class General extends Component
public string $git_branch;
public string|null $git_commit_sha;
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_git_submodules_enabled;
@@ -91,18 +88,20 @@ class General extends Component
$this->application->settings->save();
$this->application->save();
$this->application->refresh();
$this->checkWildCardDomain();
$this->emit('success', 'Application settings updated!');
}
protected function checkWildCardDomain()
{
$coolify_instance_settings = InstanceSettings::get();
$this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain');
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
}
public function getWildcardDomain() {
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateFqdn($server, $this->application->uuid);
ray($fqdn);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
}
public function mount()
{
$this->is_static = $this->application->settings->is_static;
@@ -112,31 +111,6 @@ class General extends Component
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
$this->checkWildCardDomain();
}
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()

View File

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

View File

@@ -2,73 +2,142 @@
namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Service;
use Livewire\Component;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component
{
public string $dockercompose = '';
public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
$this->dockercompose = 'services:
ghost:
documentation: https://ghost.org/docs/config
image: ghost:5
volumes:
- ghost-content-data:/var/lib/ghost/content
environment:
- url=$SERVICE_FQDN_GHOST
- database__client=mysql
- database__connection__host=mysql
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE-ghost}
depends_on:
- mysql
mysql:
documentation: https://hub.docker.com/_/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_MYSQL_ROOT}
';
$this->dockerComposeRaw = '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:
';
}
}
public function submit()
{
$this->validate([
'dockercompose' => 'required'
]);
$server_id = $this->query['server_id'];
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$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();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$project = Project::where('uuid', $this->parameters['project_uuid'])->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([
'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);
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
return redirect()->route('project.service', [
'service_uuid' => $service->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\Route;
use Livewire\Component;
use Spatie\Url\Url;
class GithubPrivateRepository extends Component
{
@@ -95,6 +96,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage();
}
}
$this->selected_branch_name = data_get($this->branches,'0.name');
}
protected function loadBranchByPage()
@@ -144,10 +146,9 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save();

View File

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

View File

@@ -76,13 +76,14 @@ class PublicGitRepository extends Component
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
return handleError($e, $this);
}
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} else {
return handleError($e, $this);
}
}
@@ -156,10 +157,8 @@ class PublicGitRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$application->save();

View File

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

View File

@@ -60,10 +60,7 @@ CMD ["nginx", "-g", "daemon off;"]
'source_type' => GithubApp::class
]);
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
if (isDev()) {
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'dockerfile-' . $application->uuid,
'fqdn' => $fqdn

View File

@@ -8,23 +8,47 @@ use Livewire\Component;
class Application extends Component
{
public ServiceApplication $application;
public $parameters;
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
'application.fqdn' => 'nullable',
'application.image' => 'required',
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
];
public function render()
{
ray($this->application->fileStorages()->get());
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()
{
try {
$this->validate();
$this->application->save();
updateCompose($this->application);
$this->emit('success', 'Application saved successfully.');
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
} finally {
$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;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component;
class Database extends Component
{
public ServiceDatabase $database;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'database.human_name' => 'nullable',
'database.description' => 'nullable',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
];
public function render()
{
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()
{
try {
$this->validate();
$this->database->save();
updateCompose($this->database);
$this->emit('success', 'Database saved successfully.');
} catch (\Throwable $e) {
ray($e);
} finally {

View File

@@ -3,17 +3,56 @@
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase $service;
public string $fs_path;
public ?string $workdir = null;
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'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()
{
return view('livewire.project.service.file-storage');

View File

@@ -2,45 +2,71 @@
namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Index extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => '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()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$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() {
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->emit('refreshEnvs');
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStack();
}
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 {
$this->validate();
$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) {
return handleError($e, $this);
}
}
}

View File

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

View File

@@ -11,8 +11,8 @@ use Livewire\Component;
class Show extends Component
{
public Service $service;
public ServiceApplication $serviceApplication;
public ServiceDatabase $serviceDatabase;
public ?ServiceApplication $serviceApplication = null;
public ?ServiceDatabase $serviceDatabase = null;
public array $parameters;
public array $query;
public Collection $services;
@@ -20,16 +20,26 @@ class Show extends Component
public function mount()
{
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$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()
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ class Show extends Component
public LocalPersistentVolume $storage;
public bool $isReadOnly = false;
public ?string $modalId = null;
public ?string $realName = null;
protected $rules = [
'storage.name' => 'required|string',
@@ -25,6 +26,11 @@ class Show extends Component
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);
}

View File

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

View File

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

View File

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

View File

@@ -14,8 +14,8 @@ class ShowPrivateKey extends Component
public function setPrivateKey($newPrivateKeyId)
{
try {
refresh_server_connection($this->server->privateKey);
$oldPrivateKeyId = $this->server->private_key_id;
refresh_server_connection($this->server->privateKey);
$this->server->update([
'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->is_auto_update_enabled = $this->is_auto_update_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->emit('success', 'Settings updated!');
}
@@ -68,7 +73,7 @@ class Configuration extends Component
{
$file = "$this->dynamic_config_path/coolify.yaml";
if (empty($this->settings->fqdn)) {
remote_process([
instant_remote_process([
"rm -f $file",
], $this->server);
} else {
@@ -124,7 +129,6 @@ class Configuration extends Component
];
}
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
dispatch(new ContainerStatusJob($this->server));
}
}
@@ -137,7 +141,7 @@ class Configuration extends Component
$yaml;
$base64 = base64_encode($yaml);
remote_process([
instant_remote_process([
"mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $this->server);

View File

@@ -97,7 +97,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
$preview_fqdn = getOnlyFqdn(data_get($this->preview, 'fqdn'));
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
$template = $this->application->preview_url_template;
$url = Url::fromString($this->application->fqdn);
$host = $url->getHost();
@@ -284,9 +284,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update()
{
$this->start_by_compose_file();
$this->health_check();
$this->stop_running_container();
if (count($this->application->ports_mappings_array) > 0){
$this->execute_remote_command(
["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()
{
@@ -529,7 +540,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'labels' => generateLabelsApplication($this->application, $this->preview),
// 'expose' => $ports,
'expose' => $ports,
'networks' => [
$this->destination->network,
],
@@ -704,10 +715,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->newVersionIsHealthy) {
if ($this->newVersionIsHealthy || $force) {
$this->execute_remote_command(
["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],
@@ -724,7 +735,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function start_by_compose_file()
{
$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],
);
}

View File

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

View File

@@ -61,7 +61,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void
{
try {
if (data_get($this->database, 'status') !== 'running') {
$status = Str::of(data_get($this->database, 'status'));
if (!$status->startsWith('running')) {
ray('database not running');
return;
}

View File

@@ -16,65 +16,66 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 500;
public $timeout = 1000;
public ?string $dockerRootFilesystem = null;
public ?int $usageBefore = null;
public function middleware(): array
{
return [
(new WithoutOverlapping("dockerimagejobs"))->shared(),
];
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
}
public function __construct()
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function handle(): void
{
$queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
if ($queue->count() > 0) {
$queuedCount = 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');
return;
}
try {
// ray()->showQueries()->color('orange');
$servers = Server::all();
foreach ($servers as $server) {
if (
!$server->isFunctional()
) {
continue;
}
if (isDev()) {
$this->dockerRootFilesystem = "/";
if (!$this->server->isFunctional()) {
return;
}
if (isDev()) {
$this->dockerRootFilesystem = "/";
} else {
$this->dockerRootFilesystem = instant_remote_process(
[
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
],
$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 {
$this->dockerRootFilesystem = instant_remote_process(
[
"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');
ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange');
}
} else {
ray('No need to clean up ' . $this->server->name)->color('orange');
}
} catch (\Throwable $e) {
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')) {
return 'deploy_key';
}
if (data_get($this, 'source')) {
} else if (data_get($this, 'source')) {
return 'source';
}
if (data_get($this, 'private_key_id')) {
return 'deploy_key';
}
throw new \Exception('No deployment type found');
}
public function could_set_build_commands(): bool
@@ -230,7 +226,7 @@ class Application extends BaseModel
}
public function git_based(): bool
{
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->dockercompose || $this->build_pack === 'dockercompose') {
if ($this->dockerfile) {
return false;
}
return true;

View File

@@ -14,7 +14,7 @@ class Environment extends Model
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()

View File

@@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
class GithubApp extends BaseModel
{
protected $guarded = [];
protected $appends = ['type'];
protected $casts = [
@@ -17,6 +18,7 @@ class GithubApp extends BaseModel
'webhook_secret',
];
static public function public()
{
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
@@ -34,6 +36,7 @@ class GithubApp extends BaseModel
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.');
}
$github_app->privateKey()->delete();
});
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
class LocalFileVolume extends BaseModel
{
@@ -11,6 +12,42 @@ class LocalFileVolume extends BaseModel
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()
{
return $this->morphTo();
return $this->morphTo('resource');
}
public function service()
{
return $this->morphTo();
return $this->morphTo('resource');
}
public function database()
{
return $this->morphTo('resource');
}
public function standalone_postgresql()
{
return $this->morphTo();
return $this->morphTo('resource');
}
protected function name(): Attribute

View File

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

View File

@@ -2,10 +2,10 @@
namespace App\Models;
use App\Enums\ProxyTypes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Str;
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 network rm {$service->uuid}"], $service->server, false);
});
}
public function type()
@@ -48,6 +49,12 @@ class Service extends BaseModel
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()
{
return $this->hasMany(ServiceApplication::class);
@@ -80,9 +87,31 @@ class Service extends BaseModel
{
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
{
ray('parsing');
// ray()->clearAll();
if ($this->docker_compose_raw) {
try {
@@ -91,73 +120,101 @@ class Service extends BaseModel
throw new \Exception($e->getMessage());
}
$composeVolumes = collect(data_get($yaml, 'volumes', []));
$composeNetworks = collect(data_get($yaml, 'networks', []));
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
$services = data_get($yaml, 'services');
$definedNetwork = $this->uuid;
$volumes = collect([]);
$envs = collect([]);
$ports = collect([]);
$generatedServiceFQDNS = collect([]);
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) {
$container_name = "$serviceName-{$this->uuid}";
$isDatabase = false;
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
$servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
$serviceVariables = collect(data_get($service, 'environment', []));
$serviceLabels = collect(data_get($service, 'labels', []));
$containerName = "$serviceName-{$this->uuid}";
// Decide if the service is a database
$image = data_get($service, 'image');
if ($image) {
$imageName = Str::of($image)->before(':');
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
$isDatabase = true;
data_set($service, 'is_database', true);
$isDatabase = false;
$image = data_get_str($service, 'image');
if ($image->contains(':')) {
$image = Str::of($image);
} else {
$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) {
$savedService = ServiceDatabase::create([
'name' => $serviceName,
'image' => $image,
'service_id' => $this->id
]);
} 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([
'name' => $serviceName,
'fqdn' => $defaultUsableFqdn,
'image' => $image,
'service_id' => $this->id
]);
}
} else {
if ($isDatabase) {
$savedService = $this->databases()->whereName($serviceName)->first();
} else {
$savedService = $this->applications()->whereName($serviceName)->first();
if (data_get($savedService, 'fqdn')) {
$defaultUsableFqdn = data_get($savedService, 'fqdn', null);
} else {
if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
if (isDev()) {
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
}
}
}
// Check if image changed
if ($savedService->image !== $image) {
$savedService->image = $image;
$savedService->save();
}
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
$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) {
$fqdns = collect(Str::of($fqdns)->explode(','));
}
// Collect ports
$servicePorts = collect(data_get($service, 'ports', []));
$ports->put($serviceName, $servicePorts);
// Collect/create/update ports
$collectedPorts = collect([]);
if ($servicePorts->count() > 0) {
foreach ($servicePorts as $sport) {
@@ -167,279 +224,286 @@ class Service extends BaseModel
if (is_array($sport)) {
$target = data_get($sport, 'target');
$published = data_get($sport, 'published');
$collectedPorts->push("$target:$published");
$protocol = data_get($sport, 'protocol');
$collectedPorts->push("$target:$published/$protocol");
}
}
}
$savedService->ports = $collectedPorts->implode(',');
$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
$definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) {
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
$composeNetworks->put($definedNetwork, [
$topLevelNetworks->put($definedNetwork, [
'name' => $definedNetwork,
'external' => false
'external' => true
]);
}
$networks = $serviceNetworks->toArray();
$networks = array_merge($networks, [$definedNetwork]);
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
foreach ($serviceVariables as $variable) {
$value = Str::after($variable, '=');
if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) {
$value = Str::of(replaceVariables(Str::of($value)));
if ($value->contains(':')) {
$nakedName = $value->before(':');
$nakedValue = $value->after(':');
} else if ($value->contains('-')) {
$nakedName = $value->before('-');
$nakedValue = $value->after('-');
} else if ($value->contains('+')) {
$nakedName = $value->before('+');
$nakedValue = $value->after('+');
foreach ($serviceVariables as $variableName => $variable) {
if (is_numeric($variableName)) {
$variable = Str::of($variable);
if ($variable->contains('=')) {
// - SESSION_SECRET=123
// - SESSION_SECRET=
$key = $variable->before('=');
$value = $variable->after('=');
} else {
$nakedName = $value;
// - SESSION_SECRET
$key = $variable;
$value = null;
}
if (isset($nakedName)) {
if (isset($nakedValue)) {
if ($nakedValue->startsWith('-')) {
$nakedValue = Str::of($nakedValue)->after('-');
} else {
// SESSION_SECRET: 123
// SESSION_SECRET:
$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('+')) {
$nakedValue = Str::of($nakedValue)->after('+');
}
if (!$envs->has($nakedName->value())) {
$envs->put($nakedName->value(), $nakedValue->value());
EnvironmentVariable::updateOrCreate([
'key' => $nakedName->value(),
'service_id' => $this->id,
], [
'value' => $nakedValue->value(),
$fqdn = "$fqdn$path";
}
if (!$isDatabase) {
$savedService->fqdn = $fqdn;
$savedService->save();
}
}
continue;
}
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,
'service_id' => $this->id,
'is_preview' => false,
]);
}
if (!$isDatabase) {
$savedService->fqdn = $fqdn;
$savedService->save();
}
} else {
if (!$envs->has($nakedName->value())) {
$envs->put($nakedName->value(), null);
$envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists();
if (!$envExists) {
EnvironmentVariable::create([
'key' => $nakedName->value(),
'value' => null,
'service_id' => $this->id,
'is_build_time' => false,
'is_preview' => false,
]);
}
switch ($command) {
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;
}
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $this->id,
'is_preview' => false,
]);
}
}
}
} else {
$variableName = Str::of(replaceVariables(Str::of($value)));
$generatedValue = null;
if ($variableName->startsWith('SERVICE_USER')) {
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
if (!$variableDefined) {
$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 if ($value->contains(':?')) {
$key = $value->before(':');
$defaultValue = $value->after(':?');
} else if ($value->contains('?')) {
$key = $value->before('?');
$defaultValue = $value->after('?');
} else {
$generatedValue = $variableDefined->value;
$key = $value;
$defaultValue = null;
}
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_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();
if ($foundEnv) {
$defaultValue = data_get($foundEnv, 'value');
}
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', []));
$labels = collect([]);
$labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service'));
if (!$isDatabase) {
if ($fqdns) {
$labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true));
}
// Add labels to the service
$fqdns = collect(data_get($savedService, 'fqdns'));
$defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) {
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_set($service, 'restart', RESTART_MODE);
data_set($service, 'container_name', $container_name);
data_forget($service, 'documentation');
data_set($service, 'container_name', $containerName);
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;
});
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
'volumes' => $composeVolumes->toArray(),
'networks' => $composeNetworks->toArray(),
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
];
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
$this->save();
$shouldBeDefined = collect([
'envs' => $envs,
'volumes' => $volumes,
'ports' => $ports
]);
$parsedCompose = collect([
'dockerCompose' => $finalServices,
'shouldBeDefined' => $shouldBeDefined
]);
return $parsedCompose;
$this->saveComposeConfigs();
return collect([]);
} else {
return collect([]);
}

View File

@@ -4,7 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Symfony\Component\Yaml\Yaml;
use Illuminate\Support\Facades\Cache;
class ServiceApplication extends BaseModel
{
@@ -15,10 +15,6 @@ class ServiceApplication extends BaseModel
{
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()
{
return $this->belongsTo(Service::class);
@@ -31,4 +27,17 @@ class ServiceApplication extends BaseModel
{
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';
}
public function documentation()
{
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
}
public function service()
{
return $this->belongsTo(Service::class);
@@ -26,4 +22,12 @@ class ServiceDatabase extends BaseModel
{
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;
public $tries = 5;
public $tries = 1;
public function __construct(public string $message)
{
}

View File

@@ -2,9 +2,6 @@
namespace App\Notifications;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
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());
});
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) {
$fqdns = collect(Str::of($application->fqdn)->explode(','));
$fqdns->map(function ($fqdn) {
$this->links->push(getOnlyFqdn($fqdn));
$this->links->push(getFqdnWithoutPort($fqdn));
});
}
if ($application->ports) {

View File

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

View File

@@ -130,7 +130,7 @@ function get_port_from_dockerfile($dockerfile): int
return 80;
}
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->push('coolify.managed=true');
@@ -141,13 +141,17 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
if ($pull_request_id !== 0) {
$labels->push('coolify.pullRequestId=' . $pull_request_id);
}
if ($type === 'service') {
$labels->push('coolify.service.subId=' . $subId);
$labels->push('coolify.service.subType=' . $subType);
}
return $labels;
}
function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
foreach($domains as $domain) {
foreach ($domains as $domain) {
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
@@ -216,9 +220,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
} else {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
}
if ($application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) {
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
}
// Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
}
return $labels->all();
}

View File

@@ -1,5 +1,6 @@
<?php
use App\Actions\Proxy\SaveConfiguration;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Symfony\Component\Yaml\Yaml;
@@ -10,7 +11,8 @@ function get_proxy_path()
$proxy_path = "$base_path/proxy";
return $proxy_path;
}
function connectProxyToNetworks(Server $server) {
function connectProxyToNetworks(Server $server)
{
$networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network'];
})->unique();
@@ -20,7 +22,7 @@ function connectProxyToNetworks(Server $server) {
$commands = $networks->map(function ($network) {
return [
"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",
];
});
@@ -101,7 +103,9 @@ function generate_default_proxy_configuration(Server $server)
if (isDev()) {
$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)

View File

@@ -189,7 +189,7 @@ function validateServer(Server $server, bool $throwError = false)
];
}
$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);
if (!$dockerVersion) {
$dockerVersion = null;

View File

@@ -1,7 +1,12 @@
<?php
use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
function replaceRegex(?string $name = null)
{
@@ -20,27 +25,115 @@ function serviceStatus(Service $service)
{
$foundRunning = false;
$isDegraded = false;
$foundRestaring = false;
$applications = $service->applications;
$databases = $service->databases;
foreach ($applications as $application) {
if ($application->exclude_from_status) {
continue;
}
if (Str::of($application->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($application->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
foreach ($databases as $database) {
if ($database->exclude_from_status) {
continue;
}
if (Str::of($database->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($database->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
if ($foundRestaring) {
return 'degraded';
}
if ($foundRunning && !$isDegraded) {
return 'running';
} else if ($foundRunning && $isDegraded) {
return 'degraded';
} else if (!$foundRunning && $isDegraded) {
} else if (!$foundRunning && !$isDegraded) {
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
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use App\Notifications\Channels\DiscordChannel;
@@ -11,11 +12,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\QueryException;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
use Nubs\RandomNameGenerator\All;
use Poliander\Cron\CronExpression;
use Visus\Cuid2\Cuid2;
@@ -240,11 +243,13 @@ function base_ip(): string
}
return "localhost";
}
function getOnlyFqdn(String $fqdn) {
function getFqdnWithoutPort(String $fqdn)
{
$url = Url::fromString($fqdn);
$host = $url->getHost();
$scheme = $url->getScheme();
return "$scheme://$host";
$path = $url->getPath();
return "$scheme://$host$path";
}
/**
* If fqdn is set, return it, otherwise return public ip.
@@ -383,3 +388,49 @@ function parseEnvFormatToArray($env_file_contents)
}
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
return [
'ssh' =>[
'docs' => [
'base_url' => 'https://coolify.io/docs',
'contact' => 'https://coolify.io/docs/contact',
],
'ssh' => [
'connection_timeout' => 10,
'server_interval' => 20,
'command_timeout' => 7200,
@@ -14,8 +18,11 @@ return [
'expiration' => 10,
],
],
'services' => [
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
],
'limits' => [
'trial_period'=> 7,
'trial_period' => 7,
'server' => [
'zero' => 0,
'self-hosted' => 999999999999,

View File

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

View File

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

View File

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

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

View File

@@ -6,19 +6,19 @@
"build": "vite build"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.2.3",
"autoprefixer": "10.4.14",
"axios": "1.4.0",
"laravel-vite-plugin": "0.7.8",
"postcss": "8.4.24",
"tailwindcss": "3.3.2",
"vite": "4.3.9",
"@vitejs/plugin-vue": "4.3.4",
"autoprefixer": "10.4.16",
"axios": "1.5.0",
"laravel-vite-plugin": "0.8.0",
"postcss": "8.4.30",
"tailwindcss": "3.3.3",
"vite": "4.4.9",
"vue": "3.3.4"
},
"dependencies": {
"@tailwindcss/typography": "0.5.9",
"alpinejs": "3.12.2",
"daisyui": "3.2.1",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.0",
"daisyui": "3.7.7",
"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.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",

View File

@@ -1,7 +1,7 @@
<template>
<Transition name="fade">
<div>
<div class="flex items-center p-1 px-2 mt-1 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
<div >
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
@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"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">

View File

@@ -15,14 +15,13 @@
@if (is_transactional_emails_active())
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf
<x-forms.input required type="email" name="email"
label="{{ __('input.email') }}" autofocus />
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form>
@else
<div>Transactional emails are not active on this instance.</div>
<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.
</div>
@endif

View File

@@ -3,7 +3,7 @@
<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">
@if (data_get($application, 'gitBrancLocation'))
<li>
@@ -19,7 +19,7 @@
@foreach (Str::of(data_get($application, 'fqdn'))->explode(',') as $fqdn)
<li>
<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"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="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="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>
</li>
@endforeach
@@ -38,7 +38,7 @@
@if (data_get($preview, 'fqdn'))
<li>
<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"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="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
<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">
<li title="Dashboard">
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
@@ -10,16 +11,13 @@
</svg>
</a>
</li>
<li title="Help" class="mt-auto">
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
</g>
<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" wire:click="help" onclick="help.showModal()">
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
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" />
</svg>
Feedback
</div>
</li>
<li class="pb-6" title="Logout">

View File

@@ -1,5 +1,6 @@
@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">
<li title="Dashboard">
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
@@ -114,16 +115,13 @@
</li>
@endif
@if (isSubscriptionActive() || isDev())
<li title="Help" class="mt-auto">
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
</g>
<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" wire:click="help" onclick="help.showModal()">
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
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" />
</svg>
Feedback
</div>
</li>
@endif

View File

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

View File

@@ -1,27 +1,7 @@
<div class="navbar-main">
<x-services.links :service="$service" />
<div class="flex-1"></div>
@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>
@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')
@if (serviceStatus($service) === 'degraded')
<button wire:click='deploy' onclick="startService.showModal()"
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">
@@ -42,4 +22,34 @@
Stop
</button>
@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>

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>

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>
@if ($exception->getMessage() !== '')
<p class="mt-6 text-xs leading-7 text-left text-red-500">Error: {{ $exception->getMessage() }}
</p>
<code class="mt-6 text-xs text-left text-red-500">Error: {{ $exception->getMessage() }}
</code>
@endif
<div class="flex items-center justify-center mt-10 gap-x-6">
<a href="/">

View File

@@ -2,7 +2,7 @@
@section('body')
@parent
<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>
</div>
<main class="main max-w-screen-2xl">

View File

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

View File

@@ -2,7 +2,7 @@
@section('body')
@parent
@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>
</div>
<x-navbar />

View File

@@ -10,7 +10,7 @@
</div>
@endif
<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>
</div>

View File

@@ -40,7 +40,7 @@
<x-boarding-step title="Server">
<x-slot:question>
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:actions>
<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>
<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">
<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>
<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>
</div>

View File

@@ -15,17 +15,17 @@
<div class="flex items-end gap-2">
<x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains"
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
@if ($wildcard_domain)
@if ($global_wildcard_domain)
<x-forms.button wire:click="generateGlobalRandomDomain">Set Global Wildcard
</x-forms.button>
@endif
@if ($server_wildcard_domain)
<x-forms.button wire:click="generateServerRandomDomain">Set Server Wildcard
</x-forms.button>
@endif
@endif
<x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button>
</div>
@if (!$application->dockerfile)
<div class="flex items-end gap-2">
<x-forms.select id="application.build_pack" label="Build Pack" required>
<option value="nixpacks">Nixpacks</option>
<option value="dockerfile">Dockerfile</option>
</x-forms.select>
</div>
@endif
@if ($application->settings->is_static)
<x-forms.select id="application.static_image" label="Static Image" required>
<option value="nginx:alpine">nginx:alpine</option>
@@ -54,6 +54,7 @@
@if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
@endif
<h3>Network</h3>
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->settings->is_static)
@@ -63,7 +64,7 @@
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
@endif
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><span class='inline-block font-bold text-warning'>Example</span>3000:3000,3002:3002" />
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
</div>
</div>
<h3>Advanced</h3>
@@ -80,7 +81,7 @@
id="is_auto_deploy_enabled" label="Auto Deploy" />
<x-forms.checkbox
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
instantSave id="is_preview_deployments_enabled" label="Previews Deployments" />
instantSave id="is_preview_deployments_enabled" label="Preview Deployments" />
<x-forms.checkbox instantSave id="is_git_submodules_enabled" label="Git Submodules"
helper="Allow Git Submodules during build process." />

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