mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-06 20:52:09 +00:00
Compare commits
114 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ffebd71f3 | ||
|
|
2f286a6595 | ||
|
|
13701f6030 | ||
|
|
91f224ddea | ||
|
|
8cffae10b2 | ||
|
|
1158b2f4db | ||
|
|
f542bcf428 | ||
|
|
22178df8ae | ||
|
|
acfe1daf9b | ||
|
|
fb3f71881f | ||
|
|
0f7546a4dc | ||
|
|
6ccafacc87 | ||
|
|
852c2df12e | ||
|
|
4c752951ab | ||
|
|
f32191b889 | ||
|
|
31251ef6cb | ||
|
|
e9365aa09b | ||
|
|
e5c860319f | ||
|
|
ceedd5225f | ||
|
|
61efdfb7c1 | ||
|
|
fcef5d902e | ||
|
|
2073b8949b | ||
|
|
5c59a752e3 | ||
|
|
caf6e3a23e | ||
|
|
8eb1c4da46 | ||
|
|
bab2f391ed | ||
|
|
131c6df82a | ||
|
|
9f44d0c47a | ||
|
|
dff7ed5b7b | ||
|
|
54b6472f3b | ||
|
|
04a36e5c90 | ||
|
|
b2f851272b | ||
|
|
778915599f | ||
|
|
a6f9527157 | ||
|
|
db976709c7 | ||
|
|
7e4947ba07 | ||
|
|
e2578a7dd0 | ||
|
|
7d028b15f5 | ||
|
|
d240bfda8b | ||
|
|
d59ec2548a | ||
|
|
e0cefc787a | ||
|
|
0496198d0f | ||
|
|
cbf7f1fa41 | ||
|
|
66587d864a | ||
|
|
b106705c8d | ||
|
|
5cc22a8271 | ||
|
|
cd22863a8c | ||
|
|
118a02f70d | ||
|
|
862177d61a | ||
|
|
41280aa780 | ||
|
|
3a987c8a6e | ||
|
|
29ca461a9a | ||
|
|
d48b72c160 | ||
|
|
ce49f26c53 | ||
|
|
02989678be | ||
|
|
810b55163c | ||
|
|
ac13ba0957 | ||
|
|
a3e088199f | ||
|
|
42ee4ca032 | ||
|
|
17deff4d86 | ||
|
|
8b6323b906 | ||
|
|
a696b5271a | ||
|
|
31959f26d4 | ||
|
|
45d2f80f69 | ||
|
|
53975fcf61 | ||
|
|
76296c1f19 | ||
|
|
c25baf69e1 | ||
|
|
f952512615 | ||
|
|
c6557eada8 | ||
|
|
2c2d74c0d6 | ||
|
|
028a2eb275 | ||
|
|
ce7fad5bef | ||
|
|
cd7852e4f9 | ||
|
|
7b022a2482 | ||
|
|
12d9b6538b | ||
|
|
335788c2d6 | ||
|
|
2352e4a71d | ||
|
|
dc03179bd1 | ||
|
|
cc72f416e8 | ||
|
|
3b67d0a8de | ||
|
|
0135ba7e89 | ||
|
|
a28a28cd23 | ||
|
|
b52680a2d8 | ||
|
|
0670e6c1d6 | ||
|
|
c3882b75c1 | ||
|
|
64b6f86a36 | ||
|
|
b9efc22253 | ||
|
|
e3d9eb0154 | ||
|
|
66f3967479 | ||
|
|
c54439e84c | ||
|
|
db4a4c74fc | ||
|
|
5c7ef80219 | ||
|
|
243d1c06fc | ||
|
|
ef25f7d800 | ||
|
|
45640ffdb1 | ||
|
|
378291b209 | ||
|
|
3583e552f1 | ||
|
|
d7dfeaf988 | ||
|
|
7fe5eca661 | ||
|
|
0dff57e69f | ||
|
|
f4803ad58b | ||
|
|
2d7bbbe300 | ||
|
|
928b68043b | ||
|
|
b21add0210 | ||
|
|
c41ffd6bfb | ||
|
|
b4874c7df3 | ||
|
|
c505a6ce9c | ||
|
|
706e4b13ee | ||
|
|
4af471ee31 | ||
|
|
87062e4e22 | ||
|
|
500ba0fab8 | ||
|
|
1c72c127d5 | ||
|
|
69bb4ae5ee | ||
|
|
ccb972dcb9 |
@@ -4,3 +4,7 @@ APP_KEY=
|
||||
|
||||
DB_PASSWORD=
|
||||
REDIS_PASSWORD=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
|
||||
@@ -11,29 +11,34 @@ class StopApplication
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$server
|
||||
);
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$server
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: make notification for application
|
||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||
}
|
||||
// TODO: make notification for application
|
||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||
}
|
||||
// Delete Preview Deployments
|
||||
$previewDeployments = $application->previews;
|
||||
foreach ($previewDeployments as $previewDeployment) {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||
// Delete Preview Deployments
|
||||
$previewDeployments = $application->previews;
|
||||
foreach ($previewDeployments as $previewDeployment) {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,35 +17,42 @@ class CheckProxy
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
if ($server->isSwarm()) {
|
||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||
$server->proxy->set('status', $status);
|
||||
$server->save();
|
||||
return false;
|
||||
}
|
||||
$ip = $server->ip;
|
||||
if ($server->id === 0) {
|
||||
$ip = 'host.docker.internal';
|
||||
}
|
||||
} else {
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->save();
|
||||
return false;
|
||||
}
|
||||
$ip = $server->ip;
|
||||
if ($server->id === 0) {
|
||||
$ip = 'host.docker.internal';
|
||||
}
|
||||
|
||||
$connection80 = @fsockopen($ip, '80');
|
||||
$connection443 = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection80) && fclose($connection80);
|
||||
$port443 = is_resource($connection443) && fclose($connection443);
|
||||
if ($port80) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
$connection80 = @fsockopen($ip, '80');
|
||||
$connection443 = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection80) && fclose($connection80);
|
||||
$port443 = is_resource($connection443) && fclose($connection443);
|
||||
if ($port80) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($port443) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
if ($port443) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ class StartProxy
|
||||
public function handle(Server $server, bool $async = true): string|Activity
|
||||
{
|
||||
try {
|
||||
|
||||
$proxyType = $server->proxyType();
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
@@ -24,18 +25,29 @@ class StartProxy
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo 'Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
} else {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo 'Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
}
|
||||
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
@@ -46,11 +58,9 @@ class StartProxy
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
} catch(\Throwable $e) {
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class InstallDocker
|
||||
}
|
||||
$command = $command->merge([
|
||||
"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)...'",
|
||||
"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",
|
||||
@@ -76,10 +76,20 @@ class InstallDocker
|
||||
"echo 'Restarting Docker Engine...'",
|
||||
"systemctl enable docker >/dev/null 2>&1 || true",
|
||||
"systemctl restart docker",
|
||||
"echo 'Creating default Docker network (coolify)...'",
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
"echo 'Done!'"
|
||||
]);
|
||||
if ($server->isSwarm()) {
|
||||
$command = $command->merge([
|
||||
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
|
||||
]);
|
||||
} else {
|
||||
$command = $command->merge([
|
||||
"docker network create --attachable coolify >/dev/null 2>&1 || true",
|
||||
]);
|
||||
$command = $command->merge([
|
||||
"echo 'Done!'",
|
||||
]);
|
||||
}
|
||||
|
||||
return remote_process($command, $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ class InstallLogDrain
|
||||
$type = 'highlight';
|
||||
} else if ($server->settings->is_logdrain_axiom_enabled) {
|
||||
$type = 'axiom';
|
||||
} else if ($server->settings->is_logdrain_custom_enabled) {
|
||||
$type = 'custom';
|
||||
} else {
|
||||
$type = 'none';
|
||||
}
|
||||
@@ -114,15 +116,23 @@ class InstallLogDrain
|
||||
json_date_format iso8601
|
||||
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 {
|
||||
throw new \Exception('Unknown log drain type.');
|
||||
}
|
||||
$parsers = base64_encode("
|
||||
if ($type !== 'custom') {
|
||||
$parsers = base64_encode("
|
||||
[PARSER]
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
");
|
||||
}
|
||||
$compose = base64_encode("
|
||||
services:
|
||||
coolify-log-drain:
|
||||
@@ -179,6 +189,12 @@ Files:
|
||||
"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",
|
||||
];
|
||||
} else if ($type === 'custom') {
|
||||
$add_envs_command = [
|
||||
"touch $config_path/.env"
|
||||
];
|
||||
} else {
|
||||
throw new \Exception('Unknown log drain type.');
|
||||
}
|
||||
$restart_command = [
|
||||
"echo 'Stopping old Fluent Bit'",
|
||||
|
||||
@@ -21,7 +21,7 @@ class StartService
|
||||
$commands[] = "echo 'Pulling images.'";
|
||||
$commands[] = "docker compose pull";
|
||||
$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";
|
||||
$compose = data_get($service,'docker_compose',[]);
|
||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||
|
||||
@@ -30,20 +30,21 @@ class Init extends Command
|
||||
$this->alive();
|
||||
$cleanup = $this->option('cleanup');
|
||||
if ($cleanup) {
|
||||
echo "Running cleanup\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
$this->cleanup_ssh();
|
||||
// $this->cleanup_ssh();
|
||||
}
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
}
|
||||
private function cleanup_stucked_helper_containers() {
|
||||
private function cleanup_stucked_helper_containers()
|
||||
{
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
if ($server->isFunctional()) {
|
||||
CleanupHelperContainersJob::dispatch($server);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private function alive()
|
||||
{
|
||||
@@ -62,21 +63,23 @@ class Init extends Command
|
||||
echo "Error in alive: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
private function cleanup_ssh()
|
||||
{
|
||||
try {
|
||||
$files = Storage::allFiles('ssh/keys');
|
||||
foreach ($files as $file) {
|
||||
Storage::delete($file);
|
||||
}
|
||||
$files = Storage::allFiles('ssh/mux');
|
||||
foreach ($files as $file) {
|
||||
Storage::delete($file);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
// private function cleanup_ssh()
|
||||
// {
|
||||
|
||||
// TODO: it will cleanup id.root@host.docker.internal
|
||||
// try {
|
||||
// $files = Storage::allFiles('ssh/keys');
|
||||
// foreach ($files as $file) {
|
||||
// Storage::delete($file);
|
||||
// }
|
||||
// $files = Storage::allFiles('ssh/mux');
|
||||
// foreach ($files as $file) {
|
||||
// Storage::delete($file);
|
||||
// }
|
||||
// } catch (\Throwable $e) {
|
||||
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
|
||||
// }
|
||||
// }
|
||||
private function cleanup_in_progress_application_deployments()
|
||||
{
|
||||
// Cleanup any failed deployments
|
||||
@@ -98,15 +101,15 @@ class Init extends Command
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
ray('Application without environment', $application->name);
|
||||
$application->delete();
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
ray('Application without server', $application->name);
|
||||
echo 'Application without environment' . $application->name . 'deleting\n';
|
||||
$application->delete();
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
ray('Application without destination', $application->name);
|
||||
echo 'Application without destination' . $application->name . 'deleting\n';
|
||||
$application->delete();
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server' . $application->name . 'deleting\n';
|
||||
$application->delete();
|
||||
}
|
||||
}
|
||||
@@ -117,15 +120,15 @@ class Init extends Command
|
||||
$postgresqls = StandalonePostgresql::all();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
ray('Postgresql without environment', $postgresql->name);
|
||||
$postgresql->delete();
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
ray('Postgresql without server', $postgresql->name);
|
||||
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
|
||||
$postgresql->delete();
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
ray('Postgresql without destination', $postgresql->name);
|
||||
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
|
||||
$postgresql->delete();
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
|
||||
$postgresql->delete();
|
||||
}
|
||||
}
|
||||
@@ -136,15 +139,15 @@ class Init extends Command
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
ray('Redis without environment', $redis->name);
|
||||
$redis->delete();
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
ray('Redis without server', $redis->name);
|
||||
echo 'Redis without environment' . $redis->name . 'deleting\n';
|
||||
$redis->delete();
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
ray('Redis without destination', $redis->name);
|
||||
echo 'Redis without destination' . $redis->name . 'deleting\n';
|
||||
$redis->delete();
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server' . $redis->name . 'deleting\n';
|
||||
$redis->delete();
|
||||
}
|
||||
}
|
||||
@@ -156,15 +159,15 @@ class Init extends Command
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
ray('Mongodb without environment', $mongodb->name);
|
||||
$mongodb->delete();
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
ray('Mongodb without server', $mongodb->name);
|
||||
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
|
||||
$mongodb->delete();
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
ray('Mongodb without destination', $mongodb->name);
|
||||
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
|
||||
$mongodb->delete();
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
|
||||
$mongodb->delete();
|
||||
}
|
||||
}
|
||||
@@ -176,15 +179,15 @@ class Init extends Command
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
ray('Mysql without environment', $mysql->name);
|
||||
$mysql->delete();
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
ray('Mysql without server', $mysql->name);
|
||||
echo 'Mysql without environment' . $mysql->name . 'deleting\n';
|
||||
$mysql->delete();
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
ray('Mysql without destination', $mysql->name);
|
||||
echo 'Mysql without destination' . $mysql->name . 'deleting\n';
|
||||
$mysql->delete();
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server' . $mysql->name . 'deleting\n';
|
||||
$mysql->delete();
|
||||
}
|
||||
}
|
||||
@@ -196,15 +199,15 @@ class Init extends Command
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
ray('Mariadb without environment', $mariadb->name);
|
||||
$mariadb->delete();
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
ray('Mariadb without server', $mariadb->name);
|
||||
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
|
||||
$mariadb->delete();
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
ray('Mariadb without destination', $mariadb->name);
|
||||
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
|
||||
$mariadb->delete();
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
|
||||
$mariadb->delete();
|
||||
}
|
||||
}
|
||||
@@ -216,15 +219,15 @@ class Init extends Command
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
ray('Service without environment', $service->name);
|
||||
$service->delete();
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
ray('Service without server', $service->name);
|
||||
echo 'Service without environment' . $service->name . 'deleting\n';
|
||||
$service->delete();
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
ray('Service without destination', $service->name);
|
||||
echo 'Service without destination' . $service->name . 'deleting\n';
|
||||
$service->delete();
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server' . $service->name . 'deleting\n';
|
||||
$service->delete();
|
||||
}
|
||||
}
|
||||
@@ -235,7 +238,7 @@ class Init extends Command
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
ray('ServiceApplication without service', $service->name);
|
||||
echo 'ServiceApplication without service' . $service->name . 'deleting\n';
|
||||
$service->delete();
|
||||
}
|
||||
}
|
||||
@@ -246,7 +249,7 @@ class Init extends Command
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
ray('ServiceDatabase without service', $service->name);
|
||||
echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
|
||||
$service->delete();
|
||||
}
|
||||
}
|
||||
|
||||
28
app/Events/TestEvent.php
Normal file
28
app/Events/TestEvent.php
Normal 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}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ class Index extends Component
|
||||
public ?string $remoteServerHost = null;
|
||||
public ?int $remoteServerPort = 22;
|
||||
public ?string $remoteServerUser = 'root';
|
||||
public bool $isSwarmManager = false;
|
||||
public bool $isCloudflareTunnel = false;
|
||||
public ?Server $createdServer = null;
|
||||
|
||||
public Collection $projects;
|
||||
@@ -182,7 +184,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'private_key_id' => $this->createdPrivateKey->id,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->createdServer->save();
|
||||
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||
$this->createdServer->settings->save();
|
||||
$this->createdServer->addInitialNetwork();
|
||||
$this->validateServer();
|
||||
}
|
||||
public function validateServer()
|
||||
@@ -197,6 +202,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->serverReachable = false;
|
||||
$this->createdServer->delete();
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ class Dashboard extends Component
|
||||
{
|
||||
public $projects = [];
|
||||
public $servers = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
|
||||
29
app/Http/Livewire/Modal/EditCompose.php
Normal file
29
app/Http/Livewire/Modal/EditCompose.php
Normal 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();
|
||||
}
|
||||
}
|
||||
110
app/Http/Livewire/Project/Application/Command.php
Normal file
110
app/Http/Livewire/Project/Application/Command.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class Configuration extends Component
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->application = $application;
|
||||
$mainServer = $application->destination->server;
|
||||
$mainServer = $this->application->destination->server;
|
||||
$servers = Server::ownedByCurrentTeam()->get();
|
||||
$this->servers = $servers->filter(function ($server) use ($mainServer) {
|
||||
return $server->id != $mainServer->id;
|
||||
|
||||
@@ -18,7 +18,6 @@ class DeploymentNavbar extends Component
|
||||
public Server $server;
|
||||
public bool $is_debug_enabled = false;
|
||||
protected $listeners = ['deploymentFinished'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
|
||||
@@ -29,8 +29,6 @@ class General extends Component
|
||||
public ?string $initialDockerComposeLocation = null;
|
||||
public ?string $initialDockerComposePrLocation = null;
|
||||
|
||||
public bool $is_static;
|
||||
|
||||
public $parsedServices = [];
|
||||
public $parsedServiceDomains = [];
|
||||
|
||||
@@ -124,6 +122,10 @@ class General extends Component
|
||||
{
|
||||
$this->application->settings->save();
|
||||
$this->emit('success', 'Settings saved.');
|
||||
$this->application->refresh();
|
||||
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
||||
$this->resetDefaultLabels(false);
|
||||
}
|
||||
}
|
||||
public function loadComposeFile($isInit = false)
|
||||
{
|
||||
@@ -156,7 +158,7 @@ class General extends Component
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
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();
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
|
||||
@@ -39,6 +39,26 @@ class Heading extends Component
|
||||
$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)
|
||||
{
|
||||
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->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()
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
|
||||
@@ -22,79 +22,19 @@ class DockerCompose extends Component
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
$this->dockerComposeRaw = 'services:
|
||||
ghost:
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ~/configs:/etc/configs/:ro
|
||||
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
|
||||
- /var/lib/ghost/content:/tmp/ghost/content:rw
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
- type: volume
|
||||
source: mydata
|
||||
target: /data
|
||||
- type: bind
|
||||
source: ./var/lib/ghost/data
|
||||
target: /data
|
||||
- type: bind
|
||||
source: /tmp
|
||||
target: /tmp
|
||||
labels:
|
||||
- "test.label=true"
|
||||
ports:
|
||||
- "3000"
|
||||
- "3000-3005"
|
||||
- "8000:8000"
|
||||
- "9090-9091:8080-8081"
|
||||
- "49100:22"
|
||||
- "127.0.0.1:8001:8001"
|
||||
- "127.0.0.1:5000-5010:5000-5010"
|
||||
- "127.0.0.1::5000"
|
||||
- "6060:6060/udp"
|
||||
- "12400-12500:1240"
|
||||
- target: 80
|
||||
published: 8080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- some-network
|
||||
- other-network
|
||||
appsmith:
|
||||
build:
|
||||
context: .
|
||||
dockerfile_inline: |
|
||||
FROM nginx
|
||||
ARG GIT_COMMIT
|
||||
ARG GIT_BRANCH
|
||||
RUN echo "Hello World ${GIT_COMMIT} ${GIT_BRANCH}"
|
||||
args:
|
||||
- GIT_COMMIT=cdc3b19
|
||||
- GIT_BRANCH=${GIT_BRANCH}
|
||||
environment:
|
||||
- database__client=${DATABASE_CLIENT:-mysql}
|
||||
- database__connection__database=${MYSQL_DATABASE:-ghost}
|
||||
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
|
||||
- test=${TEST:?true}
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=$MYSQL_DATABASE
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
||||
- SESSION_SECRET
|
||||
minio:
|
||||
image: minio/minio
|
||||
environment:
|
||||
RACK_ENV: development
|
||||
A: $A
|
||||
SHOW: ${SHOW}
|
||||
SHOW1: ${SHOW2-show1}
|
||||
SHOW2: ${SHOW3:-show2}
|
||||
SHOW3: ${SHOW4?show3}
|
||||
SHOW4: ${SHOW5:?show4}
|
||||
SHOW5: ${SERVICE_USER_MINIO}
|
||||
SHOW6: ${SERVICE_PASSWORD_MINIO}
|
||||
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
|
||||
SHOW8: ${SERVICE_BASE64_64_MINIO}
|
||||
SHOW9: ${SERVICE_BASE64_128_MINIO}
|
||||
SHOW10: ${SERVICE_BASE64_MINIO}
|
||||
SHOW11:
|
||||
- APPSMITH_MAIL_ENABLED=${APPSMITH_MAIL_ENABLED}
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class Index extends Component
|
||||
}
|
||||
public function checkStatus()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
dispatch(new ContainerStatusJob($this->service->server));
|
||||
$this->refreshStacks();
|
||||
}
|
||||
public function refreshStacks()
|
||||
|
||||
@@ -59,7 +59,6 @@ class Show extends Component
|
||||
{
|
||||
$this->validate();
|
||||
$this->env->save();
|
||||
ray($this->env);
|
||||
$this->emit('success', 'Environment variable updated successfully.');
|
||||
$this->emit('refreshEnvs');
|
||||
}
|
||||
|
||||
110
app/Http/Livewire/Project/Shared/ExecuteContainerCommand.php
Normal file
110
app/Http/Livewire/Project/Shared/ExecuteContainerCommand.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Application;
|
||||
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 Livewire\Component;
|
||||
|
||||
@@ -10,17 +19,44 @@ class GetLogs extends Component
|
||||
{
|
||||
public string $outputs = '';
|
||||
public string $errors = '';
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public ?bool $streamLogs = false;
|
||||
public ?bool $showTimeStamps = true;
|
||||
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)
|
||||
{
|
||||
$this->outputs .= removeAnsiColors($output);
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ class Logs extends Component
|
||||
public $parameters;
|
||||
public $query;
|
||||
public $status;
|
||||
public $serviceSubType;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -64,6 +65,11 @@ class Logs extends Component
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$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->server = $this->resource->server;
|
||||
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
||||
|
||||
@@ -17,14 +17,15 @@ class Form extends Component
|
||||
protected $listeners = ['serverRefresh'];
|
||||
|
||||
protected $rules = [
|
||||
'server.name' => 'required|min:6',
|
||||
'server.name' => 'required',
|
||||
'server.description' => 'nullable',
|
||||
'server.ip' => 'required',
|
||||
'server.user' => 'required',
|
||||
'server.port' => 'required',
|
||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
||||
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||
'server.settings.is_reachable' => 'required',
|
||||
'server.settings.is_part_of_swarm' => 'required',
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
// 'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -34,14 +35,16 @@ class Form extends Component
|
||||
'server.user' => 'User',
|
||||
'server.port' => 'Port',
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
'server.settings.is_reachable' => 'is reachable',
|
||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||
'server.settings.is_reachable' => 'Is reachable',
|
||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||
// 'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
$this->validateServer();
|
||||
}
|
||||
public function serverRefresh($install = true)
|
||||
{
|
||||
@@ -49,9 +52,14 @@ class Form extends Component
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->validateServer();
|
||||
$this->server->settings->save();
|
||||
try {
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->validateServer(false);
|
||||
$this->server->settings->save();
|
||||
$this->emit('success', 'Server updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
@@ -100,6 +108,12 @@ class Form extends Component
|
||||
$install && $this->installDocker();
|
||||
return;
|
||||
}
|
||||
if ($this->server->isSwarm()) {
|
||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||
if ($swarmInstalled) {
|
||||
$install && $this->emit('success', 'Docker Swarm is initiated.');
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
|
||||
@@ -19,6 +19,9 @@ class LogDrains extends Component
|
||||
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
|
||||
'server.settings.logdrain_axiom_dataset_name' => '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 = [
|
||||
'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.logdrain_axiom_dataset_name' => 'Axiom dataset name',
|
||||
'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()
|
||||
@@ -84,6 +90,7 @@ class LogDrains extends Component
|
||||
$this->server->settings->update([
|
||||
'is_logdrain_highlight_enabled' => false,
|
||||
'is_logdrain_axiom_enabled' => false,
|
||||
'is_logdrain_custom_enabled' => false,
|
||||
]);
|
||||
} else if ($type === 'highlight') {
|
||||
$this->validate([
|
||||
@@ -93,6 +100,7 @@ class LogDrains extends Component
|
||||
$this->server->settings->update([
|
||||
'is_logdrain_newrelic_enabled' => false,
|
||||
'is_logdrain_axiom_enabled' => false,
|
||||
'is_logdrain_custom_enabled' => false,
|
||||
]);
|
||||
} else if ($type === 'axiom') {
|
||||
$this->validate([
|
||||
@@ -103,6 +111,18 @@ class LogDrains extends Component
|
||||
$this->server->settings->update([
|
||||
'is_logdrain_newrelic_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();
|
||||
@@ -121,6 +141,10 @@ class LogDrains extends Component
|
||||
$this->server->settings->update([
|
||||
'is_logdrain_axiom_enabled' => false,
|
||||
]);
|
||||
} else if ($type === 'custom') {
|
||||
$this->server->settings->update([
|
||||
'is_logdrain_custom_enabled' => false,
|
||||
]);
|
||||
}
|
||||
handleError($e, $this);
|
||||
return false;
|
||||
|
||||
@@ -21,7 +21,7 @@ class ByIp extends Component
|
||||
public string $ip;
|
||||
public string $user = 'root';
|
||||
public int $port = 22;
|
||||
public bool $is_part_of_swarm = false;
|
||||
public bool $is_swarm_manager = false;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
@@ -29,6 +29,7 @@ class ByIp extends Component
|
||||
'ip' => 'required',
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
'is_swarm_manager' => 'required|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Name',
|
||||
@@ -36,6 +37,7 @@ class ByIp extends Component
|
||||
'ip' => 'IP Address/Domain',
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
'is_swarm_manager' => 'Swarm Manager',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -72,11 +74,11 @@ class ByIp extends Component
|
||||
'proxy' => [
|
||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||
"status" => ProxyStatus::EXITED->value,
|
||||
]
|
||||
|
||||
],
|
||||
]);
|
||||
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
|
||||
$server->settings->is_swarm_manager = $this->is_swarm_manager;
|
||||
$server->settings->save();
|
||||
$server->addInitialNetwork();
|
||||
return redirect()->route('server.show', $server->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -58,11 +58,25 @@ class Deploy extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
instant_remote_process([
|
||||
"docker rm -f coolify-proxy",
|
||||
], $this->server);
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
try {
|
||||
if ($this->server->isSwarm()) {
|
||||
instant_remote_process([
|
||||
"docker service rm coolify-proxy_traefik",
|
||||
], $this->server);
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
} else {
|
||||
instant_remote_process([
|
||||
"docker rm -f coolify-proxy",
|
||||
], $this->server);
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,12 @@ class Status extends Component
|
||||
public int $numberOfPolls = 0;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function mount() {
|
||||
$this->checkProxy();
|
||||
}
|
||||
public function startProxyPolling()
|
||||
{
|
||||
$this->polling = true;
|
||||
$this->checkProxy();
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,17 @@ use Livewire\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()
|
||||
{
|
||||
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
|
||||
|
||||
@@ -11,6 +11,9 @@ class PricingPlans extends Component
|
||||
public bool $isTrial = false;
|
||||
public function mount() {
|
||||
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
|
||||
if (config('constants.limits.trial_period') == 0) {
|
||||
$this->isTrial = false;
|
||||
}
|
||||
}
|
||||
public function subscribeStripe($type)
|
||||
{
|
||||
@@ -63,6 +66,7 @@ class PricingPlans extends Component
|
||||
];
|
||||
|
||||
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
|
||||
if (config('constants.limits.trial_period') > 0) {
|
||||
$payload['subscription_data'] = [
|
||||
'trial_period_days' => config('constants.limits.trial_period'),
|
||||
'trial_settings' => [
|
||||
@@ -71,6 +75,7 @@ class PricingPlans extends Component
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
$payload['payment_method_collection'] = 'if_required';
|
||||
}
|
||||
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
|
||||
|
||||
@@ -156,25 +156,27 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
// Generate custom host<->ip mapping
|
||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||
$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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->addHosts = $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
}
|
||||
$this->addHosts = $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
|
||||
if ($this->application->dockerfile_target_build) {
|
||||
$this->buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
||||
@@ -214,6 +216,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
|
||||
$this->push_to_docker_registry();
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}")
|
||||
],
|
||||
[
|
||||
"echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(true);
|
||||
@@ -290,42 +303,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ray($e);
|
||||
}
|
||||
}
|
||||
// private function deploy_docker_compose()
|
||||
// {
|
||||
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "echo 'Starting deployment of {$this->application->name}.'"
|
||||
// ],
|
||||
// );
|
||||
// $this->prepare_builder_image();
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||
// ],
|
||||
// );
|
||||
// $this->build_image_name = Str::lower("{$this->customRepository}:build");
|
||||
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
// $this->save_environment_variables();
|
||||
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
// ray($containers);
|
||||
// if ($containers->count() > 0) {
|
||||
// foreach ($containers as $container) {
|
||||
// $containerName = data_get($container, 'Names');
|
||||
// if ($containerName) {
|
||||
// instant_remote_process(
|
||||
// ["docker rm -f {$containerName}"],
|
||||
// $this->application->destination->server
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// $this->execute_remote_command(
|
||||
// ["echo -n 'Starting services (could take a while)...'"],
|
||||
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
// );
|
||||
// }
|
||||
private function generate_image_names()
|
||||
{
|
||||
if ($this->application->dockerfile) {
|
||||
@@ -370,6 +347,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_image_names();
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->create_workdir();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
@@ -402,7 +380,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
}
|
||||
}
|
||||
ray($envs);
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -466,11 +443,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->clone_repository();
|
||||
$this->generate_image_names();
|
||||
$this->cleanup_git();
|
||||
$this->application->loadComposeFile(isInit: false);
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||
$this->docker_compose_base64 = base64_encode($yaml);
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yaml"), "hidden" => true
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
|
||||
]);
|
||||
$this->save_environment_variables();
|
||||
$this->stop_running_container(force: true);
|
||||
@@ -479,13 +457,34 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
|
||||
}
|
||||
$this->execute_remote_command([
|
||||
"docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
|
||||
], [
|
||||
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
|
||||
]);
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
"docker network create --attachable '{$networkId}' >/dev/null || 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->application->loadComposeFile(isInit: false);
|
||||
}
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
@@ -528,6 +527,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->create_workdir();
|
||||
$this->execute_remote_command([
|
||||
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
|
||||
]);
|
||||
@@ -570,74 +570,83 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function rolling_update()
|
||||
{
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '\n----------------------------------------'",
|
||||
],
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||
);
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
if ($this->server->isSwarm()) {
|
||||
// Skip this.
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '\n----------------------------------------'",
|
||||
],
|
||||
["echo -n 'Rolling update started.'"],
|
||||
);
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '\n----------------------------------------'",
|
||||
],
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||
);
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '\n----------------------------------------'",
|
||||
],
|
||||
["echo -n 'Rolling update started.'"],
|
||||
);
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
// ray('New container name: ', $this->container_name);
|
||||
if ($this->container_name) {
|
||||
$counter = 1;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Waiting for healthcheck to pass on the new container.'"
|
||||
]
|
||||
);
|
||||
if ($this->full_healthcheck_url) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// Implement healthcheck for swarm
|
||||
} else {
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
// ray('New container name: ', $this->container_name);
|
||||
if ($this->container_name) {
|
||||
$counter = 1;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
|
||||
"echo 'Waiting for healthcheck to pass on the new container.'"
|
||||
]
|
||||
);
|
||||
}
|
||||
while ($counter < $this->application->health_check_retries) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||
"hidden" => true,
|
||||
"save" => "health_check"
|
||||
],
|
||||
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
||||
],
|
||||
);
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
$this->application->update(['status' => 'running']);
|
||||
if ($this->full_healthcheck_url) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'New container is healthy.'"
|
||||
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
|
||||
]
|
||||
);
|
||||
}
|
||||
while ($counter < $this->application->health_check_retries) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||
"hidden" => true,
|
||||
"save" => "health_check"
|
||||
],
|
||||
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
||||
],
|
||||
);
|
||||
break;
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
$this->application->update(['status' => 'running']);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'New container is healthy.'"
|
||||
],
|
||||
);
|
||||
break;
|
||||
}
|
||||
$counter++;
|
||||
sleep($this->application->health_check_interval);
|
||||
}
|
||||
$counter++;
|
||||
sleep($this->application->health_check_interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,7 +675,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
[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()
|
||||
{
|
||||
$helperImage = config('coolify.helper_image');
|
||||
@@ -675,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);
|
||||
|
||||
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 {
|
||||
$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(
|
||||
[
|
||||
@@ -690,6 +706,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
||||
],
|
||||
|
||||
);
|
||||
}
|
||||
private function deploy_to_additional_destinations()
|
||||
@@ -844,26 +861,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_args = $this->env_args->implode(' ');
|
||||
}
|
||||
|
||||
private function modify_compose_file()
|
||||
{
|
||||
// ray("{$this->workdir}{$this->docker_compose_location}");
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->docker_compose_location}"), "hidden" => true, "save" => 'compose_file']);
|
||||
if ($this->saved_outputs->get('compose_file')) {
|
||||
$compose = $this->saved_outputs->get('compose_file');
|
||||
}
|
||||
try {
|
||||
$yaml = Yaml::parse($compose);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
$services = data_get($yaml, 'services');
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$definedNetwork = collect([$this->application->uuid]);
|
||||
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork) {
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
});
|
||||
}
|
||||
private function generate_compose_file()
|
||||
{
|
||||
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
||||
@@ -884,21 +881,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
|
||||
// $newHostLabel = $newLabels->filter(function ($label) {
|
||||
// return str($label)->contains('Host');
|
||||
// });
|
||||
// $labels = $labels->reject(function ($label) {
|
||||
// return str($label)->contains('Host');
|
||||
// });
|
||||
// ray($labels,$newLabels);
|
||||
// $labels = $labels->map(function ($label) {
|
||||
// $pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
|
||||
// $replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
|
||||
// $newLabel = preg_replace($pattern, $replacement, $label);
|
||||
// return $newLabel;
|
||||
// });
|
||||
// $labels = $labels->merge($newHostLabel);
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||
$docker_compose = [
|
||||
@@ -909,7 +891,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'container_name' => $this->container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => $labels,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
@@ -941,6 +922,48 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if ($this->server->isSwarm()) {
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.container_name');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.expose');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.restart');
|
||||
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.cpus');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset');
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
|
||||
|
||||
$docker_compose['services'][$this->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->application->limits_cpus,
|
||||
'memory' => $this->application->limits_memory,
|
||||
],
|
||||
'reservations' => [
|
||||
'cpus' => $this->application->limits_cpus,
|
||||
'memory' => $this->application->limits_memory,
|
||||
]
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||
}
|
||||
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$this->container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
@@ -988,6 +1011,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||
// ];
|
||||
// }
|
||||
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||
@@ -1066,6 +1094,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else {
|
||||
$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) {
|
||||
$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 = [
|
||||
@@ -1204,7 +1235,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
});
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||
$this->execute_remote_command(
|
||||
@@ -1222,10 +1252,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} 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],
|
||||
);
|
||||
if ($this->docker_compose_location) {
|
||||
$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.");
|
||||
}
|
||||
|
||||
private function generate_build_env_variables()
|
||||
@@ -1259,7 +1296,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
}
|
||||
}
|
||||
ray($dockerfile->implode("\n"));
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
|
||||
@@ -1289,9 +1325,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->execute_remote_command(
|
||||
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'],
|
||||
["echo '{$exception->getMessage()}'", 'type' => 'err'],
|
||||
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||
);
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||
);
|
||||
}
|
||||
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
}
|
||||
|
||||
180
app/Jobs/ApplicationDeploymentNewJob.php
Normal file
180
app/Jobs/ApplicationDeploymentNewJob.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (!$this->server->isServerReady()) {
|
||||
return;
|
||||
};
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
if (!$containers) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\ApplicationPreview;
|
||||
@@ -21,10 +22,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->handle();
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
@@ -35,6 +32,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return $this->server->id;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
if (isDev()) $this->handle();
|
||||
}
|
||||
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// ray("checking container statuses for {$this->server->id}");
|
||||
@@ -42,23 +45,62 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (!$this->server->isServerReady()) {
|
||||
return;
|
||||
};
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
if (!$containers) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = null;
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return;
|
||||
}
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicase) {
|
||||
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
|
||||
foreach ($containerReplicase as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
$total = str($replicas)->explode('/')[1];
|
||||
if ($running === $total) {
|
||||
data_set($container, 'State.Status', 'running');
|
||||
data_set($container, 'State.Health.Status', 'healthy');
|
||||
} else {
|
||||
data_set($container, 'State.Status', 'starting');
|
||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
});
|
||||
}
|
||||
}
|
||||
$applications = $this->server->applications();
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
if ($this->server->isSwarm()) {
|
||||
$labels = data_get($container, 'Spec.Labels');
|
||||
$uuid = data_get($labels, 'coolify.name');
|
||||
} else {
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
}
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
@@ -94,11 +136,25 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($uuid) {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$isPublic = data_get($database, 'is_public');
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $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 {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
@@ -245,7 +301,11 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
}
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
try {
|
||||
@@ -253,8 +313,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
} else {
|
||||
ray('Proxy could not be started.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
@@ -48,13 +49,75 @@ class Application extends BaseModel
|
||||
$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()
|
||||
{
|
||||
return route('project.application.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'application_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.application.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'application_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function settings()
|
||||
{
|
||||
@@ -382,9 +445,7 @@ class Application extends BaseModel
|
||||
}
|
||||
public function isMultipleServerDeployment()
|
||||
{
|
||||
if (isDev()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
|
||||
return true;
|
||||
}
|
||||
@@ -428,6 +489,294 @@ class Application extends BaseModel
|
||||
{
|
||||
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)
|
||||
{
|
||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||
@@ -584,12 +933,12 @@ class Application extends BaseModel
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$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,
|
||||
]);
|
||||
} else {
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
@@ -614,7 +963,7 @@ class Application extends BaseModel
|
||||
function loadComposeFile($isInit = false)
|
||||
{
|
||||
$initialDockerComposeLocation = $this->docker_compose_location;
|
||||
$initialDockerComposePrLocation = $this->docker_compose_pr_location;
|
||||
// $initialDockerComposePrLocation = $this->docker_compose_pr_location;
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
if ($isInit && $this->docker_compose_raw) {
|
||||
return;
|
||||
@@ -623,11 +972,11 @@ class Application extends BaseModel
|
||||
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
|
||||
$workdir = rtrim($this->base_directory, '/');
|
||||
$composeFile = $this->docker_compose_location;
|
||||
$prComposeFile = $this->docker_compose_pr_location;
|
||||
$fileList = collect([".$composeFile"]);
|
||||
if ($composeFile !== $prComposeFile) {
|
||||
$fileList->push(".$prComposeFile");
|
||||
}
|
||||
// $prComposeFile = $this->docker_compose_pr_location;
|
||||
$fileList = collect([".$workdir$composeFile"]);
|
||||
// if ($composeFile !== $prComposeFile) {
|
||||
// $fileList->push(".$prComposeFile");
|
||||
// }
|
||||
$commands = collect([
|
||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
@@ -645,24 +994,24 @@ class Application extends BaseModel
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
}
|
||||
if ($composeFile === $prComposeFile) {
|
||||
$this->docker_compose_pr_raw = $composeFileContent;
|
||||
$this->save();
|
||||
} else {
|
||||
$commands = collect([
|
||||
"cd /tmp/{$uuid}",
|
||||
"cat .$workdir$prComposeFile",
|
||||
]);
|
||||
$composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
if (!$composePrFileContent) {
|
||||
$this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
||||
$this->save();
|
||||
throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
||||
} else {
|
||||
$this->docker_compose_pr_raw = $composePrFileContent;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
// if ($composeFile === $prComposeFile) {
|
||||
// $this->docker_compose_pr_raw = $composeFileContent;
|
||||
// $this->save();
|
||||
// } else {
|
||||
// $commands = collect([
|
||||
// "cd /tmp/{$uuid}",
|
||||
// "cat .$workdir$prComposeFile",
|
||||
// ]);
|
||||
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
// if (!$composePrFileContent) {
|
||||
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
||||
// $this->save();
|
||||
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
||||
// } else {
|
||||
// $this->docker_compose_pr_raw = $composePrFileContent;
|
||||
// $this->save();
|
||||
// }
|
||||
// }
|
||||
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
|
||||
@@ -9,6 +9,11 @@ class ApplicationDeploymentQueue extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function setStatus(string $status) {
|
||||
$this->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
public function getOutput($name)
|
||||
{
|
||||
if (!$this->logs) {
|
||||
|
||||
@@ -16,29 +16,16 @@ class ApplicationSetting extends Model
|
||||
'is_git_submodules_enabled' => 'boolean',
|
||||
'is_git_lfs_enabled' => 'boolean',
|
||||
];
|
||||
protected $fillable = [
|
||||
'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',
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
public function isStatic(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (is_null($this->application->ports_exposes)) {
|
||||
if ($value) {
|
||||
$this->application->ports_exposes = '80';
|
||||
} else {
|
||||
$this->application->ports_exposes = '3000';
|
||||
}
|
||||
$this->application->save();
|
||||
if ($value) {
|
||||
$this->application->ports_exposes = 80;
|
||||
}
|
||||
$this->application->save();
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Server\InstallLogDrain;
|
||||
use App\Actions\Server\InstallNewRelic;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
@@ -18,7 +16,7 @@ use Illuminate\Support\Sleep;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
use Stringable;
|
||||
use Illuminate\Support\Stringable;
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
@@ -37,25 +35,10 @@ class Server extends BaseModel
|
||||
}
|
||||
$server->forceFill($payload);
|
||||
});
|
||||
|
||||
static::created(function ($server) {
|
||||
ServerSetting::create([
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
if ($server->id === 0) {
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
} else {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
}
|
||||
});
|
||||
static::deleting(function ($server) {
|
||||
$server->destinations()->each(function ($destination) {
|
||||
@@ -84,7 +67,7 @@ class Server extends BaseModel
|
||||
{
|
||||
$teamId = currentTeam()->id;
|
||||
$selectArray = collect($select)->concat(['id']);
|
||||
return Server::whereTeamId($teamId)->with('settings')->select($selectArray->all())->orderBy('name');
|
||||
return Server::whereTeamId($teamId)->with('settings', 'swarmDockers', 'standaloneDockers')->select($selectArray->all())->orderBy('name');
|
||||
}
|
||||
|
||||
static public function isUsable()
|
||||
@@ -103,7 +86,40 @@ class Server extends BaseModel
|
||||
{
|
||||
return $this->hasOne(ServerSetting::class);
|
||||
}
|
||||
|
||||
public function addInitialNetwork()
|
||||
{
|
||||
if ($this->id === 0) {
|
||||
if ($this->isSwarm()) {
|
||||
SwarmDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify-overlay',
|
||||
'server_id' => $this->id,
|
||||
]);
|
||||
} else {
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $this->id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if ($this->isSwarm()) {
|
||||
SwarmDocker::create([
|
||||
'name' => 'coolify-overlay',
|
||||
'network' => 'coolify-overlay',
|
||||
'server_id' => $this->id,
|
||||
]);
|
||||
} else {
|
||||
StandaloneDocker::create([
|
||||
'name' => 'coolify-overlay',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $this->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
public function proxyType()
|
||||
{
|
||||
$proxyType = $this->proxy->get('type');
|
||||
@@ -332,7 +348,7 @@ class Server extends BaseModel
|
||||
}
|
||||
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
|
||||
{
|
||||
@@ -359,30 +375,34 @@ class Server extends BaseModel
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public function isSwarm()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
if ($this->skipServer()) {
|
||||
$server = Server::find($this->id);
|
||||
if ($server->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uptime = instant_remote_process(['uptime'], $this, false);
|
||||
$uptime = instant_remote_process(['uptime'], $server, false);
|
||||
if (!$uptime) {
|
||||
$this->settings()->update([
|
||||
$server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
return false;
|
||||
} else {
|
||||
$this->settings()->update([
|
||||
$server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
$this->update([
|
||||
$server->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
if (data_get($this, 'unreachable_notification_sent') === true) {
|
||||
$this->team->notify(new Revived($this));
|
||||
$this->update(['unreachable_notification_sent' => false]);
|
||||
if (data_get($server, 'unreachable_notification_sent') === true) {
|
||||
$server->team->notify(new Revived($server));
|
||||
$server->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -400,7 +420,20 @@ class Server extends BaseModel
|
||||
}
|
||||
$this->settings->is_usable = true;
|
||||
$this->settings->save();
|
||||
$this->validateCoolifyNetwork();
|
||||
$this->validateCoolifyNetwork(isSwarm: false);
|
||||
return true;
|
||||
}
|
||||
public function validateDockerSwarm()
|
||||
{
|
||||
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
||||
$swarmStatus = str($swarmStatus)->trim()->after(':')->trim();
|
||||
if ($swarmStatus === 'inactive') {
|
||||
throw new \Exception('Docker Swarm is not initiated. Please join the server to a swarm before continuing.');
|
||||
return false;
|
||||
}
|
||||
$this->settings->is_usable = true;
|
||||
$this->settings->save();
|
||||
$this->validateCoolifyNetwork(isSwarm: true);
|
||||
return true;
|
||||
}
|
||||
public function validateDockerEngineVersion()
|
||||
@@ -417,9 +450,13 @@ class Server extends BaseModel
|
||||
$this->settings->save();
|
||||
return true;
|
||||
}
|
||||
public function validateCoolifyNetwork()
|
||||
public function validateCoolifyNetwork($isSwarm = false)
|
||||
{
|
||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||
if ($isSwarm) {
|
||||
return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
|
||||
} else {
|
||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||
}
|
||||
}
|
||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
||||
{
|
||||
@@ -500,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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,20 @@ class Service extends BaseModel
|
||||
}
|
||||
$fields->put('Weblate', $data);
|
||||
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'):
|
||||
$data = collect([]);
|
||||
$MAIL_OPTIONS_AUTH_PASS = $this->environment_variables()->where('key', 'MAIL_OPTIONS_AUTH_PASS')->first();
|
||||
@@ -193,6 +207,7 @@ class Service extends BaseModel
|
||||
break;
|
||||
}
|
||||
}
|
||||
ray($fields);
|
||||
$databases = $this->databases()->get();
|
||||
|
||||
foreach ($databases as $database) {
|
||||
@@ -363,11 +378,14 @@ class Service extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.service.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'service_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.service.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'service_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function documentation()
|
||||
{
|
||||
|
||||
@@ -43,11 +43,14 @@ class StandaloneMariadb extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'database_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
|
||||
@@ -50,11 +50,14 @@ class StandaloneMongodb extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'database_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function mongoInitdbRootPassword(): Attribute
|
||||
{
|
||||
|
||||
@@ -43,11 +43,14 @@ class StandaloneMysql extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'database_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function type(): string
|
||||
{
|
||||
|
||||
@@ -43,11 +43,14 @@ class StandalonePostgresql extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'database_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
|
||||
@@ -38,11 +38,14 @@ class StandaloneRedis extends BaseModel
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => $this->environment->project->uuid,
|
||||
'environment_name' => $this->environment->name,
|
||||
'database_uuid' => $this->uuid
|
||||
]);
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
|
||||
@@ -4,13 +4,57 @@ namespace App\Models;
|
||||
|
||||
class SwarmDocker extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function applications()
|
||||
{
|
||||
return $this->morphMany(Application::class, 'destination');
|
||||
}
|
||||
|
||||
public function postgresqls()
|
||||
{
|
||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||
}
|
||||
|
||||
public function redis()
|
||||
{
|
||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||
}
|
||||
public function mongodbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneMongodb::class, 'destination');
|
||||
}
|
||||
public function mysqls()
|
||||
{
|
||||
return $this->morphMany(StandaloneMysql::class, 'destination');
|
||||
}
|
||||
public function mariadbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneMariadb::class, 'destination');
|
||||
}
|
||||
|
||||
public function server()
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->morphMany(Service::class, 'destination');
|
||||
}
|
||||
|
||||
public function databases()
|
||||
{
|
||||
$postgresqls = $this->postgresqls;
|
||||
$redis = $this->redis;
|
||||
$mongodbs = $this->mongodbs;
|
||||
$mysqls = $this->mysqls;
|
||||
$mariadbs = $this->mariadbs;
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
}
|
||||
|
||||
public function attachedTo()
|
||||
{
|
||||
return $this->applications?->count() > 0 || $this->databases()->count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,28 +8,15 @@ use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvi
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event to listener mappings for the application.
|
||||
*
|
||||
* @var array<class-string, array<int, class-string>>
|
||||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
// Registered::class => [
|
||||
// SendEmailVerificationNotification::class,
|
||||
// ],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if events and listeners should be automatically discovered.
|
||||
*/
|
||||
public function shouldDiscoverEvents(): bool
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Jobs\ApplicationDeployDockerImageJob;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Jobs\ApplicationDeploymentNewJob;
|
||||
use App\Jobs\ApplicationDeploySimpleDockerfileJob;
|
||||
use App\Jobs\ApplicationRestartJob;
|
||||
use App\Jobs\MultipleApplicationDeploymentJob;
|
||||
@@ -11,7 +12,7 @@ use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
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([
|
||||
'application_id' => $application_id,
|
||||
@@ -36,19 +37,32 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
||||
if ($running_deployments->count() > 0) {
|
||||
return;
|
||||
}
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
|
||||
if ($is_new_deployment) {
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
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();
|
||||
if ($next_found) {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
if ($isNew) {
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
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
|
||||
@@ -97,12 +111,12 @@ function prepareHelperContainer(Server $server, string $network, string $deploym
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$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,
|
||||
]);
|
||||
} else {
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,11 @@ function executeInDocker(string $containerId, string $command)
|
||||
|
||||
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
|
||||
{
|
||||
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
||||
if ($server->isSwarm()) {
|
||||
$container = instant_remote_process(["docker service ls --filter 'name={$container_id}' --format '{{json .}}' "], $server, $throwError);
|
||||
} else {
|
||||
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
|
||||
}
|
||||
if (!$container) {
|
||||
return 'exited';
|
||||
}
|
||||
@@ -101,7 +105,19 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
||||
if ($all_data) {
|
||||
return $container[0];
|
||||
}
|
||||
return data_get($container[0], 'State.Status', 'exited');
|
||||
if ($server->isSwarm()) {
|
||||
$replicas = data_get($container[0], 'Replicas');
|
||||
$replicas = explode('/', $replicas);
|
||||
$active = (int)$replicas[0];
|
||||
$total = (int)$replicas[1];
|
||||
if ($active === $total) {
|
||||
return 'running';
|
||||
} else {
|
||||
return 'starting';
|
||||
}
|
||||
} else {
|
||||
return data_get($container[0], 'State.Status', 'exited');
|
||||
}
|
||||
}
|
||||
|
||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||
@@ -275,8 +291,11 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
return $labels->all();
|
||||
}
|
||||
|
||||
function isDatabaseImage(string $image)
|
||||
function isDatabaseImage(?string $image = null)
|
||||
{
|
||||
if (is_null($image)) {
|
||||
return false;
|
||||
}
|
||||
$image = str($image);
|
||||
if ($image->contains(':')) {
|
||||
$image = str($image);
|
||||
|
||||
@@ -54,9 +54,15 @@ function connectProxyToNetworks(Server $server)
|
||||
function generate_default_proxy_configuration(Server $server)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
} else {
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
}
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
@@ -66,6 +72,16 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"external" => true,
|
||||
];
|
||||
});
|
||||
$labels = [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.middlewares=traefik-basic-auth@file",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
// Global Middlewares
|
||||
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
|
||||
"traefik.http.middlewares.gzip.compress=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
@@ -102,7 +118,6 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--providers.docker=true",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
@@ -110,16 +125,7 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||
],
|
||||
"labels" => [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.middlewares=traefik-basic-auth@file",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
// Global Middlewares
|
||||
"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https",
|
||||
"traefik.http.middlewares.gzip.compress=true",
|
||||
],
|
||||
"labels" => $labels,
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -128,7 +134,24 @@ function generate_default_proxy_configuration(Server $server)
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
$config = Yaml::dump($config, 4, 2);
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
data_forget($config, 'services.traefik.restart');
|
||||
data_forget($config, 'services.traefik.labels');
|
||||
|
||||
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||
$config['services']['traefik']['deploy'] = [
|
||||
"labels" => $labels,
|
||||
"placement" => [
|
||||
"constraints" => [
|
||||
"node.role==manager",
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||
}
|
||||
$config = Yaml::dump($config, 12, 2);
|
||||
SaveConfiguration::run($server, $config);
|
||||
return $config;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,9 @@ function instant_remote_process(Collection|array $command, Server $server, $thro
|
||||
}
|
||||
return excludeCertainErrors($process->errorOutput(), $exitCode);
|
||||
}
|
||||
if ($output === 'null') {
|
||||
$output = null;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)
|
||||
|
||||
@@ -863,7 +863,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$key = Str::of($variableName);
|
||||
$value = Str::of($variable);
|
||||
}
|
||||
// TODO: here is the problem
|
||||
if ($key->startsWith('SERVICE_FQDN')) {
|
||||
if ($isNew || $savedService->fqdn === null) {
|
||||
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||
@@ -914,13 +913,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
continue;
|
||||
}
|
||||
if ($value?->startsWith('$')) {
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
if ($value->startsWith('SERVICE_')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||
if (Str::lower($forService) === $serviceName) {
|
||||
@@ -1000,7 +1003,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($savedService->serviceType()) {
|
||||
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
|
||||
} 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);
|
||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||
@@ -1099,6 +1102,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
|
||||
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
|
||||
if ($serviceLabels->count() > 0) {
|
||||
$removedLabels = collect([]);
|
||||
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
||||
@@ -1119,7 +1124,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) {
|
||||
if (is_string($volume)) {
|
||||
$volume = str($volume);
|
||||
if ($volume->contains(':')) {
|
||||
if ($volume->contains(':') && !$volume->startsWith('/')) {
|
||||
$name = $volume->before(':');
|
||||
$mount = $volume->after(':');
|
||||
$newName = $resource->uuid . "-{$name}-pr-$pull_request_id";
|
||||
@@ -1133,18 +1138,64 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($source) {
|
||||
$newSource = $resource->uuid . "-{$source}-pr-$pull_request_id";
|
||||
data_set($volume, 'source', $newSource);
|
||||
$topLevelVolumes->put($newSource, [
|
||||
'name' => $newSource,
|
||||
]);
|
||||
if (!str($source)->startsWith('/')) {
|
||||
$topLevelVolumes->put($newSource, [
|
||||
'name' => $newSource,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $volume->value();
|
||||
});
|
||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||
}
|
||||
} else {
|
||||
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
|
||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||
@@ -1271,13 +1322,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
continue;
|
||||
}
|
||||
if ($value?->startsWith('$')) {
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'application_id' => $resource->id,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
|
||||
if ($value->startsWith('SERVICE_')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'application_id' => $resource->id,
|
||||
])->first();
|
||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||
if (Str::lower($forService) === $serviceName) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"nubs/random-name-generator": "^2.2",
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"poliander/cron": "^3.0",
|
||||
"pusher/pusher-php-server": "^7.2",
|
||||
"resend/resend-laravel": "^0.5.0",
|
||||
"sentry/sentry-laravel": "^3.4",
|
||||
"spatie/laravel-activitylog": "^4.7.3",
|
||||
@@ -38,6 +39,7 @@
|
||||
"stripe/stripe-php": "^12.0",
|
||||
"symfony/yaml": "^6.2",
|
||||
"visus/cuid2": "^2.0.0",
|
||||
"wire-elements/modal": "^1.0",
|
||||
"yosymfony/toml": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
1279
composer.lock
generated
1279
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -194,7 +194,7 @@ return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
|
||||
@@ -15,7 +15,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('BROADCAST_DRIVER', 'null'),
|
||||
'default' => env('BROADCAST_DRIVER', 'pusher'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -32,13 +32,13 @@ return [
|
||||
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'key' => env('PUSHER_APP_KEY', 'coolify'),
|
||||
'secret' => env('PUSHER_APP_SECRET', 'coolify'),
|
||||
'app_id' => env('PUSHER_APP_ID', 'coolify'),
|
||||
'options' => [
|
||||
'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'host' => env('PUSHER_BACKEND_HOST', 'coolify-soketi'),
|
||||
'port' => env('PUSHER_BACKEND_PORT', 6001),
|
||||
'scheme' => env('PUSHER_SCHEME', 'http'),
|
||||
'encrypted' => true,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
|
||||
@@ -22,7 +22,7 @@ return [
|
||||
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
|
||||
],
|
||||
'limits' => [
|
||||
'trial_period' => 7,
|
||||
'trial_period' => 0,
|
||||
'server' => [
|
||||
'zero' => 0,
|
||||
'self-hosted' => 999999999999,
|
||||
|
||||
52
config/livewire-ui-modal.php
Normal file
52
config/livewire-ui-modal.php
Normal 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,
|
||||
],
|
||||
];
|
||||
@@ -3,11 +3,11 @@
|
||||
return [
|
||||
|
||||
// @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
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.147',
|
||||
'release' => '4.0.0-beta.154',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.147';
|
||||
return '4.0.0-beta.154';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('swarm_dockers', function (Blueprint $table) {
|
||||
$table->string('network');
|
||||
|
||||
$table->unique(['server_id', 'network']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('swarm_dockers', function (Blueprint $table) {
|
||||
$table->dropColumn('network');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->renameColumn('is_part_of_swarm', 'is_swarm_manager');
|
||||
$table->boolean('is_swarm_worker')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->renameColumn('is_swarm_manager', 'is_part_of_swarm');
|
||||
$table->dropColumn('is_swarm_worker');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -13,11 +13,11 @@ class StandaloneDockerSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// StandaloneDocker::create([
|
||||
// 'id' => 0,
|
||||
// 'name' => 'Standalone Docker 1',
|
||||
// 'network' => 'coolify',
|
||||
// 'server_id' => 0,
|
||||
// ]);
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'Standalone Docker 1',
|
||||
'network' => 'coolify',
|
||||
'server_id' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ class SwarmDockerSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
SwarmDocker::create([
|
||||
'name' => 'Swarm Docker 1',
|
||||
'server_id' => 1,
|
||||
]);
|
||||
// SwarmDocker::create([
|
||||
// 'name' => 'Swarm Docker 1',
|
||||
// 'server_id' => 1,
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ services:
|
||||
SSL_MODE: "off"
|
||||
AUTORUN_LARAVEL_STORAGE_LINK: "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:
|
||||
- .:/var/www/html/:cached
|
||||
postgres:
|
||||
@@ -44,9 +50,21 @@ services:
|
||||
volumes:
|
||||
- /data/coolify/_volumes/redis/:/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:
|
||||
image: node:20
|
||||
working_dir: /var/www/html
|
||||
# environment:
|
||||
# VITE_PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
ports:
|
||||
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
|
||||
volumes:
|
||||
@@ -60,14 +78,14 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /data/coolify/:/data/coolify
|
||||
# - coolify-data-dev:/data/coolify
|
||||
remote-host:
|
||||
<<: *testing-host-base
|
||||
container_name: coolify-remote-host
|
||||
volumes:
|
||||
- /:/host
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /data/coolify/:/data/coolify
|
||||
# - coolify-data-dev:/data/coolify
|
||||
# remote-host:
|
||||
# <<: *testing-host-base
|
||||
# container_name: coolify-remote-host
|
||||
# volumes:
|
||||
# - /:/host
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
# - /data/coolify/:/data/coolify
|
||||
# # - coolify-data-dev:/data/coolify
|
||||
mailpit:
|
||||
image: "axllent/mailpit:latest"
|
||||
container_name: coolify-mail
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
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:
|
||||
- type: bind
|
||||
source: /data/coolify/source/.env
|
||||
@@ -35,6 +35,14 @@ services:
|
||||
- PHP_PM_START_SERVERS=1
|
||||
- PHP_PM_MIN_SPARE_SERVERS=1
|
||||
- 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
|
||||
- WAITLIST
|
||||
- SUBSCRIPTION_PROVIDER
|
||||
@@ -74,8 +82,8 @@ services:
|
||||
- "${APP_PORT:-8000}"
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:80/api/health || exit 1
|
||||
interval: 4s
|
||||
retries: 5
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
depends_on:
|
||||
postgres:
|
||||
@@ -97,8 +105,8 @@ services:
|
||||
"-d",
|
||||
"${DB_DATABASE:-coolify}"
|
||||
]
|
||||
interval: 2s
|
||||
retries: 5
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
redis:
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
|
||||
@@ -108,8 +116,21 @@ services:
|
||||
- coolify-redis:/data
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 2s
|
||||
retries: 5
|
||||
interval: 5s
|
||||
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
|
||||
volumes:
|
||||
coolify-db:
|
||||
|
||||
@@ -24,6 +24,12 @@ services:
|
||||
restart: always
|
||||
networks:
|
||||
- coolify
|
||||
soketi:
|
||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
||||
container_name: coolify-soketi
|
||||
restart: always
|
||||
networks:
|
||||
- coolify
|
||||
networks:
|
||||
coolify:
|
||||
name: coolify
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/command/execlineb -P
|
||||
php /var/www/html/artisan app:init
|
||||
php /var/www/html/artisan app:init --cleanup
|
||||
|
||||
385
package-lock.json
generated
385
package-lock.json
generated
@@ -5,20 +5,24 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@alpinejs/focus": "^3.13.3",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"alpinejs": "3.13.2",
|
||||
"daisyui": "4.3.1",
|
||||
"alpinejs": "3.13.3",
|
||||
"daisyui": "4.4.19",
|
||||
"ioredis": "5.3.2",
|
||||
"tailwindcss-scrollbar": "0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "4.5.0",
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.6.2",
|
||||
"laravel-echo": "1.15.3",
|
||||
"laravel-vite-plugin": "0.8.1",
|
||||
"postcss": "8.4.31",
|
||||
"tailwindcss": "3.3.5",
|
||||
"vite": "4.5.0",
|
||||
"vue": "3.3.8"
|
||||
"postcss": "8.4.32",
|
||||
"pusher-js": "8.4.0-rc2",
|
||||
"tailwindcss": "3.3.6",
|
||||
"vite": "4.5.1",
|
||||
"vue": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -32,10 +36,19 @@
|
||||
"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": {
|
||||
"version": "7.23.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
|
||||
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
|
||||
"version": "7.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
|
||||
"integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -396,6 +409,11 @@
|
||||
"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": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
@@ -503,9 +521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz",
|
||||
"integrity": "sha512-a2WSpP8X8HTEww/U00bU4mX1QpLINNuz/2KMNpLsdu3BzOpak3AGI1CJYBTXcc4SPhaD0eNRUp7IyQK405L5dQ==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.1.tgz",
|
||||
"integrity": "sha512-DaUzYFr+2UGDG7VSSdShKa9sIWYBa1LL8KC0MNOf2H5LjcTPjob0x8LbkqXWmAtbANJCkpiQTj66UVcQkN2s3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
@@ -516,77 +534,77 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
|
||||
"integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.10.tgz",
|
||||
"integrity": "sha512-doe0hODR1+i1menPkRzJ5MNR6G+9uiZHIknK3Zn5OcIztu6GGw7u0XUzf3AgB8h/dfsZC9eouzoLo3c3+N/cVA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/shared": "3.3.8",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@vue/shared": "3.3.10",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
|
||||
"integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.10.tgz",
|
||||
"integrity": "sha512-NCrqF5fm10GXZIK0GrEAauBqdy+F2LZRt3yNHzrYjpYBuRssQbuPLtSnSNjyR9luHKkWSH8we5LMB3g+4z2HvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.3.8",
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/compiler-core": "3.3.10",
|
||||
"@vue/shared": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
|
||||
"integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.10.tgz",
|
||||
"integrity": "sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.8",
|
||||
"@vue/compiler-dom": "3.3.8",
|
||||
"@vue/compiler-ssr": "3.3.8",
|
||||
"@vue/reactivity-transform": "3.3.8",
|
||||
"@vue/shared": "3.3.8",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@vue/compiler-core": "3.3.10",
|
||||
"@vue/compiler-dom": "3.3.10",
|
||||
"@vue/compiler-ssr": "3.3.10",
|
||||
"@vue/reactivity-transform": "3.3.10",
|
||||
"@vue/shared": "3.3.10",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss": "^8.4.32",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
|
||||
"integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.10.tgz",
|
||||
"integrity": "sha512-12iM4jA4GEbskwXMmPcskK5wImc2ohKm408+o9iox3tfN9qua8xL0THIZtoe9OJHnXP4eOWZpgCAAThEveNlqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.3.8",
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/compiler-dom": "3.3.10",
|
||||
"@vue/shared": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
@@ -598,83 +616,83 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity-transform": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
|
||||
"integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.10.tgz",
|
||||
"integrity": "sha512-0xBdk+CKHWT+Gev8oZ63Tc0qFfj935YZx+UAynlutnrDZ4diFCVFMWixn65HzjE3S1iJppWOo6Tt1OzASH7VEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@vue/compiler-core": "3.3.8",
|
||||
"@vue/shared": "3.3.8",
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@vue/compiler-core": "3.3.10",
|
||||
"@vue/shared": "3.3.10",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity-transform/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
|
||||
"integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.10.tgz",
|
||||
"integrity": "sha512-DZ0v31oTN4YHX9JEU5VW1LoIVgFovWgIVb30bWn9DG9a7oA415idcwsRNNajqTx8HQJyOaWfRKoyuP2P2TYIag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.3.8",
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/reactivity": "3.3.10",
|
||||
"@vue/shared": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core/node_modules/@vue/reactivity": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz",
|
||||
"integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.10.tgz",
|
||||
"integrity": "sha512-H5Z7rOY/JLO+e5a6/FEXaQ1TMuOvY4LDVgT+/+HKubEAgs9qeeZ+NhADSeEtrNQeiKLDuzeKc8v0CUFpB6Pqgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/shared": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
|
||||
"integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.10.tgz",
|
||||
"integrity": "sha512-c/jKb3ny05KJcYk0j1m7Wbhrxq7mZYr06GhKykDMNRRR9S+/dGT8KpHuNQjv3/8U4JshfkAk6TpecPD3B21Ijw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "3.3.8",
|
||||
"@vue/shared": "3.3.8",
|
||||
"@vue/runtime-core": "3.3.10",
|
||||
"@vue/shared": "3.3.10",
|
||||
"csstype": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
|
||||
"integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.10.tgz",
|
||||
"integrity": "sha512-0i6ww3sBV3SKlF3YTjSVqKQ74xialMbjVYGy7cOTi7Imd8ediE7t72SK3qnvhrTAhOvlQhq6Bk6nFPdXxe0sAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.3.8",
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/compiler-ssr": "3.3.10",
|
||||
"@vue/shared": "3.3.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.3.8"
|
||||
"vue": "3.3.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
@@ -683,9 +701,9 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.13.2",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.2.tgz",
|
||||
"integrity": "sha512-WzojeeN082kLZznGI1HAuP8yFJSWqJ1fGdz2mUjj45H4y0XwToE7fFqtI3mCPRR+BpcSbxT/NL+FyPnYAWSltw==",
|
||||
"version": "3.13.3",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
|
||||
"integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
@@ -896,6 +914,14 @@
|
||||
"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": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -956,9 +982,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.3.1.tgz",
|
||||
"integrity": "sha512-dCi91VD+57lkoBd10CjdW4wPOeOPYvvzQbxti6xmyQbDMbCeCXwNq2KdoU798I4OsCcD5B+n7yVG7HAgYW+cvw==",
|
||||
"version": "4.4.19",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.4.19.tgz",
|
||||
"integrity": "sha512-IjOLWwnndD4N7Ut5CDxbUsaVtbqXPeVHM92IcgxGFxpuOd3CCKW/PAXZH6JoBTHFRaN57vB9XqEhdWm5yC+bPA==",
|
||||
"dependencies": {
|
||||
"css-selector-tokenizer": "^0.8",
|
||||
"culori": "^3",
|
||||
@@ -973,6 +999,22 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -982,6 +1024,14 @@
|
||||
"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": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -1100,6 +1150,14 @@
|
||||
"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": {
|
||||
"version": "1.15.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -1290,6 +1371,15 @@
|
||||
"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": {
|
||||
"version": "0.8.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
@@ -1398,6 +1498,11 @@
|
||||
"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": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
@@ -1409,9 +1514,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1518,9 +1623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"version": "8.4.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
|
||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1536,7 +1641,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
@@ -1647,6 +1752,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"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": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -1685,6 +1799,25 @@
|
||||
"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": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
@@ -1775,6 +1908,11 @@
|
||||
"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": {
|
||||
"version": "3.32.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
|
||||
@@ -1807,10 +1945,15 @@
|
||||
"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": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
|
||||
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.6.tgz",
|
||||
"integrity": "sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -1886,6 +2029,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
"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": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
@@ -1922,9 +2071,9 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
|
||||
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@@ -1987,16 +2136,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
|
||||
"integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.10.tgz",
|
||||
"integrity": "sha512-zg6SIXZdTBwiqCw/1p+m04VyHjLfwtjwz8N57sPaBhEex31ND0RYECVOC1YrRwMRmxFf5T1dabl6SGUbMKKuVw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.3.8",
|
||||
"@vue/compiler-sfc": "3.3.8",
|
||||
"@vue/runtime-dom": "3.3.8",
|
||||
"@vue/server-renderer": "3.3.8",
|
||||
"@vue/shared": "3.3.8"
|
||||
"@vue/compiler-dom": "3.3.10",
|
||||
"@vue/compiler-sfc": "3.3.10",
|
||||
"@vue/runtime-dom": "3.3.10",
|
||||
"@vue/server-renderer": "3.3.10",
|
||||
"@vue/shared": "3.3.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -2008,9 +2157,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue/node_modules/@vue/shared": {
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
|
||||
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==",
|
||||
"version": "3.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.10.tgz",
|
||||
"integrity": "sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
|
||||
18
package.json
18
package.json
@@ -6,19 +6,23 @@
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "4.5.0",
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.6.2",
|
||||
"laravel-echo": "1.15.3",
|
||||
"laravel-vite-plugin": "0.8.1",
|
||||
"postcss": "8.4.31",
|
||||
"tailwindcss": "3.3.5",
|
||||
"vite": "4.5.0",
|
||||
"vue": "3.3.8"
|
||||
"postcss": "8.4.32",
|
||||
"pusher-js": "8.4.0-rc2",
|
||||
"tailwindcss": "3.3.6",
|
||||
"vite": "4.5.1",
|
||||
"vue": "3.3.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alpinejs/focus": "^3.13.3",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"alpinejs": "3.13.2",
|
||||
"daisyui": "4.3.1",
|
||||
"alpinejs": "3.13.3",
|
||||
"daisyui": "4.4.19",
|
||||
"ioredis": "5.3.2",
|
||||
"tailwindcss-scrollbar": "0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
2
public/js/echo.js
Normal file
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
10
public/js/pusher.js
Normal file
File diff suppressed because one or more lines are too long
2
public/vendor/horizon/app.js
vendored
2
public/vendor/horizon/app.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/horizon/mix-manifest.json
vendored
2
public/vendor/horizon/mix-manifest.json
vendored
@@ -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.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",
|
||||
|
||||
@@ -17,6 +17,9 @@ button[isError] {
|
||||
.main {
|
||||
@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 {
|
||||
@apply text-neutral-400;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Alpine from "alpinejs";
|
||||
|
||||
import focus from '@alpinejs/focus';
|
||||
import { createApp } from "vue";
|
||||
import MagicBar from "./components/MagicBar.vue";
|
||||
import Toaster from "../../vendor/masmerise/livewire-toaster/resources/js";
|
||||
import "../../vendor/wire-elements/modal/resources/js/modal";
|
||||
|
||||
Alpine.plugin(focus);
|
||||
Alpine.plugin(Toaster);
|
||||
|
||||
window.Alpine = Alpine;
|
||||
@@ -12,3 +14,6 @@ Alpine.start();
|
||||
const app = createApp({});
|
||||
app.component("magic-bar", MagicBar);
|
||||
app.mount("#vue");
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,18 +3,24 @@
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.deployments', $parameters) }}">
|
||||
<button>Deployments</button>
|
||||
<a class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</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" />
|
||||
<div class="flex-1"></div>
|
||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||
<div>Please load a Compose file.</div>
|
||||
@elseif ($application->destination->server->isSwarm() && str($application->docker_registry_image_name)->isEmpty())
|
||||
Swarm Deployments requires a Docker Image in a Registry.
|
||||
@else
|
||||
<x-applications.advanced :application="$application" />
|
||||
@if ($application->status !== 'exited')
|
||||
@@ -43,6 +49,19 @@
|
||||
</svg>
|
||||
Restart
|
||||
</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
|
||||
<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"
|
||||
@@ -64,6 +83,18 @@
|
||||
</svg>
|
||||
Deploy
|
||||
</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
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
href="{{ route('project.database.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</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' : '' }}"
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
|
||||
@@ -21,8 +21,11 @@
|
||||
</label>
|
||||
</fieldset>
|
||||
</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>
|
||||
@if (config('constants.limits.trial_period') > 0)
|
||||
<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>Save <span class="font-bold text-warning">10%</span> annually with the yearly plans.
|
||||
</div>
|
||||
@@ -255,8 +258,8 @@
|
||||
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
|
||||
your self-hosted instance?
|
||||
<x-forms.button>
|
||||
<a class="font-bold text-white hover:no-underline"
|
||||
href="{{ config('coolify.docs') }}">Contact Us</a>
|
||||
<a class="font-bold text-white hover:no-underline" href="{{ config('coolify.docs') }}">Contact
|
||||
Us</a>
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
</button>
|
||||
@endif
|
||||
@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">
|
||||
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
|
||||
<path fill="red"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@props([
|
||||
'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="badge badge-warning badge-xs"></div>
|
||||
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@props([
|
||||
'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="badge badge-warning badge-xs"></div>
|
||||
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@section('body')
|
||||
@parent
|
||||
<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>
|
||||
</div>
|
||||
<livewire:sponsorship />
|
||||
|
||||
@@ -24,11 +24,20 @@
|
||||
@if (config('app.name') == 'Coolify Cloud')
|
||||
<script defer data-domain="app.coolify.io" src="https://analytics.coollabs.io/js/plausible.js"></script>
|
||||
@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>
|
||||
@section('body')
|
||||
|
||||
<body>
|
||||
@livewireScripts
|
||||
@livewire('livewire-ui-modal')
|
||||
<dialog id="help" class="modal">
|
||||
<livewire:help />
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
@@ -37,7 +46,47 @@
|
||||
</dialog>
|
||||
<x-toaster-hub />
|
||||
<x-version class="fixed left-2 bottom-1" />
|
||||
|
||||
<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 checkIfIamDeadInterval = null;
|
||||
|
||||
|
||||
@@ -131,16 +131,16 @@
|
||||
</form>
|
||||
</div>
|
||||
@if (!$serverReachable)
|
||||
This server is not reachable with the following public key.
|
||||
<br /> <br />
|
||||
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
||||
server.
|
||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||
<x-forms.button class="box" wire:target="validateServer"
|
||||
wire:click="validateServer">Check again
|
||||
</x-forms.button>
|
||||
@endif
|
||||
This server is not reachable with the following public key.
|
||||
<br /> <br />
|
||||
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for user
|
||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
||||
server.
|
||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||
<x-forms.button class="box" wire:target="validateServer" wire:click="validateServer">Check
|
||||
again
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</x-slot:actions>
|
||||
<x-slot:explanation>
|
||||
<p>Private Keys are used to connect to a remote server through a secure shell, called SSH.</p>
|
||||
@@ -200,14 +200,18 @@
|
||||
label="Description" id="remoteServerDescription" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address"
|
||||
id="remoteServerHost" />
|
||||
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost" />
|
||||
<x-forms.input required placeholder="Port number of your server. Default is 22."
|
||||
label="Port" id="remoteServerPort" />
|
||||
<x-forms.input required readonly
|
||||
placeholder="Username to connect to your server. Default is root." label="Username"
|
||||
id="remoteServerUser" />
|
||||
</div>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox
|
||||
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>"
|
||||
id="isCloudflareTunnel" label="Cloudflare Tunnel" />
|
||||
</div>
|
||||
<x-forms.button type="submit">Check Connection</x-forms.button>
|
||||
</form>
|
||||
</x-slot:actions>
|
||||
@@ -226,7 +230,7 @@
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center box" wire:click="installDocker">
|
||||
Let's do it!</x-forms.button>
|
||||
Let's do it!</x-forms.button>
|
||||
@if ($dockerInstallationStarted)
|
||||
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
||||
Validate Server & Continue</x-forms.button>
|
||||
@@ -234,7 +238,10 @@
|
||||
</x-slot:actions>
|
||||
<x-slot:explanation>
|
||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker Engine, check <a target="_blank" class="underline text-warning" href="https://coolify.io/docs/servers#install-docker-engine-manually">this documentation</a>.</p>
|
||||
to run optimal.<br><br>Minimum Docker Engine version is: 22<br><br>To manually install Docker
|
||||
Engine, check <a target="_blank" class="underline text-warning"
|
||||
href="https://coolify.io/docs/servers#install-docker-engine-manually">this
|
||||
documentation</a>.</p>
|
||||
</x-slot:explanation>
|
||||
</x-boarding-step>
|
||||
|
||||
|
||||
32
resources/views/livewire/modal/edit-compose.blade.php
Normal file
32
resources/views/livewire/modal/edit-compose.blade.php
Normal 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>
|
||||
@@ -67,23 +67,43 @@
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose')
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
<h3>Docker Registry</h3>
|
||||
<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>
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<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>
|
||||
@else
|
||||
@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>
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
@if ($application->build_pack === 'dockerimage')
|
||||
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||
@else
|
||||
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||
@endif
|
||||
@else
|
||||
<x-forms.input id="application.docker_registry_image_name"
|
||||
helper="Empty means it won't push the image to a docker registry."
|
||||
placeholder="Empty means it won't push the image to a docker registry."
|
||||
label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag"
|
||||
placeholder="Empty means only push commit sha tag."
|
||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||
label="Docker Image Tag" />
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag"
|
||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||
label="Docker Image Tag" />
|
||||
@else
|
||||
<x-forms.input id="application.docker_registry_image_name"
|
||||
helper="Empty means it won't push the image to a docker registry."
|
||||
placeholder="Empty means it won't push the image to a docker registry."
|
||||
label="Docker Image" />
|
||||
<x-forms.input id="application.docker_registry_image_tag"
|
||||
placeholder="Empty means only push commit sha tag."
|
||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||
label="Docker Image Tag" />
|
||||
@endif
|
||||
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@@ -140,8 +160,8 @@
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
|
||||
<x-forms.textarea rows="10" readonly id="application.docker_compose" label="Docker Compose Content"
|
||||
helper="You need to modify the docker compose file." />
|
||||
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
||||
label="Docker Compose Content" helper="You need to modify the docker compose file." />
|
||||
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
|
||||
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
|
||||
@endif
|
||||
|
||||
@@ -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>
|
||||
@@ -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.compose-modal :raw="$service->docker_compose_raw" :actual="$service->docker_compose" />
|
||||
<div class="flex h-full pt-6">
|
||||
<div class="flex flex-col items-start gap-4 min-w-fit">
|
||||
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
|
||||
@@ -8,6 +7,10 @@
|
||||
@click.prevent="activeTab = 'service-stack';
|
||||
window.location.hash = 'service-stack'"
|
||||
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'"
|
||||
@click.prevent="activeTab = 'storages';
|
||||
window.location.hash = 'storages'"
|
||||
@@ -113,6 +116,9 @@
|
||||
<div x-cloak x-show="activeTab === 'webhooks'">
|
||||
<livewire:project.shared.webhooks :resource="$service" />
|
||||
</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'">
|
||||
<livewire:project.shared.environment-variable.all :resource="$service" />
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<div>Configuration</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
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>
|
||||
</div>
|
||||
{{-- {{$resource->additional_destinations}} --}}
|
||||
{{-- Additonal Destinations:
|
||||
{{$resource->additional_destinations}} --}}
|
||||
{{-- @if (count($servers) > 0)
|
||||
<div>
|
||||
<h3>Additional Servers</h3>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="font-bold text-warning">({{ $env->key }})</span>?</p>
|
||||
</x-slot:modalBody>
|
||||
</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)
|
||||
<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">
|
||||
|
||||
@@ -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>
|
||||
@@ -7,7 +7,7 @@
|
||||
@if ($loop->first)
|
||||
<h2 class="pb-4">Logs</h2>
|
||||
@endif
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
|
||||
@empty
|
||||
<div>No containers are not running.</div>
|
||||
@endforelse
|
||||
@@ -16,7 +16,7 @@
|
||||
<h1>Logs</h1>
|
||||
<livewire:project.database.heading :database="$resource" />
|
||||
<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>
|
||||
@elseif ($type === 'service')
|
||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
|
||||
@@ -28,7 +28,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<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>
|
||||
@endif
|
||||
|
||||
@@ -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>."
|
||||
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Manual Git Webhooks</h3>
|
||||
@if ($githubManualWebhook && $gitlabManualWebhook)
|
||||
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
|
||||
readonly label="GitHub" id="githubManualWebhook"></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 GitHub."
|
||||
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
|
||||
@if ($resource->type() !== 'service')
|
||||
<div>
|
||||
<h3>Manual Git Webhooks</h3>
|
||||
@if ($githubManualWebhook && $gitlabManualWebhook)
|
||||
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
|
||||
readonly label="GitHub" id="githubManualWebhook"></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 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>
|
||||
|
||||
@@ -25,10 +25,17 @@
|
||||
@if ($loop->first)
|
||||
<h3 class="pt-4">Defined resources</h3>
|
||||
@endif
|
||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||
<div>{{ $resource->name }}</div>
|
||||
</a>
|
||||
@if ($resource->link())
|
||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||
<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
|
||||
@endforelse
|
||||
</div>
|
||||
@@ -38,10 +45,17 @@
|
||||
@if ($loop->first)
|
||||
<h3 class="pt-4">Defined resources</h3>
|
||||
@endif
|
||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||
<div>{{ $resource->name }}</div>
|
||||
</a>
|
||||
@if ($resource->link())
|
||||
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
|
||||
<div class="w-64">{{ str($resource->type())->headline() }}</div>
|
||||
<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
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
<x-forms.input id="server.description" label="Description" />
|
||||
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
|
||||
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
|
||||
{{-- <x-forms.checkbox disabled type="checkbox" id="server.settings.is_part_of_swarm"
|
||||
label="Is it part of a Swarm cluster?" /> --}}
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input id="server.ip" label="IP Address/Domain"
|
||||
@@ -49,13 +48,17 @@
|
||||
<x-forms.input type="number" id="server.port" label="Port" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (!$server->isLocalhost())
|
||||
<div class="w-64">
|
||||
<div class="w-64">
|
||||
@if (!$server->isLocalhost())
|
||||
<x-forms.checkbox instantSave
|
||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
|
||||
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
{{-- <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
|
||||
label="Is it a Swarm Manager?" /> --}}
|
||||
{{-- <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
|
||||
label="Is it a Swarm Worker?" /> --}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($server->isFunctional())
|
||||
|
||||
@@ -62,6 +62,25 @@
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user