Compare commits

...

96 Commits

Author SHA1 Message Date
Andras Bacsai
7ffebd71f3 Merge pull request #1518 from coollabsio/next
v4.0.0-beta.154
2023-12-07 14:04:03 +01:00
Andras Bacsai
2f286a6595 fix: container selection 2023-12-07 13:48:23 +01:00
Andras Bacsai
13701f6030 ui: env vars 2023-12-07 13:31:06 +01:00
Andras Bacsai
91f224ddea Merge pull request #1461 from DanHulton/feature/better_env_vars
Friendlier env vars page.
2023-12-07 13:29:41 +01:00
Andras Bacsai
8cffae10b2 rename things 2023-12-07 13:28:11 +01:00
Andras Bacsai
1158b2f4db feat: execute command in container 2023-12-07 13:07:16 +01:00
Andras Bacsai
f542bcf428 Merge pull request #1524 from stooit/feat/application-run-command
Add support for command execution in containers.
2023-12-07 11:29:32 +01:00
Stuart Rowlands
22178df8ae Add support for command execution in containers. 2023-12-06 15:42:14 -08:00
Andras Bacsai
acfe1daf9b fix: switching to static build 2023-12-06 21:32:23 +01:00
Andras Bacsai
fb3f71881f fix: use hc port 80 in case of static build 2023-12-06 21:09:41 +01:00
Andras Bacsai
0f7546a4dc add error message if realtime service is not reachable 2023-12-06 18:10:49 +01:00
Andras Bacsai
6ccafacc87 very important commit 2023-12-06 17:02:27 +01:00
Andras Bacsai
852c2df12e mono 2023-12-06 17:02:07 +01:00
Andras Bacsai
4c752951ab asd 2023-12-06 17:00:05 +01:00
Andras Bacsai
f32191b889 fix modal 2023-12-06 16:59:52 +01:00
Andras Bacsai
31251ef6cb nothing to see here 2023-12-06 16:38:46 +01:00
Andras Bacsai
e9365aa09b update js 2023-12-06 16:34:34 +01:00
Andras Bacsai
e5c860319f modal fix 2023-12-06 16:32:40 +01:00
Andras Bacsai
ceedd5225f modal fix? 2023-12-06 15:50:13 +01:00
Andras Bacsai
61efdfb7c1 revert 2023-12-06 15:30:04 +01:00
Andras Bacsai
fcef5d902e hmm 2023-12-06 15:04:07 +01:00
Andras Bacsai
2073b8949b lets see now 2023-12-06 14:57:03 +01:00
Andras Bacsai
5c59a752e3 test modal closing problem 2023-12-06 14:47:29 +01:00
Andras Bacsai
caf6e3a23e fix 2023-12-06 14:23:18 +01:00
Andras Bacsai
8eb1c4da46 fix prod compose 2023-12-06 14:22:19 +01:00
Andras Bacsai
bab2f391ed fix 2023-12-06 14:21:30 +01:00
Andras Bacsai
131c6df82a fix 2023-12-06 14:11:02 +01:00
Andras Bacsai
9f44d0c47a test 2023-12-06 13:50:27 +01:00
Andras Bacsai
dff7ed5b7b fix: bind volumes for compose bp 2023-12-06 13:45:43 +01:00
Andras Bacsai
54b6472f3b update packages 2023-12-06 13:32:20 +01:00
Andras Bacsai
04a36e5c90 update packages 2023-12-06 13:29:10 +01:00
Andras Bacsai
b2f851272b updates 2023-12-06 12:57:50 +01:00
Andras Bacsai
778915599f fix 2023-12-06 12:28:05 +01:00
Andras Bacsai
a6f9527157 fix 2023-12-06 12:20:20 +01:00
Andras Bacsai
db976709c7 add local js for pusher/echo 2023-12-06 10:32:49 +01:00
Andras Bacsai
7e4947ba07 setup test event 2023-12-06 10:25:23 +01:00
Andras Bacsai
e2578a7dd0 fix: deploy the right compose file 2023-12-06 09:36:11 +01:00
Andras Bacsai
7d028b15f5 wip 2023-12-05 15:26:05 +01:00
Andras Bacsai
d240bfda8b wip 2023-12-05 15:20:47 +01:00
Andras Bacsai
d59ec2548a wip 2023-12-05 15:19:54 +01:00
Andras Bacsai
e0cefc787a fix: add hc for soketi 2023-12-05 15:15:01 +01:00
Andras Bacsai
0496198d0f wip 2023-12-05 15:09:46 +01:00
Andras Bacsai
cbf7f1fa41 wip 2023-12-05 14:56:00 +01:00
Andras Bacsai
66587d864a wip 2023-12-05 14:52:19 +01:00
Andras Bacsai
b106705c8d wip 2023-12-05 14:18:44 +01:00
Andras Bacsai
5cc22a8271 wip: 🌮 2023-12-05 13:56:11 +01:00
Andras Bacsai
cd22863a8c wip 2023-12-05 12:31:07 +01:00
Andras Bacsai
118a02f70d wip 2023-12-05 12:18:23 +01:00
Andras Bacsai
862177d61a wip 2023-12-05 12:16:48 +01:00
Andras Bacsai
41280aa780 wip 2023-12-05 12:13:57 +01:00
Andras Bacsai
3a987c8a6e wip 2023-12-05 11:47:40 +01:00
Andras Bacsai
29ca461a9a wip 2023-12-05 11:35:05 +01:00
Andras Bacsai
d48b72c160 wip 2023-12-05 11:24:23 +01:00
Andras Bacsai
ce49f26c53 wip 2023-12-05 11:17:52 +01:00
Andras Bacsai
02989678be revert a few more things 2023-12-04 21:48:10 +01:00
Andras Bacsai
810b55163c revert: wip 2023-12-04 21:37:30 +01:00
Andras Bacsai
ac13ba0957 wip 2023-12-04 21:13:46 +01:00
Andras Bacsai
a3e088199f wip 2023-12-04 21:06:02 +01:00
Andras Bacsai
42ee4ca032 wip: broadcast 2023-12-04 20:47:32 +01:00
Andras Bacsai
17deff4d86 wip 2023-12-04 15:42:08 +01:00
Andras Bacsai
8b6323b906 refactor applicationdeploymentjob 2023-12-04 15:08:24 +01:00
Andras Bacsai
a696b5271a Update release version to 4.0.0-beta.154 2023-12-04 12:13:21 +01:00
Andras Bacsai
31959f26d4 Merge pull request #1517 from coollabsio/next
v4.0.0-beta.153
2023-12-04 11:42:21 +01:00
Andras Bacsai
45d2f80f69 fix: missing docker image thing 2023-12-04 11:41:44 +01:00
Andras Bacsai
53975fcf61 Merge pull request #1516 from coollabsio/next
v4.0.0-beta.152
2023-12-04 11:26:17 +01:00
Andras Bacsai
76296c1f19 fix: prevent autorefresh of proxy status 2023-12-04 11:25:24 +01:00
Andras Bacsai
c25baf69e1 fix: workdir issue for basedir
fix: remove / mount on helpers image
2023-12-04 11:20:50 +01:00
Andras Bacsai
f952512615 fix: add cf tunnel to boarding server view 2023-12-04 09:29:55 +01:00
Andras Bacsai
c6557eada8 service: meilisearch 2023-12-03 12:16:33 +01:00
Andras Bacsai
2c2d74c0d6 Update release version to 4.0.0-beta.152 2023-12-01 22:16:48 +01:00
Andras Bacsai
028a2eb275 Fix Docker compose build command and remove debug statements 2023-12-01 22:16:27 +01:00
Andras Bacsai
ce7fad5bef Merge pull request #1511 from coollabsio/next
fix: use official install script with rancher (one will work for sure)
2023-12-01 14:02:30 +01:00
Andras Bacsai
cd7852e4f9 fix: use official install script with rancher (one will work for sure) 2023-12-01 14:02:11 +01:00
Andras Bacsai
7b022a2482 Merge pull request #1510 from coollabsio/next
v4.0.0-beta.151
2023-12-01 13:03:42 +01:00
Andras Bacsai
12d9b6538b Fix environment variable parsing in Docker Compose file 2023-12-01 12:34:23 +01:00
Andras Bacsai
335788c2d6 fix: default value do not overwrite existing env value 2023-12-01 12:14:23 +01:00
Andras Bacsai
2352e4a71d Fix directory creation inApplicationDeploymentJob.php 2023-12-01 12:13:55 +01:00
Andras Bacsai
dc03179bd1 feat: auto-restart tcp proxies for databases 2023-12-01 11:37:00 +01:00
Andras Bacsai
cc72f416e8 feat: custom log drain endpoints 2023-12-01 11:13:58 +01:00
Andras Bacsai
3b67d0a8de feat: save timestamp configuration for logs 2023-12-01 10:34:30 +01:00
Andras Bacsai
0135ba7e89 Delete docker-compose.prod.standalone.yml 2023-11-30 13:38:52 +01:00
Andras Bacsai
a28a28cd23 Add docker-compose.prod.standalone.yml
configuration file
2023-11-30 13:17:43 +01:00
Andras Bacsai
b52680a2d8 Fix dispatch_sync issue in ContainerStatusJob 2023-11-30 12:55:31 +01:00
Andras Bacsai
0670e6c1d6 fix: server view for link() 2023-11-30 12:21:53 +01:00
Andras Bacsai
c3882b75c1 Update release version to 4.0.0-beta.151 2023-11-30 12:21:42 +01:00
Andras Bacsai
64b6f86a36 Update PostgreSQL image version to 16-alpine for services 2023-11-30 12:21:21 +01:00
Andras Bacsai
b9efc22253 Merge pull request #1504 from coollabsio/next
v4.0.0-beta.150 - quick fix
2023-11-29 18:44:25 +01:00
Andras Bacsai
e3d9eb0154 Add hidden flag to docker compose command 2023-11-29 18:43:02 +01:00
Andras Bacsai
66f3967479 Merge pull request #1503 from coollabsio/next
v4.0.0-beta.150
2023-11-29 18:41:41 +01:00
Andras Bacsai
c54439e84c fix: dockercompose save ./ volumes under /data/coolify 2023-11-29 18:40:41 +01:00
Andras Bacsai
db4a4c74fc Merge pull request #1502 from coollabsio/next
v4.0.0-beta.149
2023-11-29 17:05:28 +01:00
Andras Bacsai
5c7ef80219 Fix container retrieval in CheckLogDrainContainerJob and ContainerStatusJob 2023-11-29 17:03:04 +01:00
Andras Bacsai
243d1c06fc cloud: disable trial 2023-11-29 16:34:31 +01:00
Andras Bacsai
ef25f7d800 Update Sentry DSN 2023-11-29 16:21:03 +01:00
Andras Bacsai
45640ffdb1 Update version numbers to 4.0.0-beta.149 2023-11-29 16:19:40 +01:00
Dan Hulton
ccb972dcb9 Show row-based env var display at lg, not xl. Add border for col-based env var display. 2023-11-16 21:49:51 -05:00
104 changed files with 3071 additions and 1042 deletions

View File

@@ -4,3 +4,7 @@ APP_KEY=
DB_PASSWORD= DB_PASSWORD=
REDIS_PASSWORD= REDIS_PASSWORD=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=

View File

@@ -67,7 +67,7 @@ class InstallDocker
} }
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh", "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json", "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify", "echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",

View File

@@ -16,6 +16,8 @@ class InstallLogDrain
$type = 'highlight'; $type = 'highlight';
} else if ($server->settings->is_logdrain_axiom_enabled) { } else if ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom'; $type = 'axiom';
} else if ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom';
} else { } else {
$type = 'none'; $type = 'none';
} }
@@ -114,15 +116,23 @@ class InstallLogDrain
json_date_format iso8601 json_date_format iso8601
tls On tls On
"); ");
} else if ($type === 'custom') {
if (!$server->settings->is_logdrain_custom_enabled) {
throw new \Exception('Custom log drain is not enabled.');
}
$config = base64_encode($server->settings->logdrain_custom_config);
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
} else { } else {
throw new \Exception('Unknown log drain type.'); throw new \Exception('Unknown log drain type.');
} }
$parsers = base64_encode(" if ($type !== 'custom') {
$parsers = base64_encode("
[PARSER] [PARSER]
Name empty_line_skipper Name empty_line_skipper
Format regex Format regex
Regex /^(?!\s*$).+/ Regex /^(?!\s*$).+/
"); ");
}
$compose = base64_encode(" $compose = base64_encode("
services: services:
coolify-log-drain: coolify-log-drain:
@@ -179,6 +189,12 @@ Files:
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env", "echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env", "echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
]; ];
} else if ($type === 'custom') {
$add_envs_command = [
"touch $config_path/.env"
];
} else {
throw new \Exception('Unknown log drain type.');
} }
$restart_command = [ $restart_command = [
"echo 'Stopping old Fluent Bit'", "echo 'Stopping old Fluent Bit'",

View File

@@ -21,7 +21,7 @@ class StartService
$commands[] = "echo 'Pulling images.'"; $commands[] = "echo 'Pulling images.'";
$commands[] = "docker compose pull"; $commands[] = "docker compose pull";
$commands[] = "echo 'Starting containers.'"; $commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate"; $commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true"; $commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
$compose = data_get($service,'docker_compose',[]); $compose = data_get($service,'docker_compose',[]);
$serviceNames = data_get(Yaml::parse($compose),'services',[]); $serviceNames = data_get(Yaml::parse($compose),'services',[]);

View File

@@ -37,14 +37,14 @@ class Init extends Command
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
} }
private function cleanup_stucked_helper_containers() { private function cleanup_stucked_helper_containers()
{
$servers = Server::all(); $servers = Server::all();
foreach ($servers as $server) { foreach ($servers as $server) {
if ($server->isFunctional()) { if ($server->isFunctional()) {
CleanupHelperContainersJob::dispatch($server); CleanupHelperContainersJob::dispatch($server);
} }
} }
} }
private function alive() private function alive()
{ {
@@ -66,7 +66,7 @@ class Init extends Command
// private function cleanup_ssh() // private function cleanup_ssh()
// { // {
// TODO: it will cleanup id.root@host.docker.internal // TODO: it will cleanup id.root@host.docker.internal
// try { // try {
// $files = Storage::allFiles('ssh/keys'); // $files = Storage::allFiles('ssh/keys');
// foreach ($files as $file) { // foreach ($files as $file) {
@@ -101,15 +101,15 @@ class Init extends Command
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (!data_get($application, 'environment')) { if (!data_get($application, 'environment')) {
ray('Application without environment', $application->name); echo 'Application without environment' . $application->name . 'deleting\n';
$application->delete(); $application->delete();
} }
if (!$application->destination()) { if (!$application->destination()) {
ray('Application without destination', $application->name); echo 'Application without destination' . $application->name . 'deleting\n';
$application->delete(); $application->delete();
} }
if (!data_get($application, 'destination.server')) { if (!data_get($application, 'destination.server')) {
ray('Application without server', $application->name); echo 'Application without server' . $application->name . 'deleting\n';
$application->delete(); $application->delete();
} }
} }
@@ -120,15 +120,15 @@ class Init extends Command
$postgresqls = StandalonePostgresql::all(); $postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) { if (!data_get($postgresql, 'environment')) {
ray('Postgresql without environment', $postgresql->name); echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
$postgresql->delete(); $postgresql->delete();
} }
if (!$postgresql->destination()) { if (!$postgresql->destination()) {
ray('Postgresql without destination', $postgresql->name); echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
$postgresql->delete(); $postgresql->delete();
} }
if (!data_get($postgresql, 'destination.server')) { if (!data_get($postgresql, 'destination.server')) {
ray('Postgresql without server', $postgresql->name); echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
$postgresql->delete(); $postgresql->delete();
} }
} }
@@ -139,15 +139,15 @@ class Init extends Command
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) { if (!data_get($redis, 'environment')) {
ray('Redis without environment', $redis->name); echo 'Redis without environment' . $redis->name . 'deleting\n';
$redis->delete(); $redis->delete();
} }
if (!$redis->destination()) { if (!$redis->destination()) {
ray('Redis without destination', $redis->name); echo 'Redis without destination' . $redis->name . 'deleting\n';
$redis->delete(); $redis->delete();
} }
if (!data_get($redis, 'destination.server')) { if (!data_get($redis, 'destination.server')) {
ray('Redis without server', $redis->name); echo 'Redis without server' . $redis->name . 'deleting\n';
$redis->delete(); $redis->delete();
} }
} }
@@ -159,15 +159,15 @@ class Init extends Command
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) { if (!data_get($mongodb, 'environment')) {
ray('Mongodb without environment', $mongodb->name); echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
$mongodb->delete(); $mongodb->delete();
} }
if (!$mongodb->destination()) { if (!$mongodb->destination()) {
ray('Mongodb without destination', $mongodb->name); echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
$mongodb->delete(); $mongodb->delete();
} }
if (!data_get($mongodb, 'destination.server')) { if (!data_get($mongodb, 'destination.server')) {
ray('Mongodb without server', $mongodb->name); echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
$mongodb->delete(); $mongodb->delete();
} }
} }
@@ -179,15 +179,15 @@ class Init extends Command
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) { if (!data_get($mysql, 'environment')) {
ray('Mysql without environment', $mysql->name); echo 'Mysql without environment' . $mysql->name . 'deleting\n';
$mysql->delete(); $mysql->delete();
} }
if (!$mysql->destination()) { if (!$mysql->destination()) {
ray('Mysql without destination', $mysql->name); echo 'Mysql without destination' . $mysql->name . 'deleting\n';
$mysql->delete(); $mysql->delete();
} }
if (!data_get($mysql, 'destination.server')) { if (!data_get($mysql, 'destination.server')) {
ray('Mysql without server', $mysql->name); echo 'Mysql without server' . $mysql->name . 'deleting\n';
$mysql->delete(); $mysql->delete();
} }
} }
@@ -199,15 +199,15 @@ class Init extends Command
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) { if (!data_get($mariadb, 'environment')) {
ray('Mariadb without environment', $mariadb->name); echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
$mariadb->delete(); $mariadb->delete();
} }
if (!$mariadb->destination()) { if (!$mariadb->destination()) {
ray('Mariadb without destination', $mariadb->name); echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
$mariadb->delete(); $mariadb->delete();
} }
if (!data_get($mariadb, 'destination.server')) { if (!data_get($mariadb, 'destination.server')) {
ray('Mariadb without server', $mariadb->name); echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
$mariadb->delete(); $mariadb->delete();
} }
} }
@@ -219,15 +219,15 @@ class Init extends Command
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (!data_get($service, 'environment')) { if (!data_get($service, 'environment')) {
ray('Service without environment', $service->name); echo 'Service without environment' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
if (!$service->destination()) { if (!$service->destination()) {
ray('Service without destination', $service->name); echo 'Service without destination' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
if (!data_get($service, 'server')) { if (!data_get($service, 'server')) {
ray('Service without server', $service->name); echo 'Service without server' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
} }
@@ -238,7 +238,7 @@ class Init extends Command
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
ray('ServiceApplication without service', $service->name); echo 'ServiceApplication without service' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
} }
@@ -249,7 +249,7 @@ class Init extends Command
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
ray('ServiceDatabase without service', $service->name); echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
} }

28
app/Events/TestEvent.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TestEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct()
{
$this->teamId = auth()->user()->currentTeam()->id;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("custom.{$this->teamId}"),
];
}
}

View File

@@ -32,6 +32,7 @@ class Index extends Component
public ?int $remoteServerPort = 22; public ?int $remoteServerPort = 22;
public ?string $remoteServerUser = 'root'; public ?string $remoteServerUser = 'root';
public bool $isSwarmManager = false; public bool $isSwarmManager = false;
public bool $isCloudflareTunnel = false;
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection $projects; public Collection $projects;
@@ -184,6 +185,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager; $this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save(); $this->createdServer->settings->save();
$this->createdServer->addInitialNetwork(); $this->createdServer->addInitialNetwork();
$this->validateServer(); $this->validateServer();
@@ -200,6 +202,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->serverReachable = false; $this->serverReachable = false;
$this->createdServer->delete();
return handleError(error: $e, livewire: $this); return handleError(error: $e, livewire: $this);
} }

View File

@@ -10,7 +10,6 @@ class Dashboard extends Component
{ {
public $projects = []; public $projects = [];
public $servers = []; public $servers = [];
public function mount() public function mount()
{ {
$this->servers = Server::ownedByCurrentTeam()->get(); $this->servers = Server::ownedByCurrentTeam()->get();

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Livewire\Modal;
use App\Models\Service;
use Livewire\Component;
use LivewireUI\Modal\ModalComponent;
class EditCompose extends ModalComponent
{
public Service $service;
public $serviceId;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
];
public function mount() {
$this->service = Service::find($this->serviceId);
}
public function render()
{
return view('livewire.modal.edit-compose');
}
public function submit() {
$this->emit('warning', "Saving new docker compose...");
$this->emit('saveCompose', $this->service->docker_compose_raw);
$this->closeModal();
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
class Command extends Component
{
public string $command;
public string $container;
public $containers;
public $parameters;
public $resource;
public string $type;
public string $workDir = '';
public Server $server;
public $servers = [];
protected $rules = [
'server' => 'required',
'container' => 'required',
'command' => 'required',
'workDir' => 'nullable',
];
public function mount()
{
$this->containers = collect();
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names']));
});
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
}
}
}
}
}
$this->resource = $resource;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
$this->containers->push($this->container);
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
}
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
}
});
$this->server = $this->resource->server;
}
if ($this->containers->count() > 1) {
$this->container = $this->containers->first();
}
}
public function runCommand()
{
$this->validate();
try {
if (!empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$this->container} {$this->command}";
} else {
$exec = "docker exec {$this->container} {$this->command}";
}
$activity = remote_process([$exec], $this->server, ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.shared.execute-container-command');
}
}

View File

@@ -26,7 +26,7 @@ class Configuration extends Component
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
$this->application = $application; $this->application = $application;
$mainServer = $application->destination->server; $mainServer = $this->application->destination->server;
$servers = Server::ownedByCurrentTeam()->get(); $servers = Server::ownedByCurrentTeam()->get();
$this->servers = $servers->filter(function ($server) use ($mainServer) { $this->servers = $servers->filter(function ($server) use ($mainServer) {
return $server->id != $mainServer->id; return $server->id != $mainServer->id;

View File

@@ -18,7 +18,6 @@ class DeploymentNavbar extends Component
public Server $server; public Server $server;
public bool $is_debug_enabled = false; public bool $is_debug_enabled = false;
protected $listeners = ['deploymentFinished']; protected $listeners = ['deploymentFinished'];
public function mount() public function mount()
{ {
$this->application = Application::find($this->application_deployment_queue->application_id); $this->application = Application::find($this->application_deployment_queue->application_id);

View File

@@ -29,8 +29,6 @@ class General extends Component
public ?string $initialDockerComposeLocation = null; public ?string $initialDockerComposeLocation = null;
public ?string $initialDockerComposePrLocation = null; public ?string $initialDockerComposePrLocation = null;
public bool $is_static;
public $parsedServices = []; public $parsedServices = [];
public $parsedServiceDomains = []; public $parsedServiceDomains = [];
@@ -124,6 +122,10 @@ class General extends Component
{ {
$this->application->settings->save(); $this->application->settings->save();
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
$this->application->refresh();
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
} }
public function loadComposeFile($isInit = false) public function loadComposeFile($isInit = false)
{ {
@@ -156,7 +158,7 @@ class General extends Component
public function updatedApplicationBuildPack() public function updatedApplicationBuildPack()
{ {
if ($this->application->build_pack !== 'nixpacks') { if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false; $this->application->settings->is_static = false;
$this->application->settings->save(); $this->application->settings->save();
} }
if ($this->application->build_pack === 'dockercompose') { if ($this->application->build_pack === 'dockercompose') {

View File

@@ -39,6 +39,26 @@ class Heading extends Component
$this->deploy(force_rebuild: true); $this->deploy(force_rebuild: true);
} }
public function deployNew()
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->emit('error', 'Please load a Compose file first.');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
force_rebuild: false,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
public function deploy(bool $force_rebuild = false) public function deploy(bool $force_rebuild = false)
{ {
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) { if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
@@ -72,6 +92,22 @@ class Heading extends Component
$this->application->save(); $this->application->save();
$this->application->refresh(); $this->application->refresh();
} }
public function restartNew()
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
public function restart() public function restart()
{ {
$this->setDeploymentUuid(); $this->setDeploymentUuid();

View File

@@ -22,79 +22,19 @@ class DockerCompose extends Component
$this->query = request()->query(); $this->query = request()->query();
if (isDev()) { if (isDev()) {
$this->dockerComposeRaw = 'services: $this->dockerComposeRaw = 'services:
ghost: appsmith:
image: ghost:5 build:
volumes: context: .
- ~/configs:/etc/configs/:ro dockerfile_inline: |
- ./var/lib/ghost/content:/tmp/ghost2/content:ro FROM nginx
- /var/lib/ghost/content:/tmp/ghost/content:rw ARG GIT_COMMIT
- ghost-content-data:/var/lib/ghost/content ARG GIT_BRANCH
- type: volume RUN echo "Hello World ${GIT_COMMIT} ${GIT_BRANCH}"
source: mydata args:
target: /data - GIT_COMMIT=cdc3b19
- type: bind - GIT_BRANCH=${GIT_BRANCH}
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: environment:
- database__client=${DATABASE_CLIENT:-mysql} - APPSMITH_MAIL_ENABLED=${APPSMITH_MAIL_ENABLED}
- 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:
'; ';
} }
} }

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class ComposeModal extends Component
{
public ?string $raw = null;
public ?string $actual = null;
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

@@ -28,7 +28,7 @@ class Index extends Component
} }
public function checkStatus() public function checkStatus()
{ {
dispatch_sync(new ContainerStatusJob($this->service->server)); dispatch(new ContainerStatusJob($this->service->server));
$this->refreshStacks(); $this->refreshStacks();
} }
public function refreshStacks() public function refreshStacks()

View File

@@ -59,7 +59,6 @@ class Show extends Component
{ {
$this->validate(); $this->validate();
$this->env->save(); $this->env->save();
ray($this->env);
$this->emit('success', 'Environment variable updated successfully.'); $this->emit('success', 'Environment variable updated successfully.');
$this->emit('refreshEnvs'); $this->emit('refreshEnvs');
} }

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
class ExecuteContainerCommand extends Component
{
public string $command;
public string $container;
public $containers;
public $parameters;
public $resource;
public string $type;
public string $workDir = '';
public Server $server;
public $servers = [];
protected $rules = [
'server' => 'required',
'container' => 'required',
'command' => 'required',
'workDir' => 'nullable',
];
public function mount()
{
$this->containers = collect();
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names']));
});
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
}
}
}
}
}
$this->resource = $resource;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
$this->containers->push($this->container);
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
}
});
$this->resource->databases()->get()->each(function ($database) {
if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
}
});
$this->server = $this->resource->server;
}
if ($this->containers->count() > 0) {
$this->container = $this->containers->first();
}
}
public function runCommand()
{
$this->validate();
try {
if (!empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$this->container} {$this->command}";
} else {
$exec = "docker exec {$this->container} {$this->command}";
}
$activity = remote_process([$exec], $this->server, ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.shared.execute-container-command');
}
}

View File

@@ -2,7 +2,16 @@
namespace App\Http\Livewire\Project\Shared; namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server; use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Livewire\Component; use Livewire\Component;
@@ -10,17 +19,44 @@ class GetLogs extends Component
{ {
public string $outputs = ''; public string $outputs = '';
public string $errors = ''; public string $errors = '';
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
public Server $server; public Server $server;
public ?string $container = null; public ?string $container = null;
public ?bool $streamLogs = false; public ?bool $streamLogs = false;
public ?bool $showTimeStamps = true; public ?bool $showTimeStamps = true;
public int $numberOfLines = 100; public int $numberOfLines = 100;
public function mount()
{
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
} else {
if ($this->servicesubtype) {
$this->showTimeStamps = $this->servicesubtype->is_include_timestamps;
} else {
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
}
public function doSomethingWithThisChunkOfOutput($output) public function doSomethingWithThisChunkOfOutput($output)
{ {
$this->outputs .= removeAnsiColors($output); $this->outputs .= removeAnsiColors($output);
} }
public function instantSave() public function instantSave()
{ {
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
$this->resource->settings->save();
} else {
if ($this->servicesubtype) {
$this->servicesubtype->is_include_timestamps = $this->showTimeStamps;
$this->servicesubtype->save();
} else {
$this->resource->is_include_timestamps = $this->showTimeStamps;
$this->resource->save();
}
}
} }
public function getLogs($refresh = false) public function getLogs($refresh = false)
{ {

View File

@@ -22,6 +22,7 @@ class Logs extends Component
public $parameters; public $parameters;
public $query; public $query;
public $status; public $status;
public $serviceSubType;
public function mount() public function mount()
{ {
@@ -64,6 +65,11 @@ class Logs extends Component
} else if (data_get($this->parameters, 'service_uuid')) { } else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service'; $this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail(); $this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$service_name = data_get($this->parameters, 'service_name');
$this->serviceSubType = $this->resource->applications()->where('name', $service_name)->first();
if (!$this->serviceSubType) {
$this->serviceSubType = $this->resource->databases()->where('name', $service_name)->first();
}
$this->status = $this->resource->status; $this->status = $this->resource->status;
$this->server = $this->resource->server; $this->server = $this->resource->server;
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid; $this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;

View File

@@ -44,6 +44,7 @@ class Form extends Component
{ {
$this->wildcard_domain = $this->server->settings->wildcard_domain; $this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
$this->validateServer();
} }
public function serverRefresh($install = true) public function serverRefresh($install = true)
{ {

View File

@@ -19,6 +19,9 @@ class LogDrains extends Component
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean', 'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
'server.settings.logdrain_axiom_dataset_name' => 'required|string', 'server.settings.logdrain_axiom_dataset_name' => 'required|string',
'server.settings.logdrain_axiom_api_key' => 'required|string', 'server.settings.logdrain_axiom_api_key' => 'required|string',
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
'server.settings.logdrain_custom_config' => 'required|string',
'server.settings.logdrain_custom_config_parser' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain', 'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
@@ -29,6 +32,9 @@ class LogDrains extends Component
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain', 'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name', 'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
'server.settings.logdrain_axiom_api_key' => 'Axiom API key', 'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
'server.settings.is_logdrain_custom_enabled' => 'Custom log drain',
'server.settings.logdrain_custom_config' => 'Custom log drain configuration',
'server.settings.logdrain_custom_config_parser' => 'Custom log drain configuration parser',
]; ];
public function mount() public function mount()
@@ -84,6 +90,7 @@ class LogDrains extends Component
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_highlight_enabled' => false, 'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false, 'is_logdrain_axiom_enabled' => false,
'is_logdrain_custom_enabled' => false,
]); ]);
} else if ($type === 'highlight') { } else if ($type === 'highlight') {
$this->validate([ $this->validate([
@@ -93,6 +100,7 @@ class LogDrains extends Component
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_newrelic_enabled' => false, 'is_logdrain_newrelic_enabled' => false,
'is_logdrain_axiom_enabled' => false, 'is_logdrain_axiom_enabled' => false,
'is_logdrain_custom_enabled' => false,
]); ]);
} else if ($type === 'axiom') { } else if ($type === 'axiom') {
$this->validate([ $this->validate([
@@ -103,6 +111,18 @@ class LogDrains extends Component
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_newrelic_enabled' => false, 'is_logdrain_newrelic_enabled' => false,
'is_logdrain_highlight_enabled' => false, 'is_logdrain_highlight_enabled' => false,
'is_logdrain_custom_enabled' => false,
]);
} else if ($type === 'custom') {
$this->validate([
'server.settings.is_logdrain_custom_enabled' => 'required|boolean',
'server.settings.logdrain_custom_config' => 'required|string',
'server.settings.logdrain_custom_config_parser' => 'nullable',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false,
]); ]);
} }
$this->server->settings->save(); $this->server->settings->save();
@@ -121,6 +141,10 @@ class LogDrains extends Component
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_axiom_enabled' => false, 'is_logdrain_axiom_enabled' => false,
]); ]);
} else if ($type === 'custom') {
$this->server->settings->update([
'is_logdrain_custom_enabled' => false,
]);
} }
handleError($e, $this); handleError($e, $this);
return false; return false;

View File

@@ -14,6 +14,9 @@ class Status extends Component
public int $numberOfPolls = 0; public int $numberOfPolls = 0;
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling']; protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
public function mount() {
$this->checkProxy();
}
public function startProxyPolling() public function startProxyPolling()
{ {
$this->checkProxy(); $this->checkProxy();

View File

@@ -6,6 +6,17 @@ use Livewire\Component;
class Sponsorship extends Component class Sponsorship extends Component
{ {
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:custom.{$teamId},TestEvent" => 'testEvent',
];
}
public function testEvent()
{
$this->emit('success', 'Realtime events configured!');
}
public function disable() public function disable()
{ {
auth()->user()->update(['is_notification_sponsorship_enabled' => false]); auth()->user()->update(['is_notification_sponsorship_enabled' => false]);

View File

@@ -11,6 +11,9 @@ class PricingPlans extends Component
public bool $isTrial = false; public bool $isTrial = false;
public function mount() { public function mount() {
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended'); $this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
if (config('constants.limits.trial_period') == 0) {
$this->isTrial = false;
}
} }
public function subscribeStripe($type) public function subscribeStripe($type)
{ {
@@ -63,6 +66,7 @@ class PricingPlans extends Component
]; ];
if (!data_get($team,'subscription.stripe_trial_already_ended')) { if (!data_get($team,'subscription.stripe_trial_already_ended')) {
if (config('constants.limits.trial_period') > 0) {
$payload['subscription_data'] = [ $payload['subscription_data'] = [
'trial_period_days' => config('constants.limits.trial_period'), 'trial_period_days' => config('constants.limits.trial_period'),
'trial_settings' => [ 'trial_settings' => [
@@ -71,6 +75,7 @@ class PricingPlans extends Component
] ]
], ],
]; ];
}
$payload['payment_method_collection'] = 'if_required'; $payload['payment_method_collection'] = 'if_required';
} }
$customer = currentTeam()->subscription?->stripe_customer_id ?? null; $customer = currentTeam()->subscription?->stripe_customer_id ?? null;

View File

@@ -347,6 +347,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_image_names(); $this->generate_image_names();
$this->check_image_locally_or_remotely(); $this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->create_workdir();
$this->generate_compose_file(); $this->generate_compose_file();
$this->rolling_update(); $this->rolling_update();
return; return;
@@ -442,10 +443,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->clone_repository(); $this->clone_repository();
$this->generate_image_names(); $this->generate_image_names();
$this->cleanup_git(); $this->cleanup_git();
$this->application->loadComposeFile(isInit: false);
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
$yaml = Yaml::dump($composeFile->toArray(), 10); $yaml = Yaml::dump($composeFile->toArray(), 10);
ray($composeFile);
ray($this->container_name);
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
@@ -453,12 +453,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->save_environment_variables(); $this->save_environment_variables();
$this->stop_running_container(force: true); $this->stop_running_container(force: true);
ray($this->pull_request_id);
$networkId = $this->application->uuid; $networkId = $this->application->uuid;
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$networkId = "{$this->application->uuid}-{$this->pull_request_id}"; $networkId = "{$this->application->uuid}-{$this->pull_request_id}";
} }
ray($networkId);
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
// TODO // TODO
} else { } else {
@@ -468,9 +466,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]); ]);
} }
if (isset($this->docker_compose_base64)) {
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
}
$this->start_by_compose_file(); $this->start_by_compose_file();
$this->application->loadComposeFile(isInit: false);
} }
private function deploy_dockerfile_buildpack() private function deploy_dockerfile_buildpack()
{ {
@@ -513,6 +527,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->force_rebuild) { if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely(); $this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->create_workdir();
$this->execute_remote_command([ $this->execute_remote_command([
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'", "echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
]); ]);
@@ -660,7 +675,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
); );
} }
private function create_workdir()
{
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
);
}
private function prepare_builder_image() private function prepare_builder_image()
{ {
$helperImage = config('coolify.helper_image'); $helperImage = config('coolify.helper_image');
@@ -669,9 +691,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
if ($this->dockerConfigFileExists === 'OK') { if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; $runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else { } else {
$runCommand = "docker run -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; $runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
@@ -684,6 +706,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[ [
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}") "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
], ],
); );
} }
private function deploy_to_additional_destinations() private function deploy_to_additional_destinations()
@@ -1071,6 +1094,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$health_check_port = $this->application->health_check_port; $health_check_port = $this->application->health_check_port;
} }
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
$health_check_port = 80;
}
if ($this->application->health_check_path) { if ($this->application->health_check_path) {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}"; $this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
$generated_healthchecks_commands = [ $generated_healthchecks_commands = [
@@ -1226,9 +1252,15 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
); );
} else { } else {
$this->execute_remote_command( if ($this->docker_compose_location) {
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], $this->execute_remote_command(
); [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
}
} }
$this->application_deployment_queue->addLogEntry("New container started."); $this->application_deployment_queue->addLogEntry("New container started.");
} }
@@ -1264,7 +1296,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
} }
} }
ray($dockerfile->implode("\n"));
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"), executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),

View File

@@ -0,0 +1,180 @@
<?php
namespace App\Jobs;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Traits\ExecuteRemoteCommand;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;
use Throwable;
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
public $timeout = 3600;
public $tries = 1;
public static int $batch_counter = 0;
public Server $mainServer;
public $servers;
public string $basedir;
public string $workdir;
public string $deploymentUuid;
public int $pullRequestId = 0;
// Git related
public string $gitImportCommands;
public ?string $gitType = null;
public string $gitRepository;
public string $gitBranch;
public int $gitPort;
public string $gitFullRepoUrl;
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
{
$this->mainServer = data_get($this->application, 'destination.server');
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
$this->gitType = data_get($this->deployment, 'git_type');
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
}
public function handle()
{
try {
ray()->clearAll();
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
if ($this->application->dockerfile_target_build) {
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
}
// Get the git repository and port (custom port or default port)
[
'repository' => $this->gitRepository,
'port' => $this->gitPort
] = $this->application->customRepository();
// Get the git branch and git import commands
[
'commands' => $this->gitImportCommands,
'branch' => $this->gitBranch,
'fullRepoUrl' => $this->gitFullRepoUrl
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
$this->servers = $this->application->servers();
if ($this->deployment->restart_only) {
if ($this->application->build_pack === 'dockerimage') {
throw new \Exception('Restart only is not supported for docker image based deployments');
}
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
$this->servers->each(function ($server) {
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
$this->restartOnly($server);
});
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
} catch (Throwable $exception) {
$this->fail($exception);
} finally {
$this->servers->each(function ($server) {
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
$server->executeRemoteCommand(
commands: collect([])->push([
"command" => "docker rm -f {$this->deploymentUuid}",
"hidden" => true,
"ignoreErrors" => true,
]),
loggingModel: $this->deployment
);
});
}
}
public function restartOnly(Server $server)
{
$server->executeRemoteCommand(
commands: $this->application->prepareHelperImage($this->deploymentUuid),
loggingModel: $this->deployment
);
$privateKey = data_get($this->application, 'private_key.private_key', null);
$gitLsRemoteCommand = collect([]);
if ($privateKey) {
$privateKey = base64_decode($privateKey);
$gitLsRemoteCommand
->push([
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
])
->push([
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
])
->push([
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
])
->push([
"name" => "git_commit_sha",
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
"hidden" => true,
]);
} else {
$gitLsRemoteCommand->push([
"name" => "git_commit_sha",
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
"hidden" => true,
]);
}
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
$server->executeRemoteCommand(
commands: $gitLsRemoteCommand,
loggingModel: $this->deployment
);
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
[
'productionImageName' => $productionImageName
] = $this->application->generateImageNames($commit, $this->pullRequestId);
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
return;
}
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
}
public function failed(Throwable $exception): void
{
ray($exception);
$this->next(ApplicationDeploymentStatus::FAILED->value);
}
private function next(string $status)
{
// If the deployment is cancelled by the user, don't update the status
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
$this->deployment->update([
'status' => $status,
]);
}
queue_next_deployment($this->application, isNew: true);
}
}

View File

@@ -47,7 +47,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->server->isServerReady()) { if (!$this->server->isServerReady()) {
return; return;
}; };
$containers = instant_remote_process(["docker container ls -q"], $this->server); $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
if (!$containers) { if (!$containers) {
return; return;
} }

View File

@@ -2,6 +2,7 @@
namespace App\Jobs; namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
@@ -21,10 +22,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
$this->handle();
}
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->id))->dontRelease()]; return [(new WithoutOverlapping($this->server->id))->dontRelease()];
@@ -35,6 +32,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->id; return $this->server->id;
} }
public function __construct(public Server $server)
{
if (isDev()) $this->handle();
}
public function handle() public function handle()
{ {
// ray("checking container statuses for {$this->server->id}"); // ray("checking container statuses for {$this->server->id}");
@@ -47,7 +50,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); $containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else { } else {
// Precheck for containers // Precheck for containers
$containers = instant_remote_process(["docker container ls -q"], $this->server); $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
if (!$containers) { if (!$containers) {
return; return;
} }
@@ -83,7 +86,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$databases = $this->server->databases(); $databases = $this->server->databases();
$services = $this->server->services()->get(); $services = $this->server->services()->get();
$previews = $this->server->previews(); $previews = $this->server->previews();
$foundApplications = []; $foundApplications = [];
$foundApplicationPreviews = []; $foundApplicationPreviews = [];
$foundDatabases = []; $foundDatabases = [];
@@ -95,7 +97,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$uuid = data_get($labels, 'coolify.name'); $uuid = data_get($labels, 'coolify.name');
} else { } else {
$labels = data_get($container, 'Config.Labels'); $labels = data_get($container, 'Config.Labels');
$uuid = data_get($labels, 'com.docker.compose.service');
} }
$containerStatus = data_get($container, 'State.Status'); $containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
@@ -131,14 +132,29 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
} else { } else {
$uuid = data_get($labels, 'com.docker.compose.service');
if ($uuid) { if ($uuid) {
$database = $databases->where('uuid', $uuid)->first(); $database = $databases->where('uuid', $uuid)->first();
if ($database) { if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id; $foundDatabases[] = $database->id;
$statusFromDb = $database->status; $statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) { if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]); $database->update(['status' => $containerStatus]);
} }
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else { } else {
// Notify user that this container should not be there. // Notify user that this container should not be there.
} }
@@ -297,8 +313,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if ($shouldStart) { if ($shouldStart) {
StartProxy::run($this->server, false); StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server)); $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
} else {
ray('Proxy could not be started.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use RuntimeException; use RuntimeException;
@@ -48,13 +49,75 @@ class Application extends BaseModel
$application->environment_variables_preview()->delete(); $application->environment_variables_preview()->delete();
}); });
} }
// Build packs / deployment types
public function servers(): Collection
{
$mainServer = data_get($this, 'destination.server');
$additionalDestinations = data_get($this, 'additional_destinations', null);
$additionalServers = collect([]);
if ($this->isMultipleServerDeployment()) {
ray('asd');
if (str($additionalDestinations)->isNotEmpty()) {
$additionalDestinations = str($additionalDestinations)->explode(',');
foreach ($additionalDestinations as $destinationId) {
$destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first();
$server = data_get($destination, 'server');
$additionalServers->push($server);
}
}
}
return collect([$mainServer])->merge($additionalServers);
}
public function generateImageNames(string $commit, int $pullRequestId)
{
if ($this->dockerfile) {
if ($this->docker_registry_image_name) {
$buildImageName = Str::lower("{$this->docker_registry_image_name}:build");
$productionImageName = Str::lower("{$this->docker_registry_image_name}:latest");
} else {
$buildImageName = Str::lower("{$this->uuid}:build");
$productionImageName = Str::lower("{$this->uuid}:latest");
}
} else if ($this->build_pack === 'dockerimage') {
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}");
} else if ($pullRequestId === 0) {
$dockerImageTag = str($commit)->substr(0, 128);
if ($this->docker_registry_image_name) {
$buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build");
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}");
} else {
$buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build");
$productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}");
}
} else if ($pullRequestId !== 0) {
if ($this->docker_registry_image_name) {
$buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build");
$productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}");
} else {
$buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build");
$productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}");
}
}
return [
'buildImageName' => $buildImageName,
'productionImageName' => $productionImageName,
];
}
// End of build packs / deployment types
public function link() public function link()
{ {
return route('project.application.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.application.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'application_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'application_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function settings() public function settings()
{ {
@@ -382,9 +445,7 @@ class Application extends BaseModel
} }
public function isMultipleServerDeployment() public function isMultipleServerDeployment()
{ {
if (isDev()) { return false;
return true;
}
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) { if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
return true; return true;
} }
@@ -428,6 +489,294 @@ class Application extends BaseModel
{ {
return "/artifacts/{$uuid}"; return "/artifacts/{$uuid}";
} }
function generateHealthCheckCommands()
{
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0';
}
if (!$this->health_check_port) {
$health_check_port = $this->ports_exposes_array[0];
} else {
$health_check_port = $this->health_check_port;
}
if ($this->health_check_path) {
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
$generated_healthchecks_commands = [
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null"
];
} else {
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
$generated_healthchecks_commands = [
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"
];
}
return implode(' ', $generated_healthchecks_commands);
}
function generateLocalPersistentVolumes(int $pullRequestId)
{
$persistentStorages = [];
$volumeNames = [];
foreach ($this->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
if ($pullRequestId !== 0) {
$volume_name = $volume_name . '-pr-' . $pullRequestId;
}
$persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path;
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
if ($pullRequestId !== 0) {
$name = $name . '-pr-' . $pullRequestId;
}
$volumeNames[$name] = [
'name' => $name,
'external' => false,
];
}
return [
'persistentStorages' => $persistentStorages,
'volumeNames' => $volumeNames,
];
}
public function generateEnvironmentVariables($ports)
{
$environmentVariables = collect();
// ray('Generate Environment Variables')->green();
if ($this->pull_request_id === 0) {
// ray($this->runtime_environment_variables)->green();
foreach ($this->runtime_environment_variables as $env) {
$environmentVariables->push("$env->key=$env->value");
}
foreach ($this->nixpacks_environment_variables as $env) {
$environmentVariables->push("$env->key=$env->value");
}
} else {
// ray($this->runtime_environment_variables_preview)->green();
foreach ($this->runtime_environment_variables_preview as $env) {
$environmentVariables->push("$env->key=$env->value");
}
foreach ($this->nixpacks_environment_variables_preview as $env) {
$environmentVariables->push("$env->key=$env->value");
}
}
// Add PORT if not exists, use the first port as default
if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
$environmentVariables->push("PORT={$ports[0]}");
}
return $environmentVariables->all();
}
function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
{
$pullRequestId = $deployment->pull_request_id;
$ports = $this->settings->is_static ? [80] : $this->ports_exposes_array;
$container_name = generateApplicationContainerName($this, $this->pull_request_id);
$commit = str($deployment->getOutput('git_commit_sha'))->before("\t");
[
'productionImageName' => $productionImageName
] = $this->generateImageNames($commit, $pullRequestId);
[
'persistentStorages' => $persistentStorages,
'volumeNames' => $volumeNames
] = $this->generateLocalPersistentVolumes($pullRequestId);
$environmentVariables = $this->generateEnvironmentVariables($ports);
if (data_get($this, 'custom_labels')) {
$labels = collect(str($this->custom_labels)->explode(','));
$labels = $labels->filter(function ($value, $key) {
return !Str::startsWith($value, 'coolify.');
});
$this->custom_labels = $labels->implode(',');
$this->save();
} else {
$labels = collect(generateLabelsApplication($this, $this->preview));
}
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this, $this->preview));
}
$labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $productionImageName,
'container_name' => $container_name,
'restart' => RESTART_MODE,
'environment' => $environmentVariables,
'expose' => $ports,
'networks' => [
$this->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
$this->generateHealthCheckCommands()
],
'interval' => $this->health_check_interval . 's',
'timeout' => $this->health_check_timeout . 's',
'retries' => $this->health_check_retries,
'start_period' => $this->health_check_start_period . 's'
],
'mem_limit' => $this->limits_memory,
'memswap_limit' => $this->limits_memory_swap,
'mem_swappiness' => $this->limits_memory_swappiness,
'mem_reservation' => $this->limits_memory_reservation,
'cpus' => (int) $this->limits_cpus,
'cpuset' => $this->limits_cpuset,
'cpu_shares' => $this->limits_cpu_shares,
]
],
'networks' => [
$this->destination->network => [
'external' => true,
'name' => $this->destination->network,
'attachable' => true
]
]
];
if ($server->isSwarm()) {
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
data_forget($docker_compose, 'services.' . $container_name . '.expose');
data_forget($docker_compose, 'services.' . $container_name . '.restart');
data_forget($docker_compose, 'services.' . $container_name . '.mem_limit');
data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit');
data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness');
data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation');
data_forget($docker_compose, 'services.' . $container_name . '.cpus');
data_forget($docker_compose, 'services.' . $container_name . '.cpuset');
data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares');
$docker_compose['services'][$container_name]['deploy'] = [
'placement' => [
'constraints' => [
'node.role == worker'
]
],
'mode' => 'replicated',
'replicas' => 1,
'update_config' => [
'order' => 'start-first'
],
'rollback_config' => [
'order' => 'start-first'
],
'labels' => $labels,
'resources' => [
'limits' => [
'cpus' => $this->limits_cpus,
'memory' => $this->limits_memory,
],
'reservations' => [
'cpus' => $this->limits_cpus,
'memory' => $this->limits_memory,
]
]
];
} else {
$docker_compose['services'][$container_name]['labels'] = $labels;
}
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if ($this->settings->is_gpu_enabled) {
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [
[
'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'),
'capabilities' => ['gpu'],
'options' => data_get($this, 'settings.gpu_options', [])
]
];
if (data_get($this, 'settings.gpu_count')) {
$count = data_get($this, 'settings.gpu_count');
if ($count === 'all') {
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
} else {
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
}
} else if (data_get($this, 'settings.gpu_device_ids')) {
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids');
}
}
if ($this->isHealthcheckDisabled()) {
data_forget($docker_compose, 'services.' . $container_name . '.healthcheck');
}
if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array;
}
if (count($persistentStorages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistentStorages;
}
if (count($volumeNames) > 0) {
$docker_compose['volumes'] = $volumeNames;
}
$docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name];
data_forget($docker_compose, 'services.' . $container_name);
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$server->executeRemoteCommand(
commands: collect([])->push([
'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"),
'hidden' => true,
'ignoreErrors' => true
]),
loggingModel: $deployment
);
}
function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
{
$pullRequestId = $deployment->pull_request_id;
$containerName = generateApplicationContainerName($this, $pullRequestId);
// if (count($this->ports_mappings_array) > 0) {
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
ray($containers);
// if ($pullRequestId === 0) {
// $containers = $containers->filter(function ($container) use ($containerName) {
// return data_get($container, 'Names') !== $containerName;
// });
// }
$containers->each(function ($container) use ($server, $deployment) {
$removingContainerName = data_get($container, 'Names');
$server->executeRemoteCommand(
commands: collect([])->push([
'command' => "docker rm -f $removingContainerName",
'hidden' => true,
'ignoreErrors' => true
]),
loggingModel: $deployment
);
});
// }
$server->executeRemoteCommand(
commands: collect([])->push([
'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"),
'hidden' => true,
'ignoreErrors' => true
]),
loggingModel: $deployment
);
$deployment->addLogEntry("New container started.");
}
function setGitImportSettings(string $deployment_uuid, string $git_clone_command) function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
{ {
$baseDir = $this->generateBaseDir($deployment_uuid); $baseDir = $this->generateBaseDir($deployment_uuid);
@@ -584,12 +933,12 @@ class Application extends BaseModel
$commands = collect([]); $commands = collect([]);
if ($dockerConfigFileExists === 'OK') { if ($dockerConfigFileExists === 'OK') {
$commands->push([ $commands->push([
"command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage", "command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
"hidden" => true, "hidden" => true,
]); ]);
} else { } else {
$commands->push([ $commands->push([
"command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}", "command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
"hidden" => true, "hidden" => true,
]); ]);
} }
@@ -603,7 +952,6 @@ class Application extends BaseModel
{ {
if ($this->docker_compose_raw) { if ($this->docker_compose_raw) {
$mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id); $mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id);
ray($this->docker_compose_pr_raw);
if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) { if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) {
parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true); parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true);
} }

View File

@@ -9,6 +9,11 @@ class ApplicationDeploymentQueue extends Model
{ {
protected $guarded = []; protected $guarded = [];
public function setStatus(string $status) {
$this->update([
'status' => $status,
]);
}
public function getOutput($name) public function getOutput($name)
{ {
if (!$this->logs) { if (!$this->logs) {

View File

@@ -16,29 +16,16 @@ class ApplicationSetting extends Model
'is_git_submodules_enabled' => 'boolean', 'is_git_submodules_enabled' => 'boolean',
'is_git_lfs_enabled' => 'boolean', 'is_git_lfs_enabled' => 'boolean',
]; ];
protected $fillable = [ protected $guarded = [];
'application_id',
'is_static',
'is_auto_deploy_enabled',
'is_force_https_enabled',
'is_debug_enabled',
'is_preview_deployments_enabled',
'is_git_submodules_enabled',
'is_git_lfs_enabled',
];
public function isStatic(): Attribute public function isStatic(): Attribute
{ {
return Attribute::make( return Attribute::make(
set: function ($value) { set: function ($value) {
if (is_null($this->application->ports_exposes)) { if ($value) {
if ($value) { $this->application->ports_exposes = 80;
$this->application->ports_exposes = '80';
} else {
$this->application->ports_exposes = '3000';
}
$this->application->save();
} }
$this->application->save();
return $value; return $value;
} }
); );

View File

@@ -67,7 +67,7 @@ class Server extends BaseModel
{ {
$teamId = currentTeam()->id; $teamId = currentTeam()->id;
$selectArray = collect($select)->concat(['id']); $selectArray = collect($select)->concat(['id']);
return Server::whereTeamId($teamId)->with('settings','swarmDockers','standaloneDockers')->select($selectArray->all())->orderBy('name'); return Server::whereTeamId($teamId)->with('settings', 'swarmDockers', 'standaloneDockers')->select($selectArray->all())->orderBy('name');
} }
static public function isUsable() static public function isUsable()
@@ -86,9 +86,8 @@ class Server extends BaseModel
{ {
return $this->hasOne(ServerSetting::class); return $this->hasOne(ServerSetting::class);
} }
public function addInitialNetwork() { public function addInitialNetwork()
ray($this->id); {
if ($this->id === 0) { if ($this->id === 0) {
if ($this->isSwarm()) { if ($this->isSwarm()) {
SwarmDocker::create([ SwarmDocker::create([
@@ -349,7 +348,7 @@ class Server extends BaseModel
} }
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {
return $this->settings->is_logdrain_newrelic_enabled || $this->settings->is_logdrain_highlight_enabled || $this->settings->is_logdrain_axiom_enabled; return $this->settings->is_logdrain_newrelic_enabled || $this->settings->is_logdrain_highlight_enabled || $this->settings->is_logdrain_axiom_enabled || $this->settings->is_logdrain_custom_enabled;
} }
public function validateOS(): bool | Stringable public function validateOS(): bool | Stringable
{ {
@@ -383,17 +382,17 @@ class Server extends BaseModel
public function validateConnection() public function validateConnection()
{ {
$server = Server::find($this->id); $server = Server::find($this->id);
if ($this->skipServer()) { if ($server->skipServer()) {
return false; return false;
} }
$uptime = instant_remote_process(['uptime'], $this, false); $uptime = instant_remote_process(['uptime'], $server, false);
if (!$uptime) { if (!$uptime) {
$this->settings()->update([ $server->settings()->update([
'is_reachable' => false, 'is_reachable' => false,
]); ]);
return false; return false;
} else { } else {
$this->settings()->update([ $server->settings()->update([
'is_reachable' => true, 'is_reachable' => true,
]); ]);
$server->update([ $server->update([
@@ -401,8 +400,8 @@ class Server extends BaseModel
]); ]);
} }
if (data_get($this, 'unreachable_notification_sent') === true) { if (data_get($server, 'unreachable_notification_sent') === true) {
$this->team->notify(new Revived($this)); $server->team->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]); $server->update(['unreachable_notification_sent' => false]);
} }
@@ -538,4 +537,74 @@ class Server extends BaseModel
); );
}); });
} }
public function getHostIPMappings($network)
{
$addHosts = null;
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this);
if (!is_null($allContainers)) {
$allContainers = format_docker_command_output_to_json($allContainers);
$ips = collect([]);
if (count($allContainers) > 0) {
$allContainers = $allContainers[0];
foreach ($allContainers as $container) {
$containerName = data_get($container, 'Name');
if ($containerName === 'coolify-proxy') {
continue;
}
$containerIp = data_get($container, 'IPv4Address');
if ($containerName && $containerIp) {
$containerIp = str($containerIp)->before('/');
$ips->put($containerName, $containerIp->value());
}
}
}
$addHosts = $ips->map(function ($ip, $name) {
return "--add-host $name:$ip";
})->implode(' ');
}
return $addHosts;
}
public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment)
{
$this->executeRemoteCommand(
commands: collect([
[
"name" => "local_image_found",
"command" => "docker images -q {$imageName} 2>/dev/null",
"hidden" => true,
]
]),
loggingModel: $deployment
);
if (str($deployment->getOutput('local_image_found'))->isEmpty()) {
$this->executeRemoteCommand(
commands: collect([
[
"command" => "docker pull {$imageName} 2>/dev/null",
"ignoreErrors" => true,
"hidden" => true
],
[
"name" => "local_image_found",
"command" => "docker images -q {$imageName} 2>/dev/null",
"hidden" => true,
]
]),
loggingModel: $deployment
);
}
}
public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment)
{
$this->executeRemoteCommand(
commands: collect([
[
"command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"),
"ignoreErrors" => true,
"hidden" => true
],
]),
loggingModel: $deployment
);
}
} }

View File

@@ -131,6 +131,20 @@ class Service extends BaseModel
} }
$fields->put('Weblate', $data); $fields->put('Weblate', $data);
break; break;
case str($image)?->contains('meilisearch'):
$data = collect([]);
$SERVICE_PASSWORD_MEILISEARCH = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MEILISEARCH')->first();
if ($SERVICE_PASSWORD_MEILISEARCH) {
$data = $data->merge([
'API Key' => [
'key' => data_get($SERVICE_PASSWORD_MEILISEARCH, 'key'),
'value' => data_get($SERVICE_PASSWORD_MEILISEARCH, 'value'),
'isPassword' => true,
],
]);
}
$fields->put('Meilisearch', $data);
break;
case str($image)?->contains('ghost'): case str($image)?->contains('ghost'):
$data = collect([]); $data = collect([]);
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first(); $MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
@@ -193,6 +207,7 @@ class Service extends BaseModel
break; break;
} }
} }
ray($fields);
$databases = $this->databases()->get(); $databases = $this->databases()->get();
foreach ($databases as $database) { foreach ($databases as $database) {
@@ -363,11 +378,14 @@ class Service extends BaseModel
} }
public function link() public function link()
{ {
return route('project.service.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.service.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'service_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'service_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function documentation() public function documentation()
{ {

View File

@@ -43,11 +43,14 @@ class StandaloneMariadb extends BaseModel
} }
public function link() public function link()
{ {
return route('project.database.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.database.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'database_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'database_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {

View File

@@ -50,11 +50,14 @@ class StandaloneMongodb extends BaseModel
} }
public function link() public function link()
{ {
return route('project.database.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.database.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'database_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'database_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function mongoInitdbRootPassword(): Attribute public function mongoInitdbRootPassword(): Attribute
{ {

View File

@@ -43,11 +43,14 @@ class StandaloneMysql extends BaseModel
} }
public function link() public function link()
{ {
return route('project.database.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.database.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'database_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'database_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function type(): string public function type(): string
{ {

View File

@@ -43,11 +43,14 @@ class StandalonePostgresql extends BaseModel
} }
public function link() public function link()
{ {
return route('project.database.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.database.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'database_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'database_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {

View File

@@ -38,11 +38,14 @@ class StandaloneRedis extends BaseModel
} }
public function link() public function link()
{ {
return route('project.database.configuration', [ if (data_get($this, 'environment.project.uuid')) {
'project_uuid' => $this->environment->project->uuid, return route('project.database.configuration', [
'environment_name' => $this->environment->name, 'project_uuid' => data_get($this, 'environment.project.uuid'),
'database_uuid' => $this->uuid 'environment_name' => data_get($this, 'environment.name'),
]); 'database_uuid' => data_get($this, 'uuid')
]);
}
return null;
} }
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {

View File

@@ -8,28 +8,15 @@ use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvi
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
{ {
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [ protected $listen = [
Registered::class => [ // Registered::class => [
SendEmailVerificationNotification::class, // SendEmailVerificationNotification::class,
], // ],
]; ];
/**
* Register any events for your application.
*/
public function boot(): void public function boot(): void
{ {
// //
} }
/**
* Determine if events and listeners should be automatically discovered.
*/
public function shouldDiscoverEvents(): bool public function shouldDiscoverEvents(): bool
{ {
return false; return false;

View File

@@ -2,6 +2,7 @@
use App\Jobs\ApplicationDeployDockerImageJob; use App\Jobs\ApplicationDeployDockerImageJob;
use App\Jobs\ApplicationDeploymentJob; use App\Jobs\ApplicationDeploymentJob;
use App\Jobs\ApplicationDeploymentNewJob;
use App\Jobs\ApplicationDeploySimpleDockerfileJob; use App\Jobs\ApplicationDeploySimpleDockerfileJob;
use App\Jobs\ApplicationRestartJob; use App\Jobs\ApplicationRestartJob;
use App\Jobs\MultipleApplicationDeploymentJob; use App\Jobs\MultipleApplicationDeploymentJob;
@@ -11,7 +12,7 @@ use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null) function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $is_new_deployment = false)
{ {
$deployment = ApplicationDeploymentQueue::create([ $deployment = ApplicationDeploymentQueue::create([
'application_id' => $application_id, 'application_id' => $application_id,
@@ -36,19 +37,32 @@ function queue_application_deployment(int $application_id, string $deployment_uu
if ($running_deployments->count() > 0) { if ($running_deployments->count() > 0) {
return; return;
} }
dispatch(new ApplicationDeploymentJob( if ($is_new_deployment) {
application_deployment_queue_id: $deployment->id, dispatch(new ApplicationDeploymentNewJob(
))->onConnection('long-running')->onQueue('long-running'); deployment: $deployment,
application: Application::find($application_id)
))->onConnection('long-running')->onQueue('long-running');
} else {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
))->onConnection('long-running')->onQueue('long-running');
}
} }
function queue_next_deployment(Application $application) function queue_next_deployment(Application $application, bool $isNew = false)
{ {
$next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first(); $next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first();
if ($next_found) { if ($next_found) {
dispatch(new ApplicationDeploymentJob( if ($isNew) {
application_deployment_queue_id: $next_found->id, dispatch(new ApplicationDeploymentNewJob(
))->onConnection('long-running')->onQueue('long-running'); deployment: $next_found,
application: $application
))->onConnection('long-running')->onQueue('long-running');
} else {
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
))->onConnection('long-running')->onQueue('long-running');
}
} }
} }
// Deployment things // Deployment things
@@ -97,12 +111,12 @@ function prepareHelperContainer(Server $server, string $network, string $deploym
$commands = collect([]); $commands = collect([]);
if ($dockerConfigFileExists === 'OK') { if ($dockerConfigFileExists === 'OK') {
$commands->push([ $commands->push([
"command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage", "command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
"hidden" => true, "hidden" => true,
]); ]);
} else { } else {
$commands->push([ $commands->push([
"command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}", "command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
"hidden" => true, "hidden" => true,
]); ]);
} }

View File

@@ -913,13 +913,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
continue; continue;
} }
if ($value?->startsWith('$')) { if ($value?->startsWith('$')) {
$value = Str::of(replaceVariables($value));
$key = $value;
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
$value = Str::of(replaceVariables($value));
$key = $value;
if ($value->startsWith('SERVICE_')) { if ($value->startsWith('SERVICE_')) {
$foundEnv = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if ($command->value() === 'FQDN' || $command->value() === 'URL') { if ($command->value() === 'FQDN' || $command->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
@@ -999,7 +1003,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($savedService->serviceType()) { if ($savedService->serviceType()) {
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
} else { } else {
$fqdns = collect(data_get($savedService, 'fqdns')); $fqdns = collect(data_get($savedService, 'fqdns'))->filter();
} }
$defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
$serviceLabels = $serviceLabels->merge($defaultLabels); $serviceLabels = $serviceLabels->merge($defaultLabels);
@@ -1098,6 +1102,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceNetworks = collect(data_get($service, 'networks', [])); $serviceNetworks = collect(data_get($service, 'networks', []));
$serviceVariables = collect(data_get($service, 'environment', [])); $serviceVariables = collect(data_get($service, 'environment', []));
$serviceLabels = collect(data_get($service, 'labels', [])); $serviceLabels = collect(data_get($service, 'labels', []));
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
if ($serviceLabels->count() > 0) { if ($serviceLabels->count() > 0) {
$removedLabels = collect([]); $removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
@@ -1118,7 +1124,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) { $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) {
if (is_string($volume)) { if (is_string($volume)) {
$volume = str($volume); $volume = str($volume);
if ($volume->contains(':')) { if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
$newName = $resource->uuid . "-{$name}-pr-$pull_request_id"; $newName = $resource->uuid . "-{$name}-pr-$pull_request_id";
@@ -1132,19 +1138,64 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($source) { if ($source) {
$newSource = $resource->uuid . "-{$source}-pr-$pull_request_id"; $newSource = $resource->uuid . "-{$source}-pr-$pull_request_id";
data_set($volume, 'source', $newSource); data_set($volume, 'source', $newSource);
$topLevelVolumes->put($newSource, [ if (!str($source)->startsWith('/')) {
'name' => $newSource, $topLevelVolumes->put($newSource, [
]); 'name' => $newSource,
]);
}
} }
} }
return $volume->value(); return $volume->value();
}); });
data_set($service, 'volumes', $serviceVolumes->toArray()); data_set($service, 'volumes', $serviceVolumes->toArray());
} }
} else { } else {
// TODO if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
$volume = str("$name:$mount");
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
} else if (is_array($volume)) {
$source = data_get($volume, 'source');
if ($source) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~')) && !str($source)->startsWith('/')) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) {
$source = str('.', $dir, $source);
}
if (str($source, '~')) {
$source = str('~', $dir, $source);
}
data_set($volume, 'source', $source);
} else {
data_set($volume, 'source', $source);
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
} }
// Decide if the service is a database // Decide if the service is a database
$isDatabase = isDatabaseImage(data_get_str($service, 'image')); $isDatabase = isDatabaseImage(data_get_str($service, 'image'));
@@ -1271,13 +1322,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
continue; continue;
} }
if ($value?->startsWith('$')) { if ($value?->startsWith('$')) {
$value = Str::of(replaceVariables($value));
$key = $value;
$foundEnv = EnvironmentVariable::where([ $foundEnv = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'application_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
$value = Str::of(replaceVariables($value));
$key = $value;
if ($value->startsWith('SERVICE_')) { if ($value->startsWith('SERVICE_')) {
$foundEnv = EnvironmentVariable::where([
'key' => $key,
'application_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if ($command->value() === 'FQDN' || $command->value() === 'URL') { if ($command->value() === 'FQDN' || $command->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {

View File

@@ -28,6 +28,7 @@
"nubs/random-name-generator": "^2.2", "nubs/random-name-generator": "^2.2",
"phpseclib/phpseclib": "~3.0", "phpseclib/phpseclib": "~3.0",
"poliander/cron": "^3.0", "poliander/cron": "^3.0",
"pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0", "resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4", "sentry/sentry-laravel": "^3.4",
"spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-activitylog": "^4.7.3",
@@ -38,6 +39,7 @@
"stripe/stripe-php": "^12.0", "stripe/stripe-php": "^12.0",
"symfony/yaml": "^6.2", "symfony/yaml": "^6.2",
"visus/cuid2": "^2.0.0", "visus/cuid2": "^2.0.0",
"wire-elements/modal": "^1.0",
"yosymfony/toml": "^1.0" "yosymfony/toml": "^1.0"
}, },
"require-dev": { "require-dev": {

1279
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -194,7 +194,7 @@ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\FortifyServiceProvider::class, App\Providers\FortifyServiceProvider::class,
App\Providers\AuthServiceProvider::class, App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class, App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\HorizonServiceProvider::class, App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,

View File

@@ -15,7 +15,7 @@ return [
| |
*/ */
'default' => env('BROADCAST_DRIVER', 'null'), 'default' => env('BROADCAST_DRIVER', 'pusher'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -32,13 +32,13 @@ return [
'pusher' => [ 'pusher' => [
'driver' => 'pusher', 'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'), 'key' => env('PUSHER_APP_KEY', 'coolify'),
'secret' => env('PUSHER_APP_SECRET'), 'secret' => env('PUSHER_APP_SECRET', 'coolify'),
'app_id' => env('PUSHER_APP_ID'), 'app_id' => env('PUSHER_APP_ID', 'coolify'),
'options' => [ 'options' => [
'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com', 'host' => env('PUSHER_BACKEND_HOST', 'coolify-soketi'),
'port' => env('PUSHER_PORT', 443), 'port' => env('PUSHER_BACKEND_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'https'), 'scheme' => env('PUSHER_SCHEME', 'http'),
'encrypted' => true, 'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
], ],

View File

@@ -22,7 +22,7 @@ return [
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
], ],
'limits' => [ 'limits' => [
'trial_period' => 7, 'trial_period' => 0,
'server' => [ 'server' => [
'zero' => 0, 'zero' => 0,
'self-hosted' => 999999999999, 'self-hosted' => 999999999999,

View File

@@ -0,0 +1,52 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Include CSS
|--------------------------------------------------------------------------
|
| The modal uses TailwindCSS, if you don't use TailwindCSS you will need
| to set this parameter to true. This includes the modern-normalize css.
|
*/
'include_css' => false,
/*
|--------------------------------------------------------------------------
| Include JS
|--------------------------------------------------------------------------
|
| Livewire UI will inject the required Javascript in your blade template.
| If you want to bundle the required Javascript you can set this to false
| and add `require('vendor/wire-elements/modal/resources/js/modal');`
| to your script bundler like webpack.
|
*/
'include_js' => false,
/*
|--------------------------------------------------------------------------
| Modal Component Defaults
|--------------------------------------------------------------------------
|
| Configure the default properties for a modal component.
|
| Supported modal_max_width
| 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'
*/
'component_defaults' => [
'modal_max_width' => '7xl',
'close_modal_on_click_away' => true,
'close_modal_on_escape' => true,
'close_modal_on_escape_is_forceful' => true,
'dispatch_close_event' => false,
'destroy_on_close' => false,
],
];

View File

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

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.148'; return '4.0.0-beta.154';

View File

@@ -0,0 +1,64 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('service_applications', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('service_databases', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->boolean('is_include_timestamps')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('service_applications', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('service_databases', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->dropColumn('is_include_timestamps');
});
}
};

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

View File

@@ -13,11 +13,11 @@ class StandaloneDockerSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// StandaloneDocker::create([ StandaloneDocker::create([
// 'id' => 0, 'id' => 0,
// 'name' => 'Standalone Docker 1', 'name' => 'Standalone Docker 1',
// 'network' => 'coolify', 'network' => 'coolify',
// 'server_id' => 0, 'server_id' => 0,
// ]); ]);
} }
} }

View File

@@ -21,6 +21,12 @@ services:
SSL_MODE: "off" SSL_MODE: "off"
AUTORUN_LARAVEL_STORAGE_LINK: "false" AUTORUN_LARAVEL_STORAGE_LINK: "false"
AUTORUN_LARAVEL_MIGRATION: "false" AUTORUN_LARAVEL_MIGRATION: "false"
PUSHER_HOST: "${PUSHER_HOST}"
PUSHER_PORT: "${PUSHER_PORT:-6001}"
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
volumes: volumes:
- .:/var/www/html/:cached - .:/var/www/html/:cached
postgres: postgres:
@@ -44,9 +50,21 @@ services:
volumes: volumes:
- /data/coolify/_volumes/redis/:/data - /data/coolify/_volumes/redis/:/data
# - coolify-redis-data-dev:/data # - coolify-redis-data-dev:/data
soketi:
env_file:
- .env
ports:
- "${FORWARD_SOKETI_PORT:-6001}:6001"
environment:
SOKETI_DEBUG: "false"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
vite: vite:
image: node:20 image: node:20
working_dir: /var/www/html working_dir: /var/www/html
# environment:
# VITE_PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
ports: ports:
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}" - "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
volumes: volumes:
@@ -60,14 +78,14 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /data/coolify/:/data/coolify - /data/coolify/:/data/coolify
# - coolify-data-dev:/data/coolify # - coolify-data-dev:/data/coolify
remote-host: # remote-host:
<<: *testing-host-base # <<: *testing-host-base
container_name: coolify-remote-host # container_name: coolify-remote-host
volumes: # volumes:
- /:/host # - /:/host
- /var/run/docker.sock:/var/run/docker.sock # - /var/run/docker.sock:/var/run/docker.sock
- /data/coolify/:/data/coolify # - /data/coolify/:/data/coolify
# - coolify-data-dev:/data/coolify # # - coolify-data-dev:/data/coolify
mailpit: mailpit:
image: "axllent/mailpit:latest" image: "axllent/mailpit:latest"
container_name: coolify-mail container_name: coolify-mail

View File

@@ -1,7 +1,7 @@
version: '3.8' version: '3.8'
services: services:
coolify: coolify:
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-4.0.0-beta.20}" image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-4.0.0-beta.153}"
volumes: volumes:
- type: bind - type: bind
source: /data/coolify/source/.env source: /data/coolify/source/.env
@@ -35,6 +35,14 @@ services:
- PHP_PM_START_SERVERS=1 - PHP_PM_START_SERVERS=1
- PHP_PM_MIN_SPARE_SERVERS=1 - PHP_PM_MIN_SPARE_SERVERS=1
- PHP_PM_MAX_SPARE_SERVERS=10 - PHP_PM_MAX_SPARE_SERVERS=10
- PUSHER_HOST
- PUSHER_BACKEND_HOST
- PUSHER_PORT
- PUSHER_BACKEND_PORT
- PUSHER_SCHEME
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- SELF_HOSTED - SELF_HOSTED
- WAITLIST - WAITLIST
- SUBSCRIPTION_PROVIDER - SUBSCRIPTION_PROVIDER
@@ -74,8 +82,8 @@ services:
- "${APP_PORT:-8000}" - "${APP_PORT:-8000}"
healthcheck: healthcheck:
test: curl --fail http://localhost:80/api/health || exit 1 test: curl --fail http://localhost:80/api/health || exit 1
interval: 4s interval: 5s
retries: 5 retries: 10
timeout: 2s timeout: 2s
depends_on: depends_on:
postgres: postgres:
@@ -97,8 +105,8 @@ services:
"-d", "-d",
"${DB_DATABASE:-coolify}" "${DB_DATABASE:-coolify}"
] ]
interval: 2s interval: 5s
retries: 5 retries: 10
timeout: 2s timeout: 2s
redis: redis:
command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD} command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
@@ -108,8 +116,21 @@ services:
- coolify-redis:/data - coolify-redis:/data
healthcheck: healthcheck:
test: redis-cli ping test: redis-cli ping
interval: 2s interval: 5s
retries: 5 retries: 10
timeout: 2s
soketi:
ports:
- "${SOKETI_PORT:-6001}:6001"
environment:
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
healthcheck:
test: wget -qO- http://localhost:6001/ready || exit 1
interval: 5s
retries: 10
timeout: 2s timeout: 2s
volumes: volumes:
coolify-db: coolify-db:

View File

@@ -24,6 +24,12 @@ services:
restart: always restart: always
networks: networks:
- coolify - coolify
soketi:
image: 'quay.io/soketi/soketi:1.6-16-alpine'
container_name: coolify-soketi
restart: always
networks:
- coolify
networks: networks:
coolify: coolify:
name: coolify name: coolify

385
package-lock.json generated
View File

@@ -5,20 +5,24 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@alpinejs/focus": "^3.13.3",
"@tailwindcss/typography": "0.5.10", "@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.2", "alpinejs": "3.13.3",
"daisyui": "4.3.1", "daisyui": "4.4.19",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0" "tailwindcss-scrollbar": "0.1.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "4.5.0", "@vitejs/plugin-vue": "4.5.1",
"autoprefixer": "10.4.16", "autoprefixer": "10.4.16",
"axios": "1.6.2", "axios": "1.6.2",
"laravel-echo": "1.15.3",
"laravel-vite-plugin": "0.8.1", "laravel-vite-plugin": "0.8.1",
"postcss": "8.4.31", "postcss": "8.4.32",
"tailwindcss": "3.3.5", "pusher-js": "8.4.0-rc2",
"vite": "4.5.0", "tailwindcss": "3.3.6",
"vue": "3.3.8" "vite": "4.5.1",
"vue": "3.3.10"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@@ -32,10 +36,19 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@alpinejs/focus": {
"version": "3.13.3",
"resolved": "https://registry.npmjs.org/@alpinejs/focus/-/focus-3.13.3.tgz",
"integrity": "sha512-fTRX/9wOfysyZ1PJ4gHeUnmiNTIgqBDIqKxeP5iMvj1UHD3TFLDXllvoIKH3ezqcsyQZqxd/q1MFM7dlIhkmeg==",
"dependencies": {
"focus-trap": "^6.9.4",
"tabbable": "^5.3.3"
}
},
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.3", "version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -396,6 +409,11 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -503,9 +521,9 @@
} }
}, },
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "4.5.0", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.1.tgz",
"integrity": "sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ==", "integrity": "sha512-DaUzYFr+2UGDG7VSSdShKa9sIWYBa1LL8KC0MNOf2H5LjcTPjob0x8LbkqXWmAtbANJCkpiQTj66UVcQkN2s3g==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0" "node": "^14.18.0 || >=16.0.0"
@@ -516,77 +534,77 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.10.tgz",
"integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==", "integrity": "sha512-doe0hODR1+i1menPkRzJ5MNR6G+9uiZHIknK3Zn5OcIztu6GGw7u0XUzf3AgB8h/dfsZC9eouzoLo3c3+N/cVA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.0", "@babel/parser": "^7.23.5",
"@vue/shared": "3.3.8", "@vue/shared": "3.3.10",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"node_modules/@vue/compiler-core/node_modules/@vue/shared": { "node_modules/@vue/compiler-core/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.10.tgz",
"integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==", "integrity": "sha512-NCrqF5fm10GXZIK0GrEAauBqdy+F2LZRt3yNHzrYjpYBuRssQbuPLtSnSNjyR9luHKkWSH8we5LMB3g+4z2HvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.3.8", "@vue/compiler-core": "3.3.10",
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
} }
}, },
"node_modules/@vue/compiler-dom/node_modules/@vue/shared": { "node_modules/@vue/compiler-dom/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.10.tgz",
"integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==", "integrity": "sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.0", "@babel/parser": "^7.23.5",
"@vue/compiler-core": "3.3.8", "@vue/compiler-core": "3.3.10",
"@vue/compiler-dom": "3.3.8", "@vue/compiler-dom": "3.3.10",
"@vue/compiler-ssr": "3.3.8", "@vue/compiler-ssr": "3.3.10",
"@vue/reactivity-transform": "3.3.8", "@vue/reactivity-transform": "3.3.10",
"@vue/shared": "3.3.8", "@vue/shared": "3.3.10",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
"postcss": "^8.4.31", "postcss": "^8.4.32",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.10.tgz",
"integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==", "integrity": "sha512-12iM4jA4GEbskwXMmPcskK5wImc2ohKm408+o9iox3tfN9qua8xL0THIZtoe9OJHnXP4eOWZpgCAAThEveNlqQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.3.8", "@vue/compiler-dom": "3.3.10",
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
} }
}, },
"node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
@@ -598,83 +616,83 @@
} }
}, },
"node_modules/@vue/reactivity-transform": { "node_modules/@vue/reactivity-transform": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.10.tgz",
"integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==", "integrity": "sha512-0xBdk+CKHWT+Gev8oZ63Tc0qFfj935YZx+UAynlutnrDZ4diFCVFMWixn65HzjE3S1iJppWOo6Tt1OzASH7VEg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.0", "@babel/parser": "^7.23.5",
"@vue/compiler-core": "3.3.8", "@vue/compiler-core": "3.3.10",
"@vue/shared": "3.3.8", "@vue/shared": "3.3.10",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.5" "magic-string": "^0.30.5"
} }
}, },
"node_modules/@vue/reactivity-transform/node_modules/@vue/shared": { "node_modules/@vue/reactivity-transform/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.10.tgz",
"integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==", "integrity": "sha512-DZ0v31oTN4YHX9JEU5VW1LoIVgFovWgIVb30bWn9DG9a7oA415idcwsRNNajqTx8HQJyOaWfRKoyuP2P2TYIag==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/reactivity": "3.3.8", "@vue/reactivity": "3.3.10",
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
} }
}, },
"node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.10.tgz",
"integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==", "integrity": "sha512-H5Z7rOY/JLO+e5a6/FEXaQ1TMuOvY4LDVgT+/+HKubEAgs9qeeZ+NhADSeEtrNQeiKLDuzeKc8v0CUFpB6Pqgw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
} }
}, },
"node_modules/@vue/runtime-core/node_modules/@vue/shared": { "node_modules/@vue/runtime-core/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.10.tgz",
"integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==", "integrity": "sha512-c/jKb3ny05KJcYk0j1m7Wbhrxq7mZYr06GhKykDMNRRR9S+/dGT8KpHuNQjv3/8U4JshfkAk6TpecPD3B21Ijw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.3.8", "@vue/runtime-core": "3.3.10",
"@vue/shared": "3.3.8", "@vue/shared": "3.3.10",
"csstype": "^3.1.2" "csstype": "^3.1.2"
} }
}, },
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": { "node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.10.tgz",
"integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==", "integrity": "sha512-0i6ww3sBV3SKlF3YTjSVqKQ74xialMbjVYGy7cOTi7Imd8ediE7t72SK3qnvhrTAhOvlQhq6Bk6nFPdXxe0sAg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.3.8", "@vue/compiler-ssr": "3.3.10",
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.3.8" "vue": "3.3.10"
} }
}, },
"node_modules/@vue/server-renderer/node_modules/@vue/shared": { "node_modules/@vue/server-renderer/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
@@ -683,9 +701,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
}, },
"node_modules/alpinejs": { "node_modules/alpinejs": {
"version": "3.13.2", "version": "3.13.3",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.2.tgz", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
"integrity": "sha512-WzojeeN082kLZznGI1HAuP8yFJSWqJ1fGdz2mUjj45H4y0XwToE7fFqtI3mCPRR+BpcSbxT/NL+FyPnYAWSltw==", "integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
"dependencies": { "dependencies": {
"@vue/reactivity": "~3.1.1" "@vue/reactivity": "~3.1.1"
} }
@@ -896,6 +914,14 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -956,9 +982,9 @@
} }
}, },
"node_modules/daisyui": { "node_modules/daisyui": {
"version": "4.3.1", "version": "4.4.19",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.3.1.tgz", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.4.19.tgz",
"integrity": "sha512-dCi91VD+57lkoBd10CjdW4wPOeOPYvvzQbxti6xmyQbDMbCeCXwNq2KdoU798I4OsCcD5B+n7yVG7HAgYW+cvw==", "integrity": "sha512-IjOLWwnndD4N7Ut5CDxbUsaVtbqXPeVHM92IcgxGFxpuOd3CCKW/PAXZH6JoBTHFRaN57vB9XqEhdWm5yC+bPA==",
"dependencies": { "dependencies": {
"css-selector-tokenizer": "^0.8", "css-selector-tokenizer": "^0.8",
"culori": "^3", "culori": "^3",
@@ -973,6 +999,22 @@
"url": "https://opencollective.com/daisyui" "url": "https://opencollective.com/daisyui"
} }
}, },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -982,6 +1024,14 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -1100,6 +1150,14 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/focus-trap": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.4.tgz",
"integrity": "sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==",
"dependencies": {
"tabbable": "^5.3.3"
}
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
@@ -1233,6 +1291,29 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -1290,6 +1371,15 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/laravel-echo": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.15.3.tgz",
"integrity": "sha512-SRXzccaat6w4qKgZ4/rjFKr3nJfVxB+ly4V0MEJNIF1/TpERNXepo3uk7NnOjBGsiV/np1fl2XitAzW4Sa1s/w==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz",
@@ -1324,6 +1414,16 @@
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==" "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q=="
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -1398,6 +1498,11 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -1409,9 +1514,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.6", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -1518,9 +1623,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.4.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -1536,7 +1641,7 @@
} }
], ],
"dependencies": { "dependencies": {
"nanoid": "^3.3.6", "nanoid": "^3.3.7",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
@@ -1647,6 +1752,15 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true "dev": true
}, },
"node_modules/pusher-js": {
"version": "8.4.0-rc2",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
"dev": true,
"dependencies": {
"tweetnacl": "^1.0.3"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1685,6 +1799,25 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.2", "version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@@ -1775,6 +1908,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"node_modules/sucrase": { "node_modules/sucrase": {
"version": "3.32.0", "version": "3.32.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
@@ -1807,10 +1945,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tabbable": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz",
"integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA=="
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.3.5", "version": "3.3.6",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz",
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", "integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==",
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -1886,6 +2029,12 @@
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -1922,9 +2071,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.0", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.18.10", "esbuild": "^0.18.10",
@@ -1987,16 +2136,16 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.10.tgz",
"integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==", "integrity": "sha512-zg6SIXZdTBwiqCw/1p+m04VyHjLfwtjwz8N57sPaBhEex31ND0RYECVOC1YrRwMRmxFf5T1dabl6SGUbMKKuVw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.3.8", "@vue/compiler-dom": "3.3.10",
"@vue/compiler-sfc": "3.3.8", "@vue/compiler-sfc": "3.3.10",
"@vue/runtime-dom": "3.3.8", "@vue/runtime-dom": "3.3.10",
"@vue/server-renderer": "3.3.8", "@vue/server-renderer": "3.3.10",
"@vue/shared": "3.3.8" "@vue/shared": "3.3.10"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@@ -2008,9 +2157,9 @@
} }
}, },
"node_modules/vue/node_modules/@vue/shared": { "node_modules/vue/node_modules/@vue/shared": {
"version": "3.3.8", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==", "integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
"dev": true "dev": true
}, },
"node_modules/wrappy": { "node_modules/wrappy": {

View File

@@ -6,19 +6,23 @@
"build": "vite build" "build": "vite build"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "4.5.0", "@vitejs/plugin-vue": "4.5.1",
"autoprefixer": "10.4.16", "autoprefixer": "10.4.16",
"axios": "1.6.2", "axios": "1.6.2",
"laravel-echo": "1.15.3",
"laravel-vite-plugin": "0.8.1", "laravel-vite-plugin": "0.8.1",
"postcss": "8.4.31", "postcss": "8.4.32",
"tailwindcss": "3.3.5", "pusher-js": "8.4.0-rc2",
"vite": "4.5.0", "tailwindcss": "3.3.6",
"vue": "3.3.8" "vite": "4.5.1",
"vue": "3.3.10"
}, },
"dependencies": { "dependencies": {
"@alpinejs/focus": "^3.13.3",
"@tailwindcss/typography": "0.5.10", "@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.2", "alpinejs": "3.13.3",
"daisyui": "4.3.1", "daisyui": "4.4.19",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0" "tailwindcss-scrollbar": "0.1.0"
} }
} }

2
public/js/echo.js Normal file

File diff suppressed because one or more lines are too long

10
public/js/pusher.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -17,6 +17,9 @@ button[isError] {
.main { .main {
@apply pt-4 pl-24 pr-10 mx-auto; @apply pt-4 pl-24 pr-10 mx-auto;
} }
.custom-modal {
@apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200;
}
.label-text, .label-text,
label { label {
@apply text-neutral-400; @apply text-neutral-400;

View File

@@ -1,9 +1,11 @@
import Alpine from "alpinejs"; import Alpine from "alpinejs";
import focus from '@alpinejs/focus';
import { createApp } from "vue"; import { createApp } from "vue";
import MagicBar from "./components/MagicBar.vue"; import MagicBar from "./components/MagicBar.vue";
import Toaster from "../../vendor/masmerise/livewire-toaster/resources/js"; import Toaster from "../../vendor/masmerise/livewire-toaster/resources/js";
import "../../vendor/wire-elements/modal/resources/js/modal";
Alpine.plugin(focus);
Alpine.plugin(Toaster); Alpine.plugin(Toaster);
window.Alpine = Alpine; window.Alpine = Alpine;
@@ -12,3 +14,6 @@ Alpine.start();
const app = createApp({}); const app = createApp({});
app.component("magic-bar", MagicBar); app.component("magic-bar", MagicBar);
app.mount("#vue"); app.mount("#vue");

View File

@@ -3,14 +3,18 @@
href="{{ route('project.application.configuration', $parameters) }}"> href="{{ route('project.application.configuration', $parameters) }}">
<button>Configuration</button> <button>Configuration</button>
</a> </a>
<a class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
href="{{ route('project.application.deployments', $parameters) }}"> href="{{ route('project.application.command', $parameters) }}">
<button>Deployments</button> <button>Execute Command</button>
</a> </a>
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
href="{{ route('project.application.logs', $parameters) }}"> href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button> <button>Logs</button>
</a> </a>
<a class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}"
href="{{ route('project.application.deployments', $parameters) }}">
<button>Deployments</button>
</a>
<x-applications.links :application="$application" /> <x-applications.links :application="$application" />
<div class="flex-1"></div> <div class="flex-1"></div>
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw)) @if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
@@ -45,6 +49,19 @@
</svg> </svg>
Restart Restart
</button> </button>
@if (isDev())
<button title="Restart without rebuilding" wire:click='restartNew'
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">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart (new)
</button>
@endif
@endif @endif
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <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" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
@@ -66,6 +83,18 @@
</svg> </svg>
Deploy Deploy
</button> </button>
@if (isDev())
<button wire:click='deployNew'
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 (new)
</button>
@endif
@endif @endif
@endif @endif
</div> </div>

View File

@@ -3,6 +3,10 @@
href="{{ route('project.database.configuration', $parameters) }}"> href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button> <button>Configuration</button>
</a> </a>
<a class="{{ request()->routeIs('project.database.command') ? 'text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Execute Command</button>
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}"> href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button> <button>Logs</button>

View File

@@ -21,8 +21,11 @@
</label> </label>
</fieldset> </fieldset>
</div> </div>
<div class="py-2 text-center"><span class="font-bold text-warning">{{ config('constants.limits.trial_period') }} @if (config('constants.limits.trial_period') > 0)
days trial</span> included on all plans, without credit card details.</div> <div class="py-2 text-center"><span
class="font-bold text-warning">{{ config('constants.limits.trial_period') }}
days trial</span> included on all plans, without credit card details.</div>
@endif
<div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 "> <div x-show="selected === 'monthly'" class="flex justify-center h-10 mt-3 text-sm leading-6 ">
<div>Save <span class="font-bold text-warning">10%</span> annually with the yearly plans. <div>Save <span class="font-bold text-warning">10%</span> annually with the yearly plans.
</div> </div>
@@ -255,8 +258,8 @@
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for <div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
your self-hosted instance? your self-hosted instance?
<x-forms.button> <x-forms.button>
<a class="font-bold text-white hover:no-underline" <a class="font-bold text-white hover:no-underline" href="{{ config('coolify.docs') }}">Contact
href="{{ config('coolify.docs') }}">Contact Us</a> Us</a>
</x-forms.button> </x-forms.button>
</div> </div>
</div> </div>

View File

@@ -38,7 +38,8 @@
</button> </button>
@endif @endif
@if (serviceStatus($service) === 'exited') @if (serviceStatus($service) === 'exited')
<button wire:click='stop(true)' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> <button wire:click='stop(true)'
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"> <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="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
<path fill="red" <path fill="red"

View File

@@ -1,7 +1,7 @@
@props([ @props([
'status' => 'Degraded', 'status' => 'Degraded',
]) ])
<x-loading wire:loading.delay /> <x-loading wire:loading.delay.longer />
<div class="flex items-center gap-2" wire:loading.remove.delay.longer> <div class="flex items-center gap-2" wire:loading.remove.delay.longer>
<div class="badge badge-warning badge-xs"></div> <div class="badge badge-warning badge-xs"></div>
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div> <div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>

View File

@@ -1,7 +1,7 @@
@props([ @props([
'status' => 'Restarting', 'status' => 'Restarting',
]) ])
<x-loading wire:loading.delay /> <x-loading wire:loading.delay.longer />
<div class="flex items-center gap-2" wire:loading.remove.delay.longer> <div class="flex items-center gap-2" wire:loading.remove.delay.longer>
<div class="badge badge-warning badge-xs"></div> <div class="badge badge-warning badge-xs"></div>
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div> <div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>

View File

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

View File

@@ -24,11 +24,20 @@
@if (config('app.name') == 'Coolify Cloud') @if (config('app.name') == 'Coolify Cloud')
<script defer data-domain="app.coolify.io" src="https://analytics.coollabs.io/js/plausible.js"></script> <script defer data-domain="app.coolify.io" src="https://analytics.coollabs.io/js/plausible.js"></script>
@endif @endif
@auth
<script src="https://cdnjs.cloudflare.com/ajax/libs/laravel-echo/1.15.3/echo.iife.min.js"
integrity="sha512-aPAh2oRUr3ALz2MwVWkd6lmdgBQC0wSr0R++zclNjXZreT/JrwDPZQwA/p6R3wOCTcXKIHgA9pQGEQBWQmdLaA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pusher/8.3.0/pusher.min.js"
integrity="sha512-tXL5mrkSoP49uQf2jO0LbvzMyFgki//znmq0wYXGq94gVF6TU0QlrSbwGuPpKTeN1mIjReeqKZ4/NJPjHN1d2Q=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@endauth
</head> </head>
@section('body') @section('body')
<body> <body>
@livewireScripts @livewireScripts
@livewire('livewire-ui-modal')
<dialog id="help" class="modal"> <dialog id="help" class="modal">
<livewire:help /> <livewire:help />
<form method="dialog" class="modal-backdrop"> <form method="dialog" class="modal-backdrop">
@@ -37,7 +46,47 @@
</dialog> </dialog>
<x-toaster-hub /> <x-toaster-hub />
<x-version class="fixed left-2 bottom-1" /> <x-version class="fixed left-2 bottom-1" />
<script> <script>
@auth
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
cluster: "{{ env('PUSHER_HOST') }}" || window.location.hostname,
key: "{{ env('PUSHER_APP_KEY') }}" || 'coolify',
wsHost: "{{ env('PUSHER_HOST') }}" || window.location.hostname,
wsPort: "{{ env('PUSHER_PORT') }}" || 6001,
wssPort: "{{ env('PUSHER_PORT') }}" || 6001,
forceTLS: false,
encrypted: true,
enableStats: false,
enableLogging: true,
enabledTransports: ['ws', 'wss'],
});
if ("{{ auth()->user()->id }}" == 0) {
let checkPusherInterval = null;
let checkNumber = 0;
let errorMessage = "Coolify could not connect to the new realtime service introduced in beta.154.<br>Please check the related <a href='https://coolify.io/docs/cloudflare-tunnels' target='_blank'>documentation</a> or get help on <a href='https://coollabs.io/discord' target='_blank'>Discord</a>.";
checkPusherInterval = setInterval(() => {
if (window.Echo) {
if (window.Echo.connector.pusher.connection.state !== 'connected') {
checkNumber++;
if (checkNumber > 5) {
clearInterval(checkPusherInterval);
Livewire.emit('error', errorMessage);
}
} else {
console.log('Coolify is now connected to the new realtime service introduced in beta.154.');
clearInterval(checkPusherInterval);
}
} else {
clearInterval(checkPusherInterval);
Livewire.emit('error', errorMessage);
}
}, 2000);
}
@endauth
let checkHealthInterval = null; let checkHealthInterval = null;
let checkIfIamDeadInterval = null; let checkIfIamDeadInterval = null;

View File

@@ -207,10 +207,11 @@
placeholder="Username to connect to your server. Default is root." label="Username" placeholder="Username to connect to your server. Default is root." label="Username"
id="remoteServerUser" /> id="remoteServerUser" />
</div> </div>
{{-- <div class="w-64"> <div class="w-64">
<x-forms.checkbox type="checkbox" id="isSwarmManager" <x-forms.checkbox
label="Is it a Swarm Manager?" /> helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
</div> --}} id="isCloudflareTunnel" label="Cloudflare Tunnel" />
</div>
<x-forms.button type="submit">Check Connection</x-forms.button> <x-forms.button type="submit">Check Connection</x-forms.button>
</form> </form>
</x-slot:actions> </x-slot:actions>

View File

@@ -0,0 +1,32 @@
<form x-data="{ raw: true }" class="custom-modal" wire:submit.prevent='submit'>
<div class="flex items-end gap-2">
<h1>Docker Compose</h1>
<div x-cloak x-show="raw">
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable Compose</x-forms.button>
</div>
<div x-cloak x-show="raw === false">
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source
Compose</x-forms.button>
</div>
</div>
<div>Volume names are updated upon save. The service UUID will be added as a prefix to all volumes, to prevent
name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage
menu.</div>
<div x-cloak x-show="raw" class="font-mono">
<x-forms.textarea rows="20" id="service.docker_compose_raw">
</x-forms.textarea>
</div>
<div x-cloak x-show="raw === false" class="font-mono">
<x-forms.textarea rows="20" readonly id="service.docker_compose">
</x-forms.textarea>
</div>
<div class="flex justify-end w-full gap-2">
<x-forms.button class="w-64" type="submit">
Save
</x-forms.button>
<x-forms.button wire:click='closeModal'>
Close
</x-forms.button>
</div>
</form>

View File

@@ -67,14 +67,16 @@
@endif @endif
</div> </div>
@endif @endif
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<h3>Docker Registry</h3> <h3>Docker Registry</h3>
@if ($application->destination->server->isSwarm()) @if ($application->destination->server->isSwarm())
<div>Docker Swarm requires the image to be available in a registry. More info <a class="underline" <div>Docker Swarm requires the image to be available in a registry. More info <a class="underline"
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
@else @else
<div>Push the built image to a docker registry. More info <a class="underline" @if ($application->build_pack !== 'dockerimage')
<div>Push the built image to a docker registry. More info <a class="underline"
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
@endif
@endif @endif
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
@if ($application->build_pack === 'dockerimage') @if ($application->build_pack === 'dockerimage')

View File

@@ -1,30 +0,0 @@
<dialog id="composeModal" class="modal" x-data="{ raw: true }" wire:ignore.self>
<form method="dialog" class="flex flex-col gap-2 rounded max-w-7xl modal-box" wire:submit.prevent='submit'>
<div class="flex items-end gap-2">
<h1>Docker Compose</h1>
<div x-cloak x-show="raw">
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable Compose</x-forms.button>
</div>
<div x-cloak x-show="raw === false">
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source
Compose</x-forms.button>
</div>
</div>
<div>Volume names are updated upon save. The service UUID will be added as a prefix to all volumes, to prevent name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage menu.</div>
<div x-cloak x-show="raw">
<x-forms.textarea rows="20" id="raw">
</x-forms.textarea>
</div>
<div x-cloak x-show="raw === false">
<x-forms.textarea rows="20" readonly id="actual">
</x-forms.textarea>
</div>
<x-forms.button onclick="composeModal.close()" type="submit">
Save
</x-forms.button>
</form>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>

View File

@@ -1,6 +1,5 @@
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.checkStatus" wire:poll.10000ms="checkStatus"> <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.checkStatus" wire:poll.10000ms="checkStatus">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" /> <livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<livewire:project.service.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
<div class="flex h-full pt-6"> <div class="flex h-full pt-6">
<div class="flex flex-col items-start gap-4 min-w-fit"> <div class="flex flex-col items-start gap-4 min-w-fit">
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a> <a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
@@ -8,6 +7,10 @@
@click.prevent="activeTab = 'service-stack'; @click.prevent="activeTab = 'service-stack';
window.location.hash = 'service-stack'" window.location.hash = 'service-stack'"
href="#">Service Stack</a> href="#">Service Stack</a>
<a :class="activeTab === 'execute-command' && 'text-white'"
@click.prevent="activeTab = 'execute-command';
window.location.hash = 'execute-command'"
href="#">Execute Command</a>
<a :class="activeTab === 'storages' && 'text-white'" <a :class="activeTab === 'storages' && 'text-white'"
@click.prevent="activeTab = 'storages'; @click.prevent="activeTab = 'storages';
window.location.hash = 'storages'" window.location.hash = 'storages'"
@@ -113,6 +116,9 @@
<div x-cloak x-show="activeTab === 'webhooks'"> <div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$service" /> <livewire:project.shared.webhooks :resource="$service" />
</div> </div>
<div x-cloak x-show="activeTab === 'execute-command'">
<livewire:project.shared.execute-container-command :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
<div x-cloak x-show="activeTab === 'environment-variables'"> <div x-cloak x-show="activeTab === 'environment-variables'">
<livewire:project.shared.environment-variable.all :resource="$service" /> <livewire:project.shared.environment-variable.all :resource="$service" />

View File

@@ -5,7 +5,9 @@
<div>Configuration</div> <div>Configuration</div>
</div> </div>
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
<x-forms.button class="w-64" onclick="composeModal.showModal()">Edit Compose <x-forms.button class="w-64"
onclick="Livewire.emit('openModal', 'modals.edit-compose',{{ json_encode(['serviceId' => $service->id]) }})">Edit
Compose
File</x-forms.button> File</x-forms.button>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">

View File

@@ -7,7 +7,8 @@
server <span class="px-1 text-warning">{{ data_get($resource, 'destination.server.name') }}</span> server <span class="px-1 text-warning">{{ data_get($resource, 'destination.server.name') }}</span>
in <span class="px-1 text-warning"> {{ data_get($resource, 'destination.network') }} </span> network.</a> in <span class="px-1 text-warning"> {{ data_get($resource, 'destination.network') }} </span> network.</a>
</div> </div>
{{-- {{$resource->additional_destinations}} --}} {{-- Additonal Destinations:
{{$resource->additional_destinations}} --}}
{{-- @if (count($servers) > 0) {{-- @if (count($servers) > 0)
<div> <div>
<h3>Additional Servers</h3> <h3>Additional Servers</h3>

View File

@@ -5,7 +5,7 @@
class="font-bold text-warning">({{ $env->key }})</span>?</p> class="font-bold text-warning">({{ $env->key }})</span>?</p>
</x-slot:modalBody> </x-slot:modalBody>
</x-modal> </x-modal>
<form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row"> <form wire:submit.prevent='submit' class="flex flex-col gap-2 p-4 m-2 border lg:items-center border-coolgray-300 lg:m-0 lg:p-0 lg:border-0 lg:flex-row">
@if ($isLocked) @if ($isLocked)
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="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"> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">

View File

@@ -0,0 +1,31 @@
<div>
@if ($type === 'application')
<h1>Execute Command</h1>
<livewire:project.application.heading :application="$resource" />
@elseif ($type === 'database')
<h1>Execute Command</h1>
<livewire:project.database.heading :database="$resource" />
@elseif ($type === 'service')
<h2>Execute Command</h2>
@endif
@if (count($containers) > 0)
<form class="flex flex-col gap-2 pt-4" wire:submit.prevent='runCommand'>
<div class="flex gap-2">
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
<x-forms.input id="workDir" label="Working directory" />
</div>
<x-forms.select label="Container" id="container" required>
<option disabled selected>Select container</option>
@foreach ($containers as $container)
<option value="{{ $container }}">{{ $container }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Run</x-forms.button>
</form>
@else
<div class="pt-4">No containers are not running.</div>
@endif
<div class="container w-full pt-10 mx-auto">
<livewire:activity-monitor header="Command output" />
</div>
</div>

View File

@@ -7,7 +7,7 @@
@if ($loop->first) @if ($loop->first)
<h2 class="pb-4">Logs</h2> <h2 class="pb-4">Logs</h2>
@endif @endif
<livewire:project.shared.get-logs :server="$server" :container="$container" /> <livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
@empty @empty
<div>No containers are not running.</div> <div>No containers are not running.</div>
@endforelse @endforelse
@@ -16,7 +16,7 @@
<h1>Logs</h1> <h1>Logs</h1>
<livewire:project.database.heading :database="$resource" /> <livewire:project.database.heading :database="$resource" />
<div class="pt-4"> <div class="pt-4">
<livewire:project.shared.get-logs :server="$server" :container="$container" /> <livewire:project.shared.get-logs :resource="$resource" :server="$server" :container="$container" />
</div> </div>
@elseif ($type === 'service') @elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" /> <livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
@@ -28,7 +28,7 @@
</a> </a>
</div> </div>
<div class="flex-1 pl-8"> <div class="flex-1 pl-8">
<livewire:project.shared.get-logs :server="$server" :container="$container" /> <livewire:project.shared.get-logs :server="$server" :resource="$resource" :servicesubtype="$serviceSubType" :container="$container" />
</div> </div>
</div> </div>
@endif @endif

View File

@@ -9,33 +9,36 @@
helper="See details in our <a target='_blank' class='text-white underline' href='https://coolify.io/docs/api-authentication'>documentation</a>." helper="See details in our <a target='_blank' class='text-white underline' href='https://coolify.io/docs/api-authentication'>documentation</a>."
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input> label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
</div> </div>
<div> @if ($resource->type() !== 'service')
<h3>Manual Git Webhooks</h3> <div>
@if ($githubManualWebhook && $gitlabManualWebhook) <h3>Manual Git Webhooks</h3>
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2"> @if ($githubManualWebhook && $gitlabManualWebhook)
<div class="flex items-end gap-2"> <form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded." <div class="flex items-end gap-2">
readonly label="GitHub" id="githubManualWebhook"></x-forms.input> <x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
<x-forms.input type="password" readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub." <x-forms.input type="password"
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input> helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
</div>
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
<x-forms.button>Webhook Configuration on GitHub
<x-external-link />
</x-forms.button>
</a>
<div class="flex gap-2">
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
</div>
<x-forms.button type="submit">Save</x-forms.button>
</form>
@else
You are using an official Git App. You do not need manual webhooks.
@endif
</div>
@endif
</div>
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
<x-forms.button>Webhook Configuration on GitHub
<x-external-link />
</x-forms.button>
</a>
<div class="flex gap-2">
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
</div>
<x-forms.button type="submit">Save</x-forms.button>
</form>
@else
You are using an official Git App. You do not need manual webhooks.
@endif
</div>
</div> </div>

View File

@@ -25,10 +25,17 @@
@if ($loop->first) @if ($loop->first)
<h3 class="pt-4">Defined resources</h3> <h3 class="pt-4">Defined resources</h3>
@endif @endif
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}"> @if ($resource->link())
<div class="w-64">{{ str($resource->type())->headline() }}</div> <a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
<div>{{ $resource->name }}</div> <div class="w-64">{{ str($resource->type())->headline() }}</div>
</a> <div>{{ $resource->name }}</div>
</a>
@else
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</div>
@endif
@empty @empty
@endforelse @endforelse
</div> </div>
@@ -38,10 +45,17 @@
@if ($loop->first) @if ($loop->first)
<h3 class="pt-4">Defined resources</h3> <h3 class="pt-4">Defined resources</h3>
@endif @endif
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}"> @if ($resource->link())
<div class="w-64">{{ str($resource->type())->headline() }}</div> <a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
<div>{{ $resource->name }}</div> <div class="w-64">{{ str($resource->type())->headline() }}</div>
</a> <div>{{ $resource->name }}</div>
</a>
@else
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</div>
@endif
@empty @empty
@endforelse @endforelse
</div> </div>

View File

@@ -62,6 +62,25 @@
</x-forms.button> </x-forms.button>
</div> </div>
</form> --}} </form> --}}
<h3>Custom FluentBit configuration</h3>
<div class="w-32">
<x-forms.checkbox instantSave='instantSave("custom")' id="server.settings.is_logdrain_custom_enabled"
label="Enabled" />
</div>
<form wire:submit.prevent='submit("custom")' class="flex flex-col">
<div class="flex flex-col gap-4">
<x-forms.textarea rows="6" required id="server.settings.logdrain_custom_config"
label="Custom FluentBit Configuration" />
<x-forms.textarea id="server.settings.logdrain_custom_config_parser"
label="Custom Parser Configuration" />
</div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">
Save
</x-forms.button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<div> <div>
@if ($server->isFunctional()) @if ($server->isFunctional())
<div class="flex gap-2" wire:poll.5000ms='checkProxy'> <div class="flex gap-2">
@if (data_get($server, 'proxy.status') === 'running') @if (data_get($server, 'proxy.status') === 'running')
<x-status.running status="Proxy Running" /> <x-status.running status="Proxy Running" />
@elseif (data_get($server, 'proxy.status') === 'restarting') @elseif (data_get($server, 'proxy.status') === 'restarting')

View File

@@ -0,0 +1,57 @@
<div>
@isset($jsPath)
<script>{!! file_get_contents($jsPath) !!}</script>
@endisset
@isset($cssPath)
<style>{!! file_get_contents($cssPath) !!}</style>
@endisset
<div
x-data="LivewireUIModal()"
x-init="init()"
x-on:close.stop="setShowPropertyTo(false)"
x-on:keydown.escape.window="closeModalOnEscape()"
x-show="show"
class="fixed inset-0 z-40 overflow-y-auto"
style="display: none;"
>
<div class="flex items-end justify-center px-4 pt-4 pb-10 text-center sm:block sm:p-0">
<div
x-show="show"
x-on:click="closeModalOnClickAway()"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 transition-all transform"
>
<div class="absolute inset-0 bg-black opacity-70"></div>
</div>
{{-- <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span> --}}
<div
x-show="show && showActiveComponent"
x-transition:enter="ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="w-full overflow-hidden text-left align-bottom transition-all transform sm:my-8 sm:align-middle sm:w-full"
id="modal-container"
x-trap.noscroll.inert="show && showActiveComponent"
aria-modal="true"
>
@forelse($components as $id => $component)
<div class="sm:mx-20" x-show.immediate="activeComponent == '{{ $id }}'" x-ref="{{ $id }}" wire:key="{{ $id }}">
@livewire($component['name'], $component['attributes'], key($id))
</div>
@empty
@endforelse
</div>
</div>
</div>
</div>

View File

@@ -11,6 +11,13 @@
| |
*/ */
// Broadcast::channel('App.Models.User.{id}', function ($user, $id) { use App\Models\Application;
// return (int) $user->id === (int) $id; use App\Models\User;
// }); use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('custom.{teamId}', function (User $user, int $teamId) {
if ($user->teams->pluck('id')->contains($teamId)) {
return true;
}
return false;
});

View File

@@ -12,6 +12,7 @@ use App\Http\Livewire\Project\Service\Show as ServiceShow;
use App\Http\Livewire\Dev\Compose as Compose; use App\Http\Livewire\Dev\Compose as Compose;
use App\Http\Livewire\Dashboard; use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Project\CloneProject; use App\Http\Livewire\Project\CloneProject;
use App\Http\Livewire\Project\Shared\ExecuteContainerCommand;
use App\Http\Livewire\Project\Shared\Logs; use App\Http\Livewire\Project\Shared\Logs;
use App\Http\Livewire\Security\ApiTokens; use App\Http\Livewire\Security\ApiTokens;
use App\Http\Livewire\Server\All; use App\Http\Livewire\Server\All;
@@ -42,6 +43,16 @@ use Laravel\Fortify\Fortify;
if (isDev()) { if (isDev()) {
Route::get('/dev/compose', Compose::class)->name('dev.compose'); Route::get('/dev/compose', Compose::class)->name('dev.compose');
} }
Route::get('/api/v1/test/realtime', function () {
if (auth()->user()?->currentTeam()->id !== 0) {
return redirect('/');
}
event(new \App\Events\TestEvent());
return 'Look at your other tab.';
})->middleware('auth');
Route::post('/forgot-password', function (Request $request) { Route::post('/forgot-password', function (Request $request) {
if (is_transactional_emails_active()) { if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email()); $arrayOfRequest = $request->only(Fortify::email());
@@ -111,18 +122,21 @@ Route::middleware(['auth', 'verified'])->group(function () {
)->name('project.application.deployment'); )->name('project.application.deployment');
Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/logs', Logs::class)->name('project.application.logs'); Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/logs', Logs::class)->name('project.application.logs');
Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/command', ExecuteContainerCommand::class)->name('project.application.command');
// Databases // Databases
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration'); Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups', [DatabaseController::class, 'backups'])->name('project.database.backups.all'); Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups', [DatabaseController::class, 'backups'])->name('project.database.backups.all');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups/{backup_uuid}', [DatabaseController::class, 'executions'])->name('project.database.backups.executions'); Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups/{backup_uuid}', [DatabaseController::class, 'executions'])->name('project.database.backups.executions');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/logs', Logs::class)->name('project.database.logs'); Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/logs', Logs::class)->name('project.database.logs');
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/command', ExecuteContainerCommand::class)->name('project.database.command');
// Services // Services
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service.configuration'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service.configuration');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs'); Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/command', ExecuteContainerCommand::class)->name('project.service.command');
}); });
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {

View File

@@ -59,15 +59,21 @@ sles | opensuse-leap | opensuse-tumbleweed)
esac esac
if ! [ -x "$(command -v docker)" ]; then if ! [ -x "$(command -v docker)" ]; then
echo "Docker is not installed. Installing Docker..." echo "Docker is not installed. Installing Docker."
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully." echo "Docker installed successfully."
else else
echo "Docker installation failed." echo "Docker installation failed with Rancher script. Trying with official script."
echo "Maybe your OS is not supported." curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." if [ -x "$(command -v docker)" ]; then
exit 1 echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported."
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
fi fi
fi fi
echo -e "-------------" echo -e "-------------"
@@ -131,6 +137,9 @@ if [ ! -f /data/coolify/source/.env ]; then
sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env sed -i "s|APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env sed -i "s|DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi fi
# Merge .env and .env.production. New values will be added to .env # Merge .env and .env.production. New values will be added to .env

View File

@@ -1,11 +1,7 @@
#!/bin/bash #!/bin/bash
## Do not modify this file. You will lost the ability to autoupdate! ## Do not modify this file. You will lost the ability to autoupdate!
########### VERSION="1.0.4"
## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" if you update this file.
###########
VERSION="1.0.1"
CDN="https://cdn.coollabs.io/coolify" CDN="https://cdn.coollabs.io/coolify"
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
@@ -13,10 +9,28 @@ curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.p
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
# Merge .env and .env.production. New values will be added to .env # Merge .env and .env.production. New values will be added to .env
sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' >/data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env
# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env
if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
if grep -q "PUSHER_APP_KEY=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
if grep -q "PUSHER_APP_SECRET=$" /data/coolify/source/.env; then
sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env
fi
# Make sure coolify network exists # Make sure coolify network exists
docker network create --attachable coolify 2>/dev/null docker network create --attachable coolify 2>/dev/null
# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null # docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate" if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
echo "docker-compose.custom.yml detected."
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --pull always --remove-orphans --force-recreate"
else
docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate"
fi

View File

@@ -1,6 +1,8 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: [ content: [
'./vendor/wire-elements/modal/resources/views/*.blade.php',
'./storage/framework/views/*.php',
"./resources/**/*.blade.php", "./resources/**/*.blade.php",
"./app/**/*.php", "./app/**/*.php",
"./resources/**/*.js", "./resources/**/*.js",

View File

@@ -24,7 +24,7 @@ services:
- REDIS_PORT=6379 - REDIS_PORT=6379
- WEBSOCKETS_ENABLED=true - WEBSOCKETS_ENABLED=true
postgresql: postgresql:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- directus-postgresql-data:/var/lib/postgresql/data - directus-postgresql-data:/var/lib/postgresql/data
environment: environment:

View File

@@ -30,7 +30,7 @@ services:
retries: 15 retries: 15
postgresql: postgresql:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- gitea-postgresql-data:/var/lib/postgresql/data - gitea-postgresql-data:/var/lib/postgresql/data
environment: environment:

View File

@@ -25,7 +25,7 @@ services:
depends_on: depends_on:
- postgresql - postgresql
postgresql: postgresql:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- postgresql-data:/var/lib/postgresql/data - postgresql-data:/var/lib/postgresql/data
environment: environment:

View File

@@ -18,7 +18,7 @@ services:
depends_on: depends_on:
- postgres - postgres
postgres: postgres:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- postgresql-data:/var/lib/postgresql/data - postgresql-data:/var/lib/postgresql/data
environment: environment:

View File

@@ -0,0 +1,19 @@
# documentation: https://www.meilisearch.com/docs/learn/configuration/instance_options
# slogan: MeiliSearch is a powerful, fast, open-source, easy to use and deploy search engine.
# tags: search,engine,fulltext,full,text,meilisearch
services:
meilisearch:
image: getmeili/meilisearch:latest
environment:
- SERVICE_FQDN_MEILISEARCH
- MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-true}
- MEILI_ENV=${MEILI_ENV:-production}
- MEILI_MASTER_KEY=${SERVICE_PASSWORD_MEILISEARCH}
volumes:
- meilisearch-data:/meili_data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
interval: 2s
timeout: 10s
retries: 15

View File

@@ -24,7 +24,7 @@ services:
depends_on: depends_on:
- postgresql - postgresql
postgresql: postgresql:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- postgresql-data:/var/lib/postgresql/data - postgresql-data:/var/lib/postgresql/data
environment: environment:

View File

@@ -21,7 +21,7 @@ services:
postgresql: postgresql:
condition: service_healthy condition: service_healthy
postgresql: postgresql:
image: postgres:15-alpine image: postgres:16-alpine
volumes: volumes:
- postgresql-data:/var/lib/postgresql/data - postgresql-data:/var/lib/postgresql/data
environment: environment:

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