diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php new file mode 100644 index 000000000..8dd64a246 --- /dev/null +++ b/app/Console/Commands/Horizon.php @@ -0,0 +1,21 @@ +info('Horizon is enabled. Starting.'); + $this->call('horizon'); + exit(0); + } else { + exit(0); + } + } +} diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php new file mode 100644 index 000000000..eab623802 --- /dev/null +++ b/app/Console/Commands/Scheduler.php @@ -0,0 +1,21 @@ +info('Scheduler is enabled. Starting.'); + $this->call('schedule:work'); + exit(0); + } else { + exit(0); + } + } +} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 1346a6ded..98c189275 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -298,6 +298,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted "ignore_errors" => true, ] ); + + // $this->execute_remote_command( // [ // "docker image prune -f >/dev/null 2>&1", @@ -305,6 +307,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted // "ignore_errors" => true, // ] // ); + + ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); } } @@ -417,7 +421,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true ]); } - $this->write_deployment_configurations(); // Start compose file if ($this->application->settings->is_raw_compose_deployment_enabled) { @@ -425,7 +428,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->execute_remote_command( [executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], ); + $this->write_deployment_configurations(); } else { + $this->write_deployment_configurations(); $server_workdir = $this->application->workdir(); ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"); $this->execute_remote_command( @@ -437,14 +442,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->execute_remote_command( [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], ); + $this->write_deployment_configurations(); } else { $this->execute_remote_command( [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], ); + $this->write_deployment_configurations(); } } - $this->application_deployment_queue->addLogEntry("New container started."); } private function deploy_dockerfile_buildpack() @@ -822,6 +828,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } private function deploy_pull_request() { + if ($this->application->build_pack === 'dockercompose') { + $this->deploy_docker_compose_buildpack(); + return; + } if ($this->use_build_server) { $this->server = $this->build_server; } diff --git a/app/Livewire/Notifications/EmailSettings.php b/app/Livewire/Notifications/EmailSettings.php index b6152907d..a45381b84 100644 --- a/app/Livewire/Notifications/EmailSettings.php +++ b/app/Livewire/Notifications/EmailSettings.php @@ -119,16 +119,18 @@ class EmailSettings extends Component { try { $this->resetErrorBag(); - $this->validate([ - 'team.smtp_from_address' => 'required|email', - 'team.smtp_from_name' => 'required', - 'team.smtp_host' => 'required', - 'team.smtp_port' => 'required|numeric', - 'team.smtp_encryption' => 'nullable', - 'team.smtp_username' => 'nullable', - 'team.smtp_password' => 'nullable', - 'team.smtp_timeout' => 'nullable', - ]); + if (!$this->team->use_instance_email_settings) { + $this->validate([ + 'team.smtp_from_address' => 'required|email', + 'team.smtp_from_name' => 'required', + 'team.smtp_host' => 'required', + 'team.smtp_port' => 'required|numeric', + 'team.smtp_encryption' => 'nullable', + 'team.smtp_username' => 'nullable', + 'team.smtp_password' => 'nullable', + 'team.smtp_timeout' => 'nullable', + ]); + } $this->team->save(); refreshSession(); $this->dispatch('success', 'Settings saved.'); diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index 520848a54..d8e033b24 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -9,7 +9,7 @@ use Livewire\Component; class Index extends Component { public Application $application; - public array|Collection $deployments = []; + public ?Collection $deployments; public int $deployments_count = 0; public string $current_url; public int $skip = 0; @@ -48,9 +48,9 @@ class Index extends Component } private function show_more() { - if (count($this->deployments) !== 0) { + if ($this->deployments->count() !== 0) { $this->show_next = true; - if (count($this->deployments) < $this->default_take) { + if ($this->deployments->count() < $this->default_take) { $this->show_next = false; } return; @@ -63,7 +63,6 @@ class Index extends Component } public function previous_page(?int $take = null) { - if ($take) { $this->skip = $this->skip - $take; } diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index 3cc5428b8..3a7a3fa23 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -33,19 +33,23 @@ class Add extends Component public function submit() { - $this->validate(); - $isValid = validate_cron_expression($this->frequency); - if (!$isValid) { - $this->dispatch('error', 'Invalid Cron / Human expression.'); - return; + try { + $this->validate(); + $isValid = validate_cron_expression($this->frequency); + if (!$isValid) { + $this->dispatch('error', 'Invalid Cron / Human expression.'); + return; + } + $this->dispatch('saveScheduledTask', [ + 'name' => $this->name, + 'command' => $this->command, + 'frequency' => $this->frequency, + 'container' => $this->container, + ]); + $this->clear(); + } catch (\Exception $e) { + return handleError($e, $this); } - $this->dispatch('saveScheduledTask', [ - 'name' => $this->name, - 'command' => $this->command, - 'frequency' => $this->frequency, - 'container' => $this->container, - ]); - $this->clear(); } public function clear() diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php index eba25a750..75ed06f7b 100644 --- a/app/Livewire/Tags/Index.php +++ b/app/Livewire/Tags/Index.php @@ -2,14 +2,73 @@ namespace App\Livewire\Tags; +use App\Http\Controllers\Api\Deploy; +use App\Models\ApplicationDeploymentQueue; use App\Models\Tag; +use Illuminate\Support\Collection; +use Livewire\Attributes\Url; use Livewire\Component; class Index extends Component { - public $tags = []; - public function mount() { - $this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name'); + #[Url()] + public ?string $tag = null; + + public Collection $tags; + public Collection $applications; + public Collection $services; + public $webhook = null; + public $deployments_per_tag_per_server = []; + + public function updatedTag() + { + $tag = $this->tags->where('name', $this->tag)->first(); + $this->webhook = generatTagDeployWebhook($tag->name); + $this->applications = $tag->applications()->get(); + $this->services = $tag->services()->get(); + $this->get_deployments(); + } + public function get_deployments() + { + try { + $resource_ids = $this->applications->pluck('id'); + $this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([ + "id", + "application_id", + "application_name", + "deployment_url", + "pull_request_id", + "server_name", + "server_id", + "status" + ])->sortBy('id')->groupBy('server_name')->toArray(); + } catch (\Exception $e) { + return handleError($e, $this); + } + } + public function redeploy_all() + { + try { + $message = collect([]); + $this->applications->each(function ($resource) use ($message) { + $deploy = new Deploy(); + $message->push($deploy->deploy_resource($resource)); + }); + $this->services->each(function ($resource) use ($message) { + $deploy = new Deploy(); + $message->push($deploy->deploy_resource($resource)); + }); + $this->dispatch('success', 'Mass deployment started.'); + } catch (\Exception $e) { + return handleError($e, $this); + } + } + public function mount() + { + $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name'); + if ($this->tag) { + $this->updatedTag(); + } } public function render() { diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php index c9f4ebdc0..350039fce 100644 --- a/app/View/Components/Forms/Button.php +++ b/app/View/Components/Forms/Button.php @@ -16,7 +16,7 @@ class Button extends Component public bool $isModal = false, public bool $noStyle = false, public ?string $modalId = null, - public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none" + public string $defaultClass = "button" ) { if ($this->noStyle) { $this->defaultClass = ""; diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index 0bbbc1e04..36b561eda 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -12,14 +12,14 @@ class Checkbox extends Component * Create a new component instance. */ public function __construct( - public string|null $id = null, - public string|null $name = null, - public string|null $value = null, - public string|null $label = null, - public string|null $helper = null, - public string|bool $instantSave = false, + public ?string $id = null, + public ?string $name = null, + public ?string $value = null, + public ?string $label = null, + public ?string $helper = null, + public string|bool $instantSave = false, public bool $disabled = false, - public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700", + public string $defaultClass = "border-coolgray-500 text-warning focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed", ) { // } diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index a55f9b9d2..56349f6db 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -21,7 +21,7 @@ class Input extends Component public ?string $helper = null, public bool $allowToPeak = true, public bool $isMultiline = false, - public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50" + public string $defaultClass = "input", ) { } diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index e1f2fc759..99bcf1873 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -14,12 +14,12 @@ class Select extends Component * Create a new component instance. */ public function __construct( - public string|null $id = null, - public string|null $name = null, - public string|null $label = null, - public string|null $helper = null, - public bool $required = false, - public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none" + public ?string $id = null, + public ?string $name = null, + public ?string $label = null, + public ?string $helper = null, + public bool $required = false, + public string $defaultClass = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:ring-coolgray-300 dark:placeholder:text-neutral-700 focus:ring-2 focus:ring-inset dark:focus:ring-coolgray-500 dark:bg-coolgray-100 dark:text-white text-black " ) { // } diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index 8c50af533..c9d2c26ba 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -25,8 +25,8 @@ class Textarea extends Component public ?string $helper = null, public bool $realtimeValidation = false, public bool $allowToPeak = true, - public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white w-full scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50", - public string $defaultClassInput = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50" + public string $defaultClass = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 scrollbar dark:read-only:text-neutral-500", + public string $defaultClassInput = "block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500" ) { // } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index c9f98e48a..7810e0155 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1240,84 +1240,95 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $baseName = generateApplicationContainerName($resource, $pull_request_id); $containerName = "$serviceName-$baseName"; - if ($pull_request_id !== 0) { - if (count($serviceVolumes) > 0) { - $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) { - if (is_string($volume)) { - $volume = str($volume); - if ($volume->contains(':') && !$volume->startsWith('/')) { - $name = $volume->before(':'); - $mount = $volume->after(':'); - $newName = $resource->uuid . "-{$name}-pr-$pull_request_id"; - $volume = str("$newName:$mount"); - $topLevelVolumes->put($newName, [ - 'name' => $newName, - ]); - } - } else if (is_array($volume)) { - $source = data_get($volume, 'source'); - if ($source) { - $newSource = $resource->uuid . "-{$source}-pr-$pull_request_id"; - data_set($volume, 'source', $newSource); - if (!str($source)->startsWith('/')) { - $topLevelVolumes->put($newSource, [ - 'name' => $newSource, - ]); + if (count($serviceVolumes) > 0) { + $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { + 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); } - } - } - 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); - } + if ($name->startsWith('~')) { + $name = $name->replaceFirst('~', $dir); + } + if ($pull_request_id !== 0) { + $name = $name . "-pr-$pull_request_id"; + } + $volume = str("$name:$mount"); + } else { + if ($pull_request_id !== 0) { + $name = $name . "-pr-$pull_request_id"; $volume = str("$name:$mount"); + $topLevelVolumes->put($name, [ + 'name' => $name, + ]); } 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 { + if ($volume->startsWith('/')) { + $name = $volume->before(':'); + $mount = $volume->after(':'); + if ($pull_request_id !== 0) { + $name = $name . "-pr-$pull_request_id"; + } + $volume = str("$name:$mount"); + } + } + + } else if (is_array($volume)) { + $source = data_get($volume, 'source'); + $target = data_get($volume, 'target'); + $read_only = data_get($volume, 'read_only'); + if ($source && $target) { + if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + if (str($source, '.')) { + $source = str($source)->replaceFirst('.', $dir); + } + if (str($source, '~')) { + $source = str($source)->replaceFirst('~', $dir); + } + if ($pull_request_id !== 0) { + $source = $source . "-pr-$pull_request_id"; + } + if ($read_only) { + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source); + data_set($volume, 'source', $source . ':' . $target); + } + } else { + if ($pull_request_id !== 0) { + $source = $source . "-pr-$pull_request_id"; + } + if ($read_only) { + data_set($volume, 'source', $source . ':' . $target . ':ro'); + } else { + data_set($volume, 'source', $source . ':' . $target); + } + if (!str($source)->startsWith('/')) { $topLevelVolumes->put($source, [ 'name' => $source, ]); } } } - return $volume->value(); - }); - data_set($service, 'volumes', $serviceVolumes->toArray()); - } + } + if (is_array($volume)) { + return data_get($volume, 'source'); + } + return $volume->value(); + }); + data_set($service, 'volumes', $serviceVolumes->toArray()); } + // Decide if the service is a database $isDatabase = isDatabaseImage(data_get_str($service, 'image')); data_set($service, 'is_database', $isDatabase); @@ -1602,6 +1613,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal return $service; }); + if ($pull_request_id !== 0) { + $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { + $services[$serviceName . "-pr-$pull_request_id"] = $service; + data_forget($services, $serviceName); + }); + } $finalServices = [ 'version' => $dockerComposeVersion, 'services' => $services->toArray(), diff --git a/config/coolify.php b/config/coolify.php index 69ec23146..a6d6d8581 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -12,4 +12,6 @@ return [ 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), + 'is_horizon_enabled' => env('HORIZON_ENABLED', true), + 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), ]; diff --git a/config/sentry.php b/config/sentry.php index 5d46ba7f3..b4c1aa5f4 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.240', + 'release' => '4.0.0-beta.241', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index c7e1008fc..f00cd2d57 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file + } > /etc/php/current_version/cli/conf.d/upload-limits.ini diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run index cec4f1dda..87471097e 100644 --- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run +++ b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - webuser -c "php /var/www/html/artisan horizon" + su - webuser -c "php /var/www/html/artisan start:horizon" } diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run index 0f205c897..87ca0cae1 100644 --- a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -1,5 +1,5 @@ #!/command/execlineb -P foreground { s6-sleep 5 - su - webuser -c "php /var/www/html/artisan schedule:work" + su - webuser -c "php /var/www/html/artisan start:scheduler" } diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile index d5ba465b7..7d0a4d373 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod-ssu/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /var/www/html COPY composer.json composer.lock ./ RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist -FROM node:19 as static-assets +FROM node:20 as static-assets WORKDIR /app COPY . . COPY --from=base --chown=9999:9999 /var/www/html . @@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx ARG TARGETPLATFORM # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2023.10.0 +ARG CLOUDFLARED_VERSION=2024.2.1 ARG POSTGRES_VERSION=15 WORKDIR /var/www/html @@ -66,4 +66,4 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ RUN { \ echo 'upload_max_filesize=256M'; \ echo 'post_max_size=256M'; \ - } > /etc/php/current_version/cli/conf.d/upload-limits.ini \ No newline at end of file + } > /etc/php/current_version/cli/conf.d/upload-limits.ini diff --git a/package-lock.json b/package-lock.json index b2124e9df..cc9889a18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "0.5.10", "alpinejs": "3.13.5", "daisyui": "4.7.2", @@ -484,6 +485,17 @@ "node": ">= 8" } }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@tailwindcss/typography": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz", @@ -1462,6 +1474,14 @@ "node": ">= 0.6" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 8568525e6..69c5c560a 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "vue": "3.4.21" }, "dependencies": { + "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "0.5.10", "alpinejs": "3.13.5", "daisyui": "4.7.2", diff --git a/resources/css/app.css b/resources/css/app.css index 167b7c44a..6ac74f8a3 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -2,22 +2,141 @@ @tailwind components; @tailwind utilities; -html { - @apply text-neutral-400; +html, +body { + @apply text-black bg-white dark:bg-base dark:text-neutral-400; } body { @apply text-sm antialiased scrollbar; } -button[isError] { +button[isError]:not(:disabled) { @apply bg-red-600 hover:bg-red-700; } -button[isHighlighted] { +button[isHighlighted]:not(:disabled) { @apply bg-coollabs hover:bg-coollabs-100; } +.button { + @apply px-3 py-1.5 text-sm font-normal normal-case rounded dark:bg-coolgray-200 dark:text-white dark:hover:bg-coolgray-100 dark:disabled:bg-coolgray-100/40 dark:disabled:text-neutral-800 min-w-fit flex items-center justify-center; +} + +h1 { + @apply text-2xl font-bold dark:text-white text-neutral-800; +} + +h2 { + @apply text-xl font-bold dark:text-white text-neutral-800; +} + +h3 { + @apply text-lg font-bold dark:text-white text-neutral-800; +} + +h4 { + @apply text-base font-bold dark:text-white text-neutral-800; +} + +a { + @apply dark:hover:text-white dark:text-neutral-400 text-neutral-600; +} + +label { + @apply dark:text-neutral-400 text-neutral-600; +} + +table { + @apply min-w-full divide-y divide-coolgray-200; +} + +thead { + @apply uppercase; +} + +tbody { + @apply divide-y divide-coolgray-200; +} + +tr { + @apply text-neutral-400; +} + +tr th { + @apply px-3 py-3.5 text-left text-white; +} + +tr th:first-child { + @apply py-3.5 pl-4 pr-3 sm:pl-6; +} + +tr td { + @apply px-3 py-4 whitespace-nowrap; +} + +tr td:first-child { + @apply pl-4 pr-3 font-bold sm:pl-6; +} + +input { + @apply pr-10; +} +.input { + @apply block w-full py-1.5 rounded border-0 text-sm ring-inset ring-1 dark:bg-coolgray-100 dark:text-white text-black focus:ring-2 dark:focus:ring-coolgray-300 dark:ring-coolgray-300 dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 dark:placeholder:text-neutral-700; +} + +option { + @apply text-white; +} +.alert-success { + @apply flex items-center gap-2 text-success; +} +.alert-error { + @apply flex items-center gap-2 text-error; +} +.dropdown-item { + @apply relative flex cursor-pointer select-none dark:hover:text-white dark:hover:bg-coollabs items-center px-2 py-1.5 text-xs justify-center outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 +} + +.badge { + @apply inline-block w-3 h-3 text-xs font-bold leading-none border border-black rounded-full; +} + +.badge-success { + @apply bg-success; +} + +.badge-warning { + @apply bg-warning; +} + +.badge-error { + @apply bg-error; +} + + + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +.menu { + @apply flex items-center gap-1; +} + +.menu-item { + @apply flex items-center w-full gap-2 px-4 py-1 min-w-48 hover:bg-coolgray-100 dark:hover:text-white; +} + +.menu-item-active { + @apply rounded-none dark:bg-coolgray-200 dark:text-warning; +} + +.icon { + @apply w-4 h-4; +} + .scrollbar { @apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2; } @@ -27,12 +146,7 @@ button[isHighlighted] { } .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; + @apply z-50 flex flex-col gap-2 px-8 py-4 border dark:bg-coolgray-100 dark:border-coolgray-200; } .navbar-main { @@ -43,26 +157,6 @@ label { @apply w-4 text-warning; } -h1 { - @apply text-3xl font-bold text-white; -} - -h2 { - @apply text-2xl font-bold text-white; -} - -h3 { - @apply text-xl font-bold text-white; -} - -h4 { - @apply text-base font-bold text-white; -} - -a { - @apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent; -} - .kbd-custom { @apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning; } @@ -125,42 +219,13 @@ a { @apply inline-block font-bold text-warning; } -table { - @apply min-w-full divide-y divide-coolgray-200; -} - -thead { - @apply uppercase; -} - -tbody { - @apply divide-y divide-coolgray-200; -} - -tr { - @apply text-neutral-400; -} - -tr th { - @apply px-3 py-3.5 text-left text-white; -} - -tr th:first-child { - @apply py-3.5 pl-4 pr-3 sm:pl-6; -} - -tr td { - @apply px-3 py-4 whitespace-nowrap; -} - -tr td:first-child { - @apply pl-4 pr-3 font-bold sm:pl-6; -} - .buyme { @apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline; } +.title { + @apply hidden pb-0 lg:block lg:pb-8 ; +} .subtitle { @apply pt-2 pb-10; } @@ -169,13 +234,7 @@ tr td:first-child { @apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4; } -input.input-sm { - @apply pr-10; -} -option{ - @apply text-white; -} - .toast { z-index: 1; } + diff --git a/resources/js/components/MagicBar.vue b/resources/js/components/MagicBar.vue index 386a267b1..7fe770279 100644 --- a/resources/js/components/MagicBar.vue +++ b/resources/js/components/MagicBar.vue @@ -9,6 +9,7 @@ + / --}} + @if (isCloud() && isInstanceAdmin()) + + + + + Admin + + @endif +
+ @if (isInstanceAdmin() && !isCloud()) + @persist('upgrade') + + @endpersist + @endif + + + + + + + + Help us! + + + + + + +@endauth diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 4ba890c31..0271f41a0 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,243 +1,293 @@ -@auth - diff --git a/resources/views/components/popup.blade.php b/resources/views/components/popup.blade.php new file mode 100644 index 000000000..a1e2600f6 --- /dev/null +++ b/resources/views/components/popup.blade.php @@ -0,0 +1,30 @@ +@props(['title' => 'Default title', 'description' => 'Default Description', 'buttonText' => 'Default Button Text']) +
+
+
+ +
+

+ {{ $title }} +

+

{{ $description }}

+
+
+
+ +
+
+
diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index 6e2b234a3..4a4ea86f2 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -13,7 +13,7 @@ d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd">
- {{ $this->parameters['environment_name'] }} diff --git a/resources/views/components/security/navbar.blade.php b/resources/views/components/security/navbar.blade.php index e13a0e4ee..292aa857b 100644 --- a/resources/views/components/security/navbar.blade.php +++ b/resources/views/components/security/navbar.blade.php @@ -1,14 +1,7 @@

Security

- +
Security related settings.
+
-
{{ data_get($server, 'name') }}
+
{{ data_get($server, 'name') }}.
diff --git a/resources/views/components/slide-over.blade.php b/resources/views/components/slide-over.blade.php index 02dfa63c0..e62069b74 100644 --- a/resources/views/components/slide-over.blade.php +++ b/resources/views/components/slide-over.blade.php @@ -22,7 +22,7 @@ 'max-w-4xl w-screen' => $fullScreen, ])>
+ class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base border-neutral-800">

diff --git a/resources/views/components/status/degraded.blade.php b/resources/views/components/status/degraded.blade.php index 490ecc239..d91051c8e 100644 --- a/resources/views/components/status/degraded.blade.php +++ b/resources/views/components/status/degraded.blade.php @@ -1,13 +1,15 @@ @props([ 'status' => 'Degraded', ]) - -
-
-
- {{ str($status)->before(':')->headline() }} -
- @if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) -
({{ str($status)->after(':') }})
- @endif +
+ + +
+
+ {{ str($status)->before(':')->headline() }} +
+ @if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) +
({{ str($status)->after(':') }})
+ @endif +
diff --git a/resources/views/components/status/restarting.blade.php b/resources/views/components/status/restarting.blade.php index d9c353d02..d43a3a719 100644 --- a/resources/views/components/status/restarting.blade.php +++ b/resources/views/components/status/restarting.blade.php @@ -1,13 +1,15 @@ @props([ 'status' => 'Restarting', ]) - -
-
-
- {{ str($status)->before(':')->headline() }} -
- @if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) -
({{ str($status)->after(':') }})
- @endif +
+ + +
+
+ {{ str($status)->before(':')->headline() }} +
+ @if (!str($status)->startsWith('Proxy') && !str($status)->contains('(')) +
({{ str($status)->after(':') }})
+ @endif +
diff --git a/resources/views/components/status/running.blade.php b/resources/views/components/status/running.blade.php index 4751204ce..5bcb45cf5 100644 --- a/resources/views/components/status/running.blade.php +++ b/resources/views/components/status/running.blade.php @@ -1,13 +1,15 @@ @props([ 'status' => 'Running', ]) - -
-
+
+ + +
{{ str($status)->before(':')->headline() }}
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
({{ str($status)->after(':') }})
@endif +
diff --git a/resources/views/components/status/stopped.blade.php b/resources/views/components/status/stopped.blade.php index ef84e915a..f168fb3c2 100644 --- a/resources/views/components/status/stopped.blade.php +++ b/resources/views/components/status/stopped.blade.php @@ -1,8 +1,10 @@ @props([ 'status' => 'Stopped', ]) - -
-
-
{{ str($status)->before(':')->headline() }}
+
+ + +
+
{{ str($status)->before(':')->headline() }}
+
diff --git a/resources/views/components/version.blade.php b/resources/views/components/version.blade.php index a5ffdac32..8a9a710c1 100644 --- a/resources/views/components/version.blade.php +++ b/resources/views/components/version.blade.php @@ -1,2 +1,2 @@ -merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-50']) }} +merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-[60]']) }} href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }} diff --git a/resources/views/destination/all.blade.php b/resources/views/destination/all.blade.php index 0a1358f9d..8aac647b9 100644 --- a/resources/views/destination/all.blade.php +++ b/resources/views/destination/all.blade.php @@ -1,6 +1,6 @@

Destinations

-
All Destinations
+
All Destinations.
@forelse ($destinations as $destination) @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker') diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 7d74f7acb..f1d100ade 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -1,17 +1,64 @@ @extends('layouts.base') @section('body') @parent - - @persist('magic-bar') -
- -
- @endpersist @auth @endauth -
- {{ $slot }} -
+ @auth +
+ @endauth @endsection diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 44ef50f53..70a0fed0b 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -1,5 +1,5 @@ - + @@ -38,16 +38,27 @@ @section('body') + {{-- --}} @livewire('wire-elements-modal') - - - - - + - - - + + + + })) + }) + + @endif
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 449c83dbc..8deccc889 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -51,6 +51,7 @@ helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the documentation." />
@if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled) +

Domains

@foreach (data_get($parsedServices, 'services') as $serviceName => $service) @if (!isDatabaseImage(data_get($service, 'image')))
diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 6aa9f1d28..91e9c0504 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -15,8 +15,8 @@ @isset($rate_limit_remaining)
Requests remaining till rate limited by Git: {{ $rate_limit_remaining }}
@endisset - @if (count($pull_requests) > 0) -
+
+ @if ($pull_requests->count() > 0)
@@ -50,8 +50,8 @@
-
- @endif + @endif +
@if ($application->previews->count() > 0)
Previews
diff --git a/resources/views/livewire/project/application/rollback.blade.php b/resources/views/livewire/project/application/rollback.blade.php index 5d8023391..a5b8f9a90 100644 --- a/resources/views/livewire/project/application/rollback.blade.php +++ b/resources/views/livewire/project/application/rollback.blade.php @@ -3,13 +3,13 @@

Rollback

Reload Available Images
-
You can easily rollback to a previously built (local) images +
You can easily rollback to a previously built (local) images quickly.
@forelse ($images as $image)
-
+
@if (data_get($image, 'is_current')) diff --git a/resources/views/livewire/project/clone-me.blade.php b/resources/views/livewire/project/clone-me.blade.php index 893b5cee5..c7389079d 100644 --- a/resources/views/livewire/project/clone-me.blade.php +++ b/resources/views/livewire/project/clone-me.blade.php @@ -1,7 +1,7 @@

Clone

-
Quickly clone all resources to a new project or environment
+
Quickly clone all resources to a new project or environment.
Clone to a new Project diff --git a/resources/views/livewire/project/database/import.blade.php b/resources/views/livewire/project/database/import.blade.php index 7c7f3e148..fe642c206 100644 --- a/resources/views/livewire/project/database/import.blade.php +++ b/resources/views/livewire/project/database/import.blade.php @@ -1,6 +1,6 @@

Import Backup

-
+
@@ -23,7 +23,7 @@ @endif -
diff --git a/resources/views/livewire/project/database/postgresql/general.blade.php b/resources/views/livewire/project/database/postgresql/general.blade.php index 50201e115..f00a0a870 100644 --- a/resources/views/livewire/project/database/postgresql/general.blade.php +++ b/resources/views/livewire/project/database/postgresql/general.blade.php @@ -82,7 +82,7 @@

Initialization scripts

- + Add + + Add
@forelse(data_get($database,'init_scripts', []) as $script) diff --git a/resources/views/livewire/project/delete-environment.blade.php b/resources/views/livewire/project/delete-environment.blade.php index 6173721a3..30b8235da 100644 --- a/resources/views/livewire/project/delete-environment.blade.php +++ b/resources/views/livewire/project/delete-environment.blade.php @@ -1,3 +1,3 @@ - + This environment will be deleted. It is not reversible.
Please think again. -
+ diff --git a/resources/views/livewire/project/delete-project.blade.php b/resources/views/livewire/project/delete-project.blade.php index 095c139f4..b49ab4033 100644 --- a/resources/views/livewire/project/delete-project.blade.php +++ b/resources/views/livewire/project/delete-project.blade.php @@ -1,3 +1,3 @@ - + This project will be deleted. It is not reversible.
Please think again. -
+ diff --git a/resources/views/livewire/project/edit.blade.php b/resources/views/livewire/project/edit.blade.php index 164ba34cd..4118efa55 100644 --- a/resources/views/livewire/project/edit.blade.php +++ b/resources/views/livewire/project/edit.blade.php @@ -20,7 +20,7 @@
diff --git a/resources/views/livewire/project/environment-edit.blade.php b/resources/views/livewire/project/environment-edit.blade.php index 758d737d3..7cd80308f 100644 --- a/resources/views/livewire/project/environment-edit.blade.php +++ b/resources/views/livewire/project/environment-edit.blade.php @@ -50,7 +50,7 @@
diff --git a/resources/views/livewire/project/index.blade.php b/resources/views/livewire/project/index.blade.php index 4e3f28c9f..41c17e53c 100644 --- a/resources/views/livewire/project/index.blade.php +++ b/resources/views/livewire/project/index.blade.php @@ -1,12 +1,18 @@
-

Projects

+

Projects

@if ($servers > 0) - + Add - + + New Project + + + + + @endif
-
All Projects
+
All your projects.