diff --git a/.env.windows-docker-desktop.example b/.env.windows-docker-desktop.example new file mode 100644 index 0000000000..02a5a41742 --- /dev/null +++ b/.env.windows-docker-desktop.example @@ -0,0 +1,12 @@ +IS_WINDOWS_DOCKER_DESKTOP=true + +APP_ID=coolify-windows-docker-desktop +APP_NAME=Coolify +APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80= + +DB_PASSWORD=coolify +REDIS_PASSWORD=coolify + +PUSHER_APP_ID=coolify +PUSHER_APP_KEY=coolify +PUSHER_APP_SECRET=coolify diff --git a/.github/workflows/coolify-testing-host.yml b/.github/workflows/coolify-testing-host.yml new file mode 100644 index 0000000000..a40dfd2857 --- /dev/null +++ b/.github/workflows/coolify-testing-host.yml @@ -0,0 +1,84 @@ +name: Coolify Testing Host (v4-non-prod) + +on: + push: + branches: [ "main", "next" ] + paths: + - .github/workflows/coolify-testing-host.yml + - docker/testing-host/Dockerfile + +env: + REGISTRY: ghcr.io + IMAGE_NAME: "coollabsio/coolify-testing-host" + +jobs: + amd64: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v3 + - name: Login to ghcr.io + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build image and push to registry + uses: docker/build-push-action@v3 + with: + no-cache: true + context: . + file: docker/testing-host/Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + aarch64: + runs-on: [ self-hosted, arm64 ] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v3 + - name: Login to ghcr.io + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build image and push to registry + uses: docker/build-push-action@v3 + with: + no-cache: true + context: . + file: docker/testing-host/Dockerfile + platforms: linux/aarch64 + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 + merge-manifest: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [ amd64, aarch64 ] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to ghcr.io + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create & publish manifest + run: | + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} diff --git a/.github/workflows/development-build.yml b/.github/workflows/development-build.yml index 7d9b730c42..d64af3b59a 100644 --- a/.github/workflows/development-build.yml +++ b/.github/workflows/development-build.yml @@ -15,15 +15,15 @@ jobs: amd64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: docker/prod-ssu/Dockerfile @@ -36,15 +36,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . file: docker/prod-ssu/Dockerfile @@ -59,13 +59,13 @@ jobs: needs: [amd64, aarch64] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 5591452691..36321d96bb 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -12,9 +12,9 @@ jobs: amd64: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -24,7 +24,7 @@ jobs: run: | echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: docker/prod-ssu/Dockerfile @@ -34,9 +34,9 @@ jobs: aarch64: runs-on: [self-hosted, arm64] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -46,7 +46,7 @@ jobs: run: | echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: docker/prod-ssu/Dockerfile @@ -61,13 +61,13 @@ jobs: needs: [amd64, aarch64] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -78,7 +78,7 @@ jobs: echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - uses: sarisia/actions-status-discord@v1 if: always() with: diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index b557476a65..c0958acb83 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -140,7 +140,7 @@ private function generate_environment_variables() { $environment_variables = collect(); foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index fb60d778fc..3b9b07c49d 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -156,7 +156,7 @@ private function generate_environment_variables() { $environment_variables = collect(); foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) { diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 12bbc03ee0..b7ce747715 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -140,7 +140,7 @@ private function generate_environment_variables() { $environment_variables = collect(); foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index edf4a27e1b..e1427ae538 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -164,7 +164,7 @@ private function generate_environment_variables() ray('Generate Environment Variables')->green(); ray($this->database->runtime_environment_variables)->green(); foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) { diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index dc17636a78..966015746e 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -151,7 +151,7 @@ private function generate_environment_variables() { $environment_variables = collect(); foreach ($this->database->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index d9a7176a81..0de41f32a2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -220,7 +220,6 @@ public function handle(): void $this->build_server = $this->server; $this->original_server = $this->server; } - ray($this->build_server); try { if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { $this->just_restart(); @@ -311,7 +310,6 @@ private function write_deployment_configurations() } private function push_to_docker_registry($forceFail = false) { - ray((str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())); if ( $this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && @@ -383,7 +381,7 @@ private function generate_image_names() } private function just_restart() { - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->set_base_dir(); @@ -417,11 +415,11 @@ private function save_environment_variables() $envs = collect([]); if ($this->pull_request_id !== 0) { foreach ($this->application->environment_variables_preview as $env) { - $envs->push($env->key . '=' . $env->value); + $envs->push($env->key . '=' . $env->real_value); } } else { foreach ($this->application->environment_variables as $env) { - $envs->push($env->key . '=' . $env->value); + $envs->push($env->key . '=' . $env->real_value); } } $envs_base64 = base64_encode($envs->implode("\n")); @@ -929,11 +927,11 @@ private function generate_nixpacks_env_variables() $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - $this->env_nixpacks_args->push("--env {$env->key}={$env->value}"); + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - $this->env_nixpacks_args->push("--env {$env->key}={$env->value}"); + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); } } @@ -944,11 +942,11 @@ private function generate_env_variables() $this->env_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - $this->env_args->put($env->key, $env->value); + $this->env_args->put($env->key, $env->real_value); } } else { foreach ($this->application->build_environment_variables_preview as $env) { - $this->env_args->put($env->key, $env->value); + $this->env_args->put($env->key, $env->real_value); } } $this->env_args->put('SOURCE_COMMIT', $this->commit); @@ -1159,22 +1157,19 @@ private function generate_local_persistent_volumes_only_volume_names() private function generate_environment_variables($ports) { $environment_variables = collect(); - // ray('Generate Environment Variables')->green(); if ($this->pull_request_id === 0) { - // ray($this->application->runtime_environment_variables)->green(); foreach ($this->application->runtime_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } foreach ($this->application->nixpacks_environment_variables as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } } else { - // ray($this->application->runtime_environment_variables_preview)->green(); foreach ($this->application->runtime_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } foreach ($this->application->nixpacks_environment_variables_preview as $env) { - $environment_variables->push("$env->key=$env->value"); + $environment_variables->push("$env->key=$env->real_value"); } } // Add PORT if not exists, use the first port as default @@ -1457,12 +1452,12 @@ private function generate_build_env_variables() $this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - $value = escapeshellarg($env->value); + $value = escapeshellarg($env->real_value); $this->build_args->push("--build-arg {$env->key}={$value}"); } } else { foreach ($this->application->build_environment_variables_preview as $env) { - $value = escapeshellarg($env->value); + $value = escapeshellarg($env->real_value); $this->build_args->push("--build-arg {$env->key}={$value}"); } } @@ -1478,11 +1473,11 @@ private function add_build_env_variables_to_dockerfile() $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } } else { foreach ($this->application->build_environment_variables_preview as $env) { - $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } } $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 49511ad957..227807b417 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -38,6 +38,8 @@ public function __construct($task) $this->resource = $service; } else if ($application = $task->application()->first()) { $this->resource = $application; + } else { + throw new \Exception('ScheduledTaskJob failed: No resource found.'); } $this->team = Team::find($task->team_id); } diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index aa5ea3a2da..4c5d2135ae 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -41,13 +41,10 @@ public function show_debug() public function cancel() { try { - $kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}"; - if ($this->application_deployment_queue->current_process_id) { - $process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers"); - if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) { - Process::run($kill_command); - } + $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; + if ($this->application_deployment_queue->logs) { $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); + $new_log_entry = [ 'command' => $kill_command, 'output' => "Deployment cancelled by user.", @@ -60,15 +57,17 @@ public function cancel() $this->application_deployment_queue->update([ 'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR), ]); + instant_remote_process([$kill_command], $this->server); } } catch (\Throwable $e) { + ray($e); return handleError($e, $this); } finally { $this->application_deployment_queue->update([ 'current_process_id' => null, 'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value, ]); - queue_next_deployment($this->application); + // queue_next_deployment($this->application); } } } diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 95dcbdf735..fd01790c34 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -3,7 +3,6 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\StopApplication; -use App\Events\ApplicationStatusChanged; use App\Jobs\ContainerStatusJob; use App\Jobs\ServerStatusJob; use App\Models\Application; @@ -56,6 +55,7 @@ public function deployNew() $this->setDeploymentUuid(); queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $this->deploymentUuid, force_rebuild: false, is_new_deployment: true, @@ -84,6 +84,7 @@ public function deploy(bool $force_rebuild = false) $this->setDeploymentUuid(); queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $this->deploymentUuid, force_rebuild: $force_rebuild, ); @@ -113,6 +114,7 @@ public function restartNew() $this->setDeploymentUuid(); queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $this->deploymentUuid, restart_only: true, is_new_deployment: true, @@ -129,6 +131,7 @@ public function restart() $this->setDeploymentUuid(); queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $this->deploymentUuid, restart_only: true, ); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 57cb433027..bd52061006 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -48,6 +48,7 @@ public function deploy(int $pull_request_id, string|null $pull_request_html_url } queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $this->deployment_uuid, force_rebuild: false, pull_request_id: $pull_request_id, diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php index a26d5cd475..042812760b 100644 --- a/app/Livewire/Project/Application/Rollback.php +++ b/app/Livewire/Project/Application/Rollback.php @@ -25,6 +25,7 @@ public function rollbackImage($commit) queue_application_deployment( application_id: $this->application->id, + server_id: $this->application->destination->server->id, deployment_uuid: $deployment_uuid, commit: $commit, force_rebuild: false, diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index e674e3dd9f..b2cda7a47a 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -12,7 +12,24 @@ class Edit extends Component 'project.name' => 'required|min:3|max:255', 'project.description' => 'nullable|string|max:255', ]; - public function mount() { + protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; + + public function saveKey($data) + { + try { + $this->project->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'type' => 'project', + 'team_id' => currentTeam()->id, + ]); + $this->project->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function mount() + { $projectUuid = request()->route('project_uuid'); $teamId = currentTeam()->id; $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index a87e2092bc..276aa58df0 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -12,14 +12,30 @@ class EnvironmentEdit extends Component public Application $application; public $environment; public array $parameters; - protected $rules = [ 'environment.name' => 'required|min:3|max:255', 'environment.description' => 'nullable|min:3|max:255', ]; - public function mount() { - $this->parameters = get_route_parameters(); + protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; + public function saveKey($data) + { + try { + $this->environment->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'type' => 'environment', + 'team_id' => currentTeam()->id, + ]); + $this->environment->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function mount() + { + $this->parameters = get_route_parameters(); $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first(); $this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first(); } diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 4108727f35..012325ff15 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -39,6 +39,8 @@ class GithubPrivateRepository extends Component public bool $is_static = false; public string|null $publish_directory = null; protected int $page = 1; + public $build_pack = 'nixpacks'; + public bool $show_is_static = true; public function mount() @@ -49,6 +51,20 @@ public function mount() $this->repositories = $this->branches = collect(); $this->github_apps = GithubApp::private(); } + public function updatedBuildPack() + { + if ($this->build_pack === 'nixpacks') { + $this->show_is_static = true; + $this->port = 3000; + } else if ($this->build_pack === 'static') { + $this->show_is_static = false; + $this->is_static = false; + $this->port = 80; + } else { + $this->show_is_static = false; + $this->is_static = false; + } + } public function loadRepositories($github_app_id) { $this->repositories = collect(); @@ -95,7 +111,7 @@ public function loadBranches() $this->loadBranchByPage(); } } - $this->selected_branch_name = data_get($this->branches,'0.name'); + $this->selected_branch_name = data_get($this->branches, '0.name', 'main'); } protected function loadBranchByPage() diff --git a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index ba403ca1c1..723d4bb601 100644 --- a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -29,12 +29,17 @@ class GithubPrivateRepositoryDeployKey extends Component public string $repository_url; public string $branch; + + public $build_pack = 'nixpacks'; + public bool $show_is_static = true; + protected $rules = [ 'repository_url' => 'required', 'branch' => 'required|string', 'port' => 'required|numeric', 'is_static' => 'required|boolean', 'publish_directory' => 'nullable|string', + 'build_pack' => 'required|string', ]; protected $validationAttributes = [ 'repository_url' => 'Repository', @@ -42,6 +47,7 @@ class GithubPrivateRepositoryDeployKey extends Component 'port' => 'Port', 'is_static' => 'Is static', 'publish_directory' => 'Publish directory', + 'build_pack' => 'Build pack', ]; private object $repository_url_parsed; private GithubApp|GitlabApp|string $git_source = 'other'; @@ -62,6 +68,20 @@ public function mount() } } + public function updatedBuildPack() + { + if ($this->build_pack === 'nixpacks') { + $this->show_is_static = true; + $this->port = 3000; + } else if ($this->build_pack === 'static') { + $this->show_is_static = false; + $this->is_static = false; + $this->port = 80; + } else { + $this->show_is_static = false; + $this->is_static = false; + } + } public function instantSave() { if ($this->is_static) { diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 840a5bd78b..8e1fcb5f11 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -30,7 +30,7 @@ class PublicGitRepository extends Component public GithubApp|GitlabApp|string $git_source = 'other'; public string $git_host; public string $git_repository; - public $build_pack; + public $build_pack = 'nixpacks'; public bool $show_is_static = true; protected $rules = [ @@ -61,9 +61,11 @@ public function updatedBuildPack() { if ($this->build_pack === 'nixpacks') { $this->show_is_static = true; + $this->port = 3000; } else if ($this->build_pack === 'static') { $this->show_is_static = false; $this->is_static = false; + $this->port = 80; } else { $this->show_is_static = false; $this->is_static = false; diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 5954b3b36d..32c174520e 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -30,7 +30,10 @@ public function mount() { $this->parameters = get_route_parameters(); $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); + $this->service = Service::whereUuid($this->parameters['service_uuid'])->first(); + if (!$this->service) { + return redirect()->route('dashboard'); + } $this->applications = $this->service->applications->sort(); $this->databases = $this->service->databases->sort(); } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index 1d6e09b430..78ed3c7801 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -32,7 +32,6 @@ public function mount() public function submit() { $this->validate(); - ray($this->key, $this->value, $this->is_build_time); $this->dispatch('saveKey', [ 'key' => $this->key, 'value' => $this->value, diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 10a8856da9..f8709afd86 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -3,16 +3,18 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; +use App\Models\SharedEnvironmentVariable; use Livewire\Component; use Visus\Cuid2\Cuid2; class Show extends Component { public $parameters; - public ModelsEnvironmentVariable $env; + public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; public ?string $modalId = null; public bool $isDisabled = false; public bool $isLocked = false; + public bool $isSharedVariable = false; public string $type; protected $rules = [ @@ -20,16 +22,20 @@ class Show extends Component 'env.value' => 'nullable', 'env.is_build_time' => 'required|boolean', 'env.is_shown_once' => 'required|boolean', + 'env.real_value' => 'nullable', ]; protected $validationAttributes = [ - 'key' => 'Key', - 'value' => 'Value', - 'is_build_time' => 'Build Time', - 'is_shown_once' => 'Shown Once', + 'env.key' => 'Key', + 'env.value' => 'Value', + 'env.is_build_time' => 'Build Time', + 'env.is_shown_once' => 'Shown Once', ]; public function mount() { + if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') { + $this->isSharedVariable = true; + } $this->modalId = new Cuid2(7); $this->parameters = get_route_parameters(); $this->checkEnvs(); @@ -44,9 +50,16 @@ public function checkEnvs() $this->isLocked = true; } } + public function serialize() { + data_forget($this->env, 'real_value'); + if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') { + data_forget($this->env, 'is_build_time'); + } + } public function lock() { $this->env->is_shown_once = true; + $this->serialize(); $this->env->save(); $this->checkEnvs(); $this->dispatch('refreshEnvs'); @@ -57,10 +70,23 @@ public function instantSave() } public function submit() { - $this->validate(); - $this->env->save(); - $this->dispatch('success', 'Environment variable updated successfully.'); - $this->dispatch('refreshEnvs'); + try { + if ($this->isSharedVariable) { + $this->validate([ + 'env.key' => 'required|string', + 'env.value' => 'nullable', + 'env.is_shown_once' => 'required|boolean', + ]); + } else { + $this->validate(); + } + $this->serialize(); + $this->env->save(); + $this->dispatch('success', 'Environment variable updated successfully.'); + $this->dispatch('refreshEnvs'); + } catch(\Exception $e) { + return handleError($e); + } } public function delete() diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php new file mode 100644 index 0000000000..cd7e2be92d --- /dev/null +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -0,0 +1,173 @@ +projectUuid = $parameters['project_uuid']; + $this->environmentName = $parameters['environment_name']; + $this->projects = Project::ownedByCurrentTeam()->get(); + $this->servers = currentTeam()->servers; + } + public function cloneTo($destination_id) + { + $new_destination = StandaloneDocker::find($destination_id); + if (!$new_destination) { + $new_destination = SwarmDocker::find($destination_id); + } + if (!$new_destination) { + return $this->addError('destination_id', 'Destination not found.'); + } + $uuid = (string)new Cuid2(7); + $server = $new_destination->server; + if ($this->resource->getMorphClass() === 'App\Models\Application') { + $new_resource = $this->resource->replicate()->fill([ + 'uuid' => $uuid, + 'name' => $this->resource->name . '-clone-' . $uuid, + 'fqdn' => generateFqdn($server, $uuid), + 'status' => 'exited', + 'destination_id' => $new_destination->id, + ]); + $new_resource->save(); + $environmentVaribles = $this->resource->environment_variables()->get(); + foreach ($environmentVaribles as $environmentVarible) { + $newEnvironmentVariable = $environmentVarible->replicate()->fill([ + 'application_id' => $new_resource->id, + ]); + $newEnvironmentVariable->save(); + } + $persistentVolumes = $this->resource->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $newPersistentVolume = $volume->replicate()->fill([ + 'name' => $new_resource->uuid . '-' . str($volume->name)->afterLast('-'), + 'resource_id' => $new_resource->id, + ]); + $newPersistentVolume->save(); + } + $route = route('project.application.configuration', [ + 'project_uuid' => $this->projectUuid, + 'environment_name' => $this->environmentName, + 'application_uuid' => $new_resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } else if ( + $this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' || + $this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' || + $this->resource->getMorphClass() === 'App\Models\StandaloneMysql' || + $this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' || + $this->resource->getMorphClass() === 'App\Models\StandaloneRedis' + ) { + $uuid = (string)new Cuid2(7); + $new_resource = $this->resource->replicate()->fill([ + 'uuid' => $uuid, + 'name' => $this->resource->name . '-clone-' . $uuid, + 'status' => 'exited', + 'started_at' => null, + 'destination_id' => $new_destination->id, + ]); + $new_resource->save(); + $environmentVaribles = $this->resource->environment_variables()->get(); + foreach ($environmentVaribles as $environmentVarible) { + $payload = []; + if ($this->resource->type() === 'standalone-postgresql') { + $payload['standalone_postgresql_id'] = $new_resource->id; + } else if ($this->resource->type() === 'standalone-redis') { + $payload['standalone_redis_id'] = $new_resource->id; + } else if ($this->resource->type() === 'standalone-mongodb') { + $payload['standalone_mongodb_id'] = $new_resource->id; + } else if ($this->resource->type() === 'standalone-mysql') { + $payload['standalone_mysql_id'] = $new_resource->id; + } else if ($this->resource->type() === 'standalone-mariadb') { + $payload['standalone_mariadb_id'] = $new_resource->id; + } + $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); + $newEnvironmentVariable->save(); + } + $route = route('project.database.configuration', [ + 'project_uuid' => $this->projectUuid, + 'environment_name' => $this->environmentName, + 'database_uuid' => $new_resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } else if ($this->resource->type() === 'service') { + $uuid = (string)new Cuid2(7); + $new_resource = $this->resource->replicate()->fill([ + 'uuid' => $uuid, + 'name' => $this->resource->name . '-clone-' . $uuid, + 'destination_id' => $new_destination->id, + ]); + $new_resource->save(); + foreach ($new_resource->applications() as $application) { + $application->update([ + 'status' => 'exited', + ]); + } + foreach ($new_resource->databases() as $database) { + $database->update([ + 'status' => 'exited', + ]); + } + $new_resource->parse(); + $route = route('project.service.configuration', [ + 'project_uuid' => $this->projectUuid, + 'environment_name' => $this->environmentName, + 'service_uuid' => $new_resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } + return; + } + public function moveTo($environment_id) + { + try { + $new_environment = Environment::findOrFail($environment_id); + $this->resource->update([ + 'environment_id' => $environment_id + ]); + if ($this->resource->type() === 'application') { + $route = route('project.application.configuration', [ + 'project_uuid' => $new_environment->project->uuid, + 'environment_name' => $new_environment->name, + 'application_uuid' => $this->resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } else if (str($this->resource->type())->startsWith('standalone-')) { + $route = route('project.database.configuration', [ + 'project_uuid' => $new_environment->project->uuid, + 'environment_name' => $new_environment->name, + 'database_uuid' => $this->resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } else if ($this->resource->type() === 'service') { + $route = route('project.service.configuration', [ + 'project_uuid' => $new_environment->project->uuid, + 'environment_name' => $new_environment->name, + 'service_uuid' => $this->resource->uuid, + ]) . "#resource-operations"; + return redirect()->to($route); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.project.shared.resource-operations'); + } +} diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index b8cabd58ba..8ec31a228f 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -27,6 +27,7 @@ class Form extends Component 'server.settings.is_swarm_manager' => 'required|boolean', 'server.settings.is_swarm_worker' => 'required|boolean', 'server.settings.is_build_server' => 'required|boolean', + 'server.settings.concurrent_builds' => 'required|integer|min:1', 'wildcard_domain' => 'nullable|url', ]; protected $validationAttributes = [ @@ -40,6 +41,7 @@ class Form extends Component 'server.settings.is_swarm_manager' => 'Swarm Manager', 'server.settings.is_swarm_worker' => 'Swarm Worker', 'server.settings.is_build_server' => 'Build Server', + 'server.settings.concurrent_builds' => 'Concurrent Builds', ]; public function mount() diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index bd8304c9ec..020e9c6ad5 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -17,7 +17,7 @@ class Change extends Component public ?bool $preview_deployment_permissions = true; public $parameters; - public GithubApp $github_app; + public ?GithubApp $github_app; public string $name; public bool $is_system_wide; diff --git a/app/Livewire/TeamSharedVariablesIndex.php b/app/Livewire/TeamSharedVariablesIndex.php new file mode 100644 index 0000000000..d2776e8b22 --- /dev/null +++ b/app/Livewire/TeamSharedVariablesIndex.php @@ -0,0 +1,36 @@ + '$refresh', 'saveKey' => 'saveKey']; + + public function saveKey($data) + { + try { + $this->team->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'type' => 'team', + 'team_id' => currentTeam()->id, + ]); + $this->team->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function mount() + { + $this->team = currentTeam(); + } + public function render() + { + return view('livewire.team-shared-variables-index'); + } +} diff --git a/app/Models/Application.php b/app/Models/Application.php index 4e845fe0dd..116cfdd76c 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -263,6 +263,10 @@ public function portsExposesArray(): Attribute : explode(',', $this->ports_exposes) ); } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function serviceType() { $found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) { @@ -431,7 +435,7 @@ public function isConfigurationChanged($save = false) { $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { - $newConfigHash .= json_encode($this->environment_variables->all()); + $newConfigHash .= json_encode($this->environment_variables()); } else { $newConfigHash .= json_encode($this->environment_variables_preview->all()); } @@ -1002,7 +1006,7 @@ function loadComposeFile($isInit = false) if (!$composeFileContent) { $this->docker_compose_location = $initialDockerComposeLocation; $this->save(); - throw new \Exception("Could not load base compose file from $workdir$composeFile"); + throw new \RuntimeException("Could not load base compose file from $workdir$composeFile"); } else { $this->docker_compose_raw = $composeFileContent; $this->save(); diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 430a02cdbc..726b9078bc 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -17,6 +17,9 @@ public function isEmpty() $this->services()->count() == 0; } + public function environment_variables() { + return $this->hasMany(SharedEnvironmentVariable::class); + } public function applications() { return $this->hasMany(Application::class); diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 5450f0127d..4ecc6699ce 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -15,6 +15,7 @@ class EnvironmentVariable extends Model 'value' => 'encrypted', 'is_build_time' => 'boolean', ]; + protected $appends = ['real_value', 'is_shared']; protected static function booted() { @@ -48,24 +49,78 @@ protected function value(): Attribute set: fn (?string $value = null) => $this->set_environment_variables($value), ); } - - private function get_environment_variables(?string $environment_variable = null): string|null + public function realValue(): Attribute + { + $resource = null; + if ($this->application_id) { + $resource = Application::find($this->application_id); + } else if ($this->service_id) { + $resource = Service::find($this->service_id); + } else if ($this->database_id) { + $resource = StandalonePostgresql::find($this->database_id); + if (!$resource) { + $resource = StandaloneMysql::find($this->database_id); + if (!$resource) { + $resource = StandaloneRedis::find($this->database_id); + if (!$resource) { + $resource = StandaloneMongodb::find($this->database_id); + if (!$resource) { + $resource = StandaloneMariadb::find($this->database_id); + } + } + } + } + } + return Attribute::make( + get: function () use ($resource) { + return $this->get_real_environment_variables($this->value, $resource); + } + ); + } + protected function isShared(): Attribute + { + return Attribute::make( + get: function () { + $type = str($this->value)->after("{{")->before(".")->value; + if (str($this->value)->startsWith('{{' . $type) && str($this->value)->endsWith('}}')) { + return true; + } + return false; + } + ); + } + private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null { - // $team_id = currentTeam()->id; if (!$environment_variable) { return null; } - $environment_variable = trim(decrypt($environment_variable)); - if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) { - $variable = Str::after($environment_variable, 'global.'); + $environment_variable = trim($environment_variable); + $type = str($environment_variable)->after("{{")->before(".")->value; + if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) { + $variable = Str::after($environment_variable, "{$type}."); $variable = Str::before($variable, '}}'); $variable = Str::of($variable)->trim()->value; - // $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value; - ray('global env variable'); - return $environment_variable; + if ($type === 'environment') { + $id = $resource->environment->id; + } else if ($type === 'project') { + $id = $resource->environment->project->id; + } else { + $id = $resource->team()->id; + } + $environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first(); + if ($environment_variable_found) { + return $environment_variable_found->value; + } } return $environment_variable; } + private function get_environment_variables(?string $environment_variable = null): string|null + { + if (!$environment_variable) { + return null; + } + return trim(decrypt($environment_variable)); + } private function set_environment_variables(?string $environment_variable = null): string|null { @@ -73,6 +128,10 @@ private function set_environment_variables(?string $environment_variable = null) return null; } $environment_variable = trim($environment_variable); + $type = str($environment_variable)->after("{{")->before(".")->value; + if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) { + return encrypt((string) str($environment_variable)->replace(' ', '')); + } return encrypt($environment_variable); } diff --git a/app/Models/Project.php b/app/Models/Project.php index 1668d40598..d5f1bdd545 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -27,7 +27,9 @@ protected static function booted() $project->settings()->delete(); }); } - + public function environment_variables() { + return $this->hasMany(SharedEnvironmentVariable::class); + } public function environments() { return $this->hasMany(Environment::class); diff --git a/app/Models/Server.php b/app/Models/Server.php index dcbb0dde33..432a296e9f 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -398,6 +398,8 @@ public function isSwarmWorker() } public function validateConnection() { + config()->set('coolify.mux_enabled', false); + $server = Server::find($this->id); if (!$server) { return false; diff --git a/app/Models/Service.php b/app/Models/Service.php index 7f71ff8656..42cb011081 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use Symfony\Component\Yaml\Yaml; class Service extends BaseModel { @@ -17,6 +18,10 @@ public function type() { return 'service'; } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function extraFields() { $fields = collect([]); @@ -423,7 +428,7 @@ public function saveComposeConfigs() $envs = $this->environment_variables()->get(); $commands[] = "rm -f .env || true"; foreach ($envs as $env) { - $commands[] = "echo '{$env->key}={$env->value}' >> .env"; + $commands[] = "echo '{$env->key}={$env->real_value}' >> .env"; } if ($envs->count() === 0) { $commands[] = "touch .env"; diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php new file mode 100644 index 0000000000..260f16afbd --- /dev/null +++ b/app/Models/SharedEnvironmentVariable.php @@ -0,0 +1,14 @@ + 'string', + 'value' => 'encrypted', + ]; +} diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 33c00260e6..8b3f3412d8 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -42,6 +42,10 @@ protected static function booted() $database->environment_variables()->delete(); }); } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function link() { if (data_get($this, 'environment.project.uuid')) { diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index d08c04adbc..de2e7f2eb8 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -45,6 +45,10 @@ protected static function booted() $database->environment_variables()->delete(); }); } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function isLogDrainEnabled() { return data_get($this, 'is_log_drain_enabled', false); diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 02f8f82c4b..c9608f1cc5 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -42,6 +42,10 @@ protected static function booted() $database->environment_variables()->delete(); }); } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function link() { if (data_get($this, 'environment.project.uuid')) { diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index df9d284600..577904260f 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -74,7 +74,10 @@ public function portsMappingsArray(): Attribute ); } - + public function team() + { + return data_get($this, 'environment.project.team'); + } public function type(): string { return 'standalone-postgresql'; diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 7ccde7f68c..5f4279183e 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -37,6 +37,10 @@ protected static function booted() $database->environment_variables()->delete(); }); } + public function team() + { + return data_get($this, 'environment.project.team'); + } public function link() { if (data_get($this, 'environment.project.uuid')) { diff --git a/app/Models/Team.php b/app/Models/Team.php index 8021b1e974..042f747894 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -70,7 +70,9 @@ public function limits(): Attribute ); } - + public function environment_variables() { + return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); + } public function members() { return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role'); diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index 381b86dce1..e1f2fc7592 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -19,7 +19,7 @@ public function __construct( public string|null $label = null, public string|null $helper = null, public bool $required = false, - public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none" + public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none" ) { // } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 6a0eeb01f5..424ec02b25 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -1,21 +1,19 @@ destination->server; $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, + 'server_id' => $server_id, 'deployment_uuid' => $deployment_uuid, 'pull_request_id' => $pull_request_id, 'force_rebuild' => $force_rebuild, @@ -24,9 +22,15 @@ function queue_application_deployment(int $application_id, string $deployment_uu 'commit' => $commit, 'git_type' => $git_type ]); - $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); - $running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at'); - ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild); + $deployments_per_server = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get(); + + $deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('server_id', $server_id); + $queued_deployments = $deployments->where('status', 'queued')->get()->sortByDesc('created_at'); + $running_deployments = $deployments->where('status', 'in_progress')->get()->sortByDesc('created_at'); + + ray("serverId:{$server->id}", "concurrentBuilds:{$server->settings->concurrent_builds}", "deployments:{$deployments_per_server->count()}", "queued:{$queued_deployments->count()}", "running:{$running_deployments->count()}"); + // ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild); + if ($queued_deployments->count() > 1) { $queued_deployments = $queued_deployments->skip(1); $queued_deployments->each(function ($queued_deployment, $key) { @@ -37,6 +41,9 @@ function queue_application_deployment(int $application_id, string $deployment_uu if ($running_deployments->count() > 0) { return; } + if ($deployments_per_server->count() > $server->settings->concurrent_builds) { + return; + } if ($is_new_deployment) { dispatch(new ApplicationDeploymentNewJob( deployment: $deployment, @@ -51,7 +58,10 @@ function queue_application_deployment(int $application_id, string $deployment_uu function queue_next_deployment(Application $application, bool $isNew = false) { - $next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first(); + $server_id = $application->destination->server_id; + $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();; + // $next_found = ApplicationDeploymentQueue::where('status', 'queued')->get()->sortBy('created_at')->first(); + ray($next_found, $server_id); if ($next_found) { if ($isNew) { dispatch(new ApplicationDeploymentNewJob( diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index f49c7cafc8..d981ca436d 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -108,7 +108,7 @@ function instant_scp(string $source, string $dest, Server $server, $throwError = } return $output; } -function generateSshCommand(Server $server, string $command, bool $isMux = true) +function generateSshCommand(Server $server, string $command) { $user = $server->user; $port = $server->port; @@ -120,7 +120,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true) $delimiter = 'EOF-COOLIFY-SSH'; $ssh_command = "timeout $timeout ssh "; - if ($isMux && config('coolify.mux_enabled')) { + if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) { $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; } if (data_get($server, 'settings.is_cloudflare_tunnel')) { diff --git a/composer.json b/composer.json index e9cf53d109..f762ed579a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,6 @@ "league/flysystem-sftp-v3": "^3.0", "livewire/livewire": "^3.0", "lorisleiva/laravel-actions": "^2.7", - "masmerise/livewire-toaster": "^2.0", "nubs/random-name-generator": "^2.2", "phpseclib/phpseclib": "~3.0", "poliander/cron": "^3.0", diff --git a/composer.lock b/composer.lock index ea7e31647d..8dfa5e5568 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "de3b59fade9b132d2582a40dcf3c00f9", + "content-hash": "19b19082b605e09867e6ae65fb8135f6", "packages": [ { "name": "amphp/amp", @@ -4485,77 +4485,6 @@ ], "time": "2023-02-05T15:03:45+00:00" }, - { - "name": "masmerise/livewire-toaster", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/masmerise/livewire-toaster.git", - "reference": "89aa127df5d17b915b0818761bdf83e8915036c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/masmerise/livewire-toaster/zipball/89aa127df5d17b915b0818761bdf83e8915036c2", - "reference": "89aa127df5d17b915b0818761bdf83e8915036c2", - "shasum": "" - }, - "require": { - "laravel/framework": "^10.0", - "livewire/livewire": "^3.0", - "php": "~8.2" - }, - "conflict": { - "stevebauman/unfinalize": "*" - }, - "require-dev": { - "dive-be/php-crowbar": "^1.0", - "laravel/pint": "^1.0", - "nunomaduro/larastan": "^2.0", - "orchestra/testbench": "^8.0", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Toaster": "Masmerise\\Toaster\\Toaster" - }, - "providers": [ - "Masmerise\\Toaster\\ToasterServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Masmerise\\Toaster\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Muhammed Sari", - "email": "support@muhammedsari.me", - "role": "Developer" - } - ], - "description": "Beautiful toast notifications for Laravel / Livewire.", - "homepage": "https://github.com/masmerise/livewire-toaster", - "keywords": [ - "alert", - "laravel", - "livewire", - "toast", - "toaster" - ], - "support": { - "issues": "https://github.com/masmerise/livewire-toaster/issues", - "source": "https://github.com/masmerise/livewire-toaster/tree/2.0.3" - }, - "time": "2023-09-28T12:07:49+00:00" - }, { "name": "monolog/monolog", "version": "3.5.0", diff --git a/config/coolify.php b/config/coolify.php index 05917d17ad..2e15c86182 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -1,12 +1,14 @@ 'https://coolify.io/docs/contact', + 'docs' => 'https://coolify.io/docs/', + 'contact' => 'https://coolify.io/docs/contact', 'self_hosted' => env('SELF_HOSTED', true), 'waitlist' => env('WAITLIST', false), 'license_url' => 'https://licenses.coollabs.io', 'mux_enabled' => env('MUX_ENABLED', true), 'dev_webhook' => env('SERVEO_URL'), + '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'), ]; diff --git a/config/sentry.php b/config/sentry.php index f6b3ee9be3..acf9a03237 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // 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.199', + 'release' => '4.0.0-beta.200', // 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 0eb21d0dd6..a48110caea 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ id(); + $table->string('key'); + $table->string('value')->nullable(); + $table->boolean('is_shown_once')->default(false); + $table->enum('type', ['team', 'project', 'environment'])->default('team'); + + $table->foreignId('team_id')->constrained()->onDelete('cascade'); + $table->foreignId('project_id')->nullable()->constrained()->onDelete('cascade'); + $table->foreignId('environment_id')->nullable()->constrained()->onDelete('cascade'); + $table->unique(['key', 'project_id', 'team_id']); + $table->unique(['key', 'environment_id', 'team_id']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('shared_environment_variables'); + } +}; diff --git a/database/migrations/2024_01_24_095449_add_concurrent_number_of_builds_per_server.php b/database/migrations/2024_01_24_095449_add_concurrent_number_of_builds_per_server.php new file mode 100644 index 0000000000..f5d87d13df --- /dev/null +++ b/database/migrations/2024_01_24_095449_add_concurrent_number_of_builds_per_server.php @@ -0,0 +1,28 @@ +integer('concurrent_builds')->default(2); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('concurrent_builds'); + }); + } +}; diff --git a/database/migrations/2024_01_25_073212_add_server_id_to_queues.php b/database/migrations/2024_01_25_073212_add_server_id_to_queues.php new file mode 100644 index 0000000000..1441ad19fd --- /dev/null +++ b/database/migrations/2024_01_25_073212_add_server_id_to_queues.php @@ -0,0 +1,28 @@ +integer('server_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('server_id'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 2a2767d782..9f93c7e9e8 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -18,6 +18,7 @@ public function run(): void ProjectSeeder::class, ProjectSettingSeeder::class, EnvironmentSeeder::class, + TeamEnvironmentVariableSeeder::class, StandaloneDockerSeeder::class, SwarmDockerSeeder::class, KubernetesSeeder::class, diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index eb5880c328..4c66bdabf5 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -14,7 +14,6 @@ use App\Models\Team; use App\Models\User; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Storage; @@ -67,7 +66,8 @@ public function run(): void ]); } - if (!isCloud()) { + if (!isCloud() && config('coolify.is_windows_docker_desktop') == false) { + echo "Checking localhost key.\n"; // Save SSH Keys for the Coolify Host $coolify_key_name = "id.root@host.docker.internal"; $coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}"); @@ -123,6 +123,59 @@ public function run(): void ]); } } + if (config('coolify.is_windows_docker_desktop')) { + PrivateKey::updateOrCreate( + [ + 'id' => 0, + 'team_id' => 0, + ], + [ + "name" => "Testing-host", + "description" => "This is a a docker container with SSH access", + "private_key" => "-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk +hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA +AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV +uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== +-----END OPENSSH PRIVATE KEY----- +" + ] + ); + if (Server::find(0) == null) { + $server_details = [ + 'id' => 0, + 'uuid' => 'coolify-testing-host', + 'name' => "localhost", + 'description' => "This is the server where Coolify is running on. Don't delete this!", + 'user' => 'root', + 'ip' => "coolify-testing-host", + 'team_id' => 0, + 'private_key_id' => 0 + ]; + $server_details['proxy'] = ServerMetadata::from([ + 'type' => ProxyTypes::TRAEFIK_V2->value, + 'status' => ProxyStatus::EXITED->value + ]); + $server = Server::create($server_details); + $server->settings->is_reachable = true; + $server->settings->is_usable = true; + $server->settings->save(); + } else { + $server = Server::find(0); + $server->settings->is_reachable = true; + $server->settings->is_usable = true; + $server->settings->save(); + } + if (StandaloneDocker::find(0) == null) { + StandaloneDocker::create([ + 'id' => 0, + 'name' => 'localhost-coolify', + 'network' => 'coolify', + 'server_id' => 0, + ]); + } + } try { $settings = InstanceSettings::get(); diff --git a/database/seeders/SharedEnvironmentVariableSeeder.php b/database/seeders/SharedEnvironmentVariableSeeder.php new file mode 100644 index 0000000000..54643fe3b9 --- /dev/null +++ b/database/seeders/SharedEnvironmentVariableSeeder.php @@ -0,0 +1,36 @@ + 'NODE_ENV', + 'value' => 'team_env', + 'type' => 'team', + 'team_id' => 0, + ]); + SharedEnvironmentVariable::create([ + 'key' => 'NODE_ENV', + 'value' => 'env_env', + 'type' => 'environment', + 'environment_id' => 1, + 'team_id' => 0, + ]); + SharedEnvironmentVariable::create([ + 'key' => 'NODE_ENV', + 'value' => 'project_env', + 'type' => 'project', + 'project_id' => 1, + 'team_id' => 0, + ]); + } +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a9f09efa3f..6d76a9abde 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,13 +1,5 @@ version: "3.8" -x-testing-host: &testing-host-base - build: - dockerfile: Dockerfile - context: ./docker/testing-host - networks: - - coolify - init: true - services: coolify: build: @@ -30,6 +22,7 @@ services: volumes: - .:/var/www/html/:cached postgres: + pull_policy: always ports: - "${FORWARD_DB_PORT:-5432}:5432" env_file: @@ -43,6 +36,7 @@ services: - /data/coolify/_volumes/database/:/var/lib/postgresql/data # - coolify-pg-data-dev:/var/lib/postgresql/data redis: + pull_policy: always ports: - "${FORWARD_REDIS_PORT:-6379}:6379" env_file: @@ -62,6 +56,7 @@ services: SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" vite: image: node:20 + pull_policy: always working_dir: /var/www/html # environment: # VITE_PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}" @@ -73,7 +68,9 @@ services: networks: - coolify testing-host: - <<: *testing-host-base + image: "ghcr.io/coollabsio/coolify-testing-host:latest" + pull_policy: always + init: true container_name: coolify-testing-host volumes: - /:/host @@ -83,6 +80,7 @@ services: - coolify mailpit: image: "axllent/mailpit:latest" + pull_policy: always container_name: coolify-mail ports: - "${FORWARD_MAILPIT_PORT:-1025}:1025" @@ -91,6 +89,7 @@ services: - coolify minio: image: minio/minio:latest + pull_policy: always container_name: coolify-minio command: server /data --console-address ":9001" ports: diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml new file mode 100644 index 0000000000..7d5a57792f --- /dev/null +++ b/docker-compose.windows.yml @@ -0,0 +1,128 @@ +version: '3.8' +services: + coolify-testing-host: + init: true + image: "ghcr.io/coollabsio/coolify-testing-host:latest" + pull_policy: always + container_name: coolify-testing-host + volumes: + - //var/run/docker.sock://var/run/docker.sock + - ./:/data/coolify + coolify: + image: "ghcr.io/coollabsio/coolify:latest" + pull_policy: always + container_name: coolify + restart: always + working_dir: /var/www/html + extra_hosts: + - 'host.docker.internal:host-gateway' + volumes: + - type: bind + source: .env + target: /var/www/html/.env + read_only: true + - ./ssh:/var/www/html/storage/app/ssh + - ./applications:/var/www/html/storage/app/applications + - ./databases:/var/www/html/storage/app/databases + - ./services:/var/www/html/storage/app/services + - ./backups:/var/www/html/storage/app/backups + env_file: + - .env + environment: + - APP_ID + - APP_ENV=production + - APP_NAME + - APP_KEY + - DB_PASSWORD + - REDIS_PASSWORD + - SSL_MODE=off + - PHP_PM_CONTROL=dynamic + - PHP_PM_START_SERVERS=1 + - PHP_PM_MIN_SPARE_SERVERS=1 + - PHP_PM_MAX_SPARE_SERVERS=10 + - PUSHER_APP_ID + - PUSHER_APP_KEY + - PUSHER_APP_SECRET + - AUTOUPDATE=true + - SELF_HOSTED=true + - MUX_ENABLED=false + - IS_WINDOWS_DOCKER_DESKTOP=true + ports: + - "${APP_PORT:-8000}:80" + expose: + - "${APP_PORT:-8000}" + healthcheck: + test: curl --fail http://localhost:80/api/health || exit 1 + interval: 5s + retries: 10 + timeout: 2s + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + postgres: + image: postgres:15-alpine + pull_policy: always + container_name: coolify-db + restart: always + env_file: + - .env + volumes: + - coolify-db:/var/lib/postgresql/data + environment: + POSTGRES_USER: "${DB_USERNAME:-coolify}" + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: "${DB_DATABASE:-coolify}" + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${DB_USERNAME:-coolify}", + "-d", + "${DB_DATABASE:-coolify}" + ] + interval: 5s + retries: 10 + timeout: 2s + redis: + image: redis:alpine + pull_policy: always + container_name: coolify-redis + restart: always + command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD} + env_file: + - .env + environment: + REDIS_PASSWORD: "${REDIS_PASSWORD}" + volumes: + - coolify-redis:/data + healthcheck: + test: redis-cli ping + interval: 5s + retries: 10 + timeout: 2s + soketi: + image: 'quay.io/soketi/soketi:1.6-16-alpine' + pull_policy: always + container_name: coolify-realtime + restart: always + env_file: + - .env + 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: + name: coolify-db + coolify-redis: + name: coolify-redis diff --git a/package-lock.json b/package-lock.json index 6e2a04379e..95caf6ae28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "postcss": "8.4.33", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.1", - "vite": "4.5.1", + "vite": "4.5.2", "vue": "3.4.13" } }, @@ -2041,9 +2041,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", - "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/package.json b/package.json index b8b92a1c98..973d2810a7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "postcss": "8.4.33", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.1", - "vite": "4.5.1", + "vite": "4.5.2", "vue": "3.4.13" }, "dependencies": { diff --git a/resources/css/app.css b/resources/css/app.css index cd383c1cdf..5778b95295 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -168,3 +168,6 @@ tr td:first-child { input.input-sm { @apply pr-10; } +option{ + @apply text-white; +} diff --git a/resources/js/app.js b/resources/js/app.js index 526e9403ef..a49c95e97b 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,6 +1,5 @@ import { createApp } from "vue"; import MagicBar from "./components/MagicBar.vue"; -import '../../vendor/masmerise/livewire-toaster/resources/js'; import "../../vendor/wire-elements/modal/resources/js/modal"; const app = createApp({}); diff --git a/resources/js/components/MagicBar.vue b/resources/js/components/MagicBar.vue index 4fc1fc5dfa..5f11d8afdd 100644 --- a/resources/js/components/MagicBar.vue +++ b/resources/js/components/MagicBar.vue @@ -429,6 +429,13 @@ const magicActions = [{ tags: 'api,tokens,rest', icon: 'goto', sequence: ['main', 'redirect'] +}, +{ + id: 26, + name: 'Goto: Team Shared Variables', + tags: 'team,shared,variables', + icon: 'goto', + sequence: ['main', 'redirect'] } ] const initialState = { @@ -665,6 +672,9 @@ async function redirect() { case 25: targetUrl.pathname = `/security/api-tokens` break; + case 26: + targetUrl.pathname = `/team/shared-variables` + break; } window.location.href = targetUrl; } diff --git a/resources/views/components/navbar-subscription.blade.php b/resources/views/components/navbar-subscription.blade.php index 4636db475a..8638430e51 100644 --- a/resources/views/components/navbar-subscription.blade.php +++ b/resources/views/components/navbar-subscription.blade.php @@ -13,7 +13,7 @@ class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}
  • - + diff --git a/resources/views/components/pricing-plans.blade.php b/resources/views/components/pricing-plans.blade.php index 3b37f6ef10..2c5844e6c3 100644 --- a/resources/views/components/pricing-plans.blade.php +++ b/resources/views/components/pricing-plans.blade.php @@ -258,7 +258,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
    Need official support for your self-hosted instance? - Contact + Contact Us
    diff --git a/resources/views/components/slide-over.blade.php b/resources/views/components/slide-over.blade.php new file mode 100644 index 0000000000..bf68f9413e --- /dev/null +++ b/resources/views/components/slide-over.blade.php @@ -0,0 +1,48 @@ +
    + {{ $slot }} + +
    diff --git a/resources/views/components/team/navbar.blade.php b/resources/views/components/team/navbar.blade.php index 3b7fb1f406..4f18477e32 100644 --- a/resources/views/components/team/navbar.blade.php +++ b/resources/views/components/team/navbar.blade.php @@ -14,20 +14,24 @@ class="text-warning">{{ session('currentTeam.name') }}