diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 7948ad6a9..1b8701d94 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -23,24 +23,42 @@ class Upgrade extends Component public function mount() { - $this->currentVersion = config('constants.coolify.version'); - $this->devMode = isDev(); + $this->refreshUpgradeState(); } public function checkUpdate() { try { - $this->latestVersion = get_latest_version_of_coolify(); - $this->currentVersion = config('constants.coolify.version'); - $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false); - if (isDev()) { - $this->isUpgradeAvailable = true; - } + $this->refreshUpgradeState(); } catch (\Throwable $e) { return handleError($e, $this); } } + protected function refreshUpgradeState(): void + { + $this->currentVersion = config('constants.coolify.version'); + $this->latestVersion = get_latest_version_of_coolify(); + $this->devMode = isDev(); + + if ($this->devMode) { + $this->isUpgradeAvailable = true; + + return; + } + + $settings = InstanceSettings::find(0); + $hasNewerVersion = version_compare($this->latestVersion, $this->currentVersion, '>'); + $newVersionAvailable = (bool) data_get($settings, 'new_version_available', false); + + if ($settings && $newVersionAvailable && ! $hasNewerVersion) { + $settings->update(['new_version_available' => false]); + $newVersionAvailable = false; + } + + $this->isUpgradeAvailable = $hasNewerVersion && $newVersionAvailable; + } + public function upgrade() { try { diff --git a/app/Models/Team.php b/app/Models/Team.php index 8a54a9dee..d5d564444 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -71,25 +71,31 @@ protected static function booted() } }); - static::deleting(function ($team) { - $keys = $team->privateKeys; - foreach ($keys as $key) { + static::deleting(function (Team $team) { + foreach ($team->privateKeys as $key) { $key->delete(); } - $sources = $team->sources(); - foreach ($sources as $source) { + + // Transfer instance-wide sources to root team so they remain available + GithubApp::where('team_id', $team->id)->where('is_system_wide', true)->update(['team_id' => 0]); + GitlabApp::where('team_id', $team->id)->where('is_system_wide', true)->update(['team_id' => 0]); + + // Delete non-instance-wide sources owned by this team + $teamSources = GithubApp::where('team_id', $team->id)->get() + ->merge(GitlabApp::where('team_id', $team->id)->get()); + foreach ($teamSources as $source) { $source->delete(); } - $tags = Tag::whereTeamId($team->id)->get(); - foreach ($tags as $tag) { + + foreach (Tag::whereTeamId($team->id)->get() as $tag) { $tag->delete(); } - $shared_variables = $team->environment_variables(); - foreach ($shared_variables as $shared_variable) { - $shared_variable->delete(); + + foreach ($team->environment_variables()->get() as $sharedVariable) { + $sharedVariable->delete(); } - $s3s = $team->s3s; - foreach ($s3s as $s3) { + + foreach ($team->s3s as $s3) { $s3->delete(); } }); @@ -338,4 +344,5 @@ public function webhookNotificationSettings() { return $this->hasOne(WebhookNotificationSettings::class); } + } diff --git a/app/Support/ValidationPatterns.php b/app/Support/ValidationPatterns.php index 15d0f19e0..88121384f 100644 --- a/app/Support/ValidationPatterns.php +++ b/app/Support/ValidationPatterns.php @@ -203,10 +203,24 @@ public static function volumeNameMessages(string $field = 'name'): array } /** - * Pattern for port mappings (e.g. 3000:3000, 8080:80, 8000-8010:8000-8010) - * Each entry requires host:container format, where each side can be a number or a range (number-number) + * Pattern for port mappings with optional IP binding and protocol suffix on either side. + * Format: [ip:]port[:ip:port] where IP is IPv4 or [IPv6], port can be a range, protocol suffix optional. + * Examples: 8080:80, 127.0.0.1:8080:80, [::1]::80/udp, 127.0.0.1:8080:80/tcp */ - public const PORT_MAPPINGS_PATTERN = '/^(\d+(-\d+)?:\d+(-\d+)?)(,\d+(-\d+)?:\d+(-\d+)?)*$/'; + public const PORT_MAPPINGS_PATTERN = '/^ + (?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[\da-fA-F:]+\]):)? # optional IP + (?:\d+(?:-\d+)?(?:\/(?:tcp|udp|sctp))?)? # optional host port + : + (?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[\da-fA-F:]+\]):)? # optional IP + \d+(?:-\d+)?(?:\/(?:tcp|udp|sctp))? # container port + (?:, + (?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[\da-fA-F:]+\]):)? + (?:\d+(?:-\d+)?(?:\/(?:tcp|udp|sctp))?)? + : + (?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[\da-fA-F:]+\]):)? + \d+(?:-\d+)?(?:\/(?:tcp|udp|sctp))? + )* + $/x'; /** * Get validation rules for container name fields @@ -230,7 +244,7 @@ public static function portMappingRules(): array public static function portMappingMessages(string $field = 'portsMappings'): array { return [ - "{$field}.regex" => 'Port mappings must be a comma-separated list of port pairs or ranges (e.g. 3000:3000,8080:80,8000-8010:8000-8010).', + "{$field}.regex" => 'Port mappings must be a comma-separated list of port pairs or ranges with optional IP and protocol (e.g. 3000:3000, 8080:80/udp, 127.0.0.1:8080:80, [::1]::80).', ]; } diff --git a/composer.lock b/composer.lock index 3884eac06..2f27235f5 100644 --- a/composer.lock +++ b/composer.lock @@ -72,7 +72,6 @@ "type": "zip", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67b6b6210af47319c74c5666388d71bc1bc58276", "reference": "67b6b6210af47319c74c5666388d71bc1bc58276", - "shasum": "" }, "require": { @@ -157,7 +156,6 @@ "source": "https://github.com/aws/aws-sdk-php/tree/3.374.2" }, "time": "2026-03-27T18:05:55+00:00" - }, { "name": "bacon/bacon-qr-code", @@ -5158,16 +5156,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.50", + "version": "3.0.51", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b" + "reference": "d59c94077f9c9915abb51ddb52ce85188ece1748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", - "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748", + "reference": "d59c94077f9c9915abb51ddb52ce85188ece1748", "shasum": "" }, "require": { @@ -5248,7 +5246,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.51" }, "funding": [ { @@ -5264,7 +5262,7 @@ "type": "tidelift" } ], - "time": "2026-03-19T02:57:58+00:00" + "time": "2026-04-10T01:33:53+00:00" }, { "name": "phpstan/phpdoc-parser", diff --git a/config/constants.php b/config/constants.php index d0ae9be65..b8cd8f6d9 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,9 +2,9 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.472', + 'version' => '4.0.0-beta.473', 'helper_version' => '1.0.13', - 'realtime_version' => '1.0.12', + 'realtime_version' => '1.0.13', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e6d2bce54..901aeb833 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -60,7 +60,7 @@ services: retries: 10 timeout: 2s soketi: - image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12' + image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.13' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml index 00734fb0e..998d35974 100644 --- a/docker-compose.windows.yml +++ b/docker-compose.windows.yml @@ -96,7 +96,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.13' pull_policy: always container_name: coolify-realtime restart: always diff --git a/docker/coolify-realtime/package-lock.json b/docker/coolify-realtime/package-lock.json index 1c49ff930..174077562 100644 --- a/docker/coolify-realtime/package-lock.json +++ b/docker/coolify-realtime/package-lock.json @@ -7,7 +7,7 @@ "dependencies": { "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", - "axios": "1.13.6", + "axios": "1.15.0", "cookie": "1.1.1", "dotenv": "17.3.1", "node-pty": "1.1.0", @@ -36,14 +36,14 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/call-bind-apply-helpers": { @@ -344,10 +344,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/ws": { "version": "8.19.0", diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json index ebb7122c8..30bfbcef7 100644 --- a/docker/coolify-realtime/package.json +++ b/docker/coolify-realtime/package.json @@ -5,7 +5,7 @@ "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", "cookie": "1.1.1", - "axios": "1.13.6", + "axios": "1.15.0", "dotenv": "17.3.1", "node-pty": "1.1.0", "ws": "8.19.0" diff --git a/other/nightly/docker-compose.prod.yml b/other/nightly/docker-compose.prod.yml index e6d2bce54..901aeb833 100644 --- a/other/nightly/docker-compose.prod.yml +++ b/other/nightly/docker-compose.prod.yml @@ -60,7 +60,7 @@ services: retries: 10 timeout: 2s soketi: - image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12' + image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.13' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/other/nightly/docker-compose.windows.yml b/other/nightly/docker-compose.windows.yml index 00734fb0e..998d35974 100644 --- a/other/nightly/docker-compose.windows.yml +++ b/other/nightly/docker-compose.windows.yml @@ -96,7 +96,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.13' pull_policy: always container_name: coolify-realtime restart: always diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 26d755967..7012f481e 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,7 +1,7 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.472" + "version": "4.0.0-beta.473" }, "nightly": { "version": "4.0.0" @@ -10,7 +10,7 @@ "version": "1.0.13" }, "realtime": { - "version": "1.0.12" + "version": "1.0.13" }, "sentinel": { "version": "0.0.21" diff --git a/package-lock.json b/package-lock.json index 0af80f950..1fcd7cc1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "devDependencies": { "@tailwindcss/postcss": "4.1.18", "@vitejs/plugin-vue": "6.0.3", - "axios": "1.13.2", + "axios": "1.15.0", "laravel-echo": "2.2.7", "laravel-vite-plugin": "2.0.1", "postcss": "8.5.6", @@ -1474,15 +1474,15 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/call-bind-apply-helpers": { @@ -2501,11 +2501,14 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pusher-js": { "version": "8.4.0", diff --git a/package.json b/package.json index 661b13e4c..3afefa833 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "devDependencies": { "@tailwindcss/postcss": "4.1.18", "@vitejs/plugin-vue": "6.0.3", - "axios": "1.13.2", + "axios": "1.15.0", "laravel-echo": "2.2.7", "laravel-vite-plugin": "2.0.1", "postcss": "8.5.6", diff --git a/resources/css/utilities.css b/resources/css/utilities.css index 02be0c0c4..a8e807041 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -145,6 +145,10 @@ @utility dropdown-item { @apply flex relative gap-2 justify-start items-center py-1 pr-4 pl-2 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-none data-disabled:pointer-events-none data-disabled:opacity-50 focus-visible:bg-neutral-100 dark:focus-visible:bg-coollabs; } +@utility dropdown-item-touch { + @apply min-h-10 px-3 py-2 text-sm; +} + @utility dropdown-item-no-padding { @apply flex relative gap-2 justify-start items-center py-1 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-none data-disabled:pointer-events-none data-disabled:opacity-50 focus-visible:bg-neutral-100 dark:focus-visible:bg-coollabs; } diff --git a/resources/views/components/dropdown.blade.php b/resources/views/components/dropdown.blade.php index 666b93dbc..2bb917f79 100644 --- a/resources/views/components/dropdown.blade.php +++ b/resources/views/components/dropdown.blade.php @@ -1,7 +1,44 @@
- -
+ :style="panelStyles" class="absolute top-full z-50 mt-1 min-w-max max-w-[calc(100vw-1rem)] md:top-0 md:mt-6" x-cloak>
+ class="border border-neutral-300 bg-white p-1 shadow-sm dark:border-coolgray-300 dark:bg-coolgray-200"> {{ $slot }}
diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index b291759a8..29717b9b8 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -11,12 +11,12 @@ ])
$fullWidth, 'dark:hover:bg-coolgray-100 cursor-pointer' => !$disabled, ])> -
@if (!isCloud()) -
+
diff --git a/resources/views/livewire/notifications/telegram.blade.php b/resources/views/livewire/notifications/telegram.blade.php index 1c83caf70..f87a13c37 100644 --- a/resources/views/livewire/notifications/telegram.blade.php +++ b/resources/views/livewire/notifications/telegram.blade.php @@ -41,7 +41,7 @@

Deployments

-
+
@@ -49,7 +49,7 @@ id="telegramNotificationsDeploymentSuccessThreadId" />
-
+
@@ -57,7 +57,7 @@ id="telegramNotificationsDeploymentFailureThreadId" />
-
+
@@ -71,7 +71,7 @@

Backups

-
+
@@ -80,7 +80,7 @@
-
+
@@ -94,7 +94,7 @@

Scheduled Tasks

-
+
@@ -103,7 +103,7 @@
-
+
@@ -117,7 +117,7 @@

Server

-
+
@@ -126,7 +126,7 @@
-
+
@@ -135,7 +135,7 @@
-
+
@@ -144,7 +144,7 @@
-
+
@@ -153,7 +153,7 @@
-
+
@@ -162,7 +162,7 @@
-
+
@@ -171,7 +171,7 @@
-
+
diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index c17cda55f..d6294f3c8 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -1,4 +1,4 @@ -
+
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify @@ -165,13 +165,13 @@ this.scheduleScroll(); } } - }"> + }" class="flex flex-1 min-h-0 flex-col overflow-hidden"> -
+
+ class="flex min-h-0 flex-col w-full overflow-hidden bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300" + :class="fullscreen ? 'h-full' : 'flex-1 border border-dotted rounded-sm'">
@@ -328,8 +328,7 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
+ class="flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar">
@@ -363,4 +362,4 @@ class="shrink-0 text-gray-500">{{ $line['timestamp'] }}
-
\ No newline at end of file +
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index d743e346e..caf105dbf 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -276,7 +276,7 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry" helper="It is calculated together with the Base Directory:
{{ Str::start($baseDirectory . $dockerComposeLocation, '/') }}" x-model="composeLocation" @blur="normalizeComposeLocation()" />
-
+
@if ($buildPack !== 'dockercompose') -
+
@endif -
+
HTTP Basic Authentication
-
+
@@ -543,7 +543,7 @@ class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blu @endif -
+