mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
v4.0.0-beta.473 (#9521)
This commit is contained in:
@@ -23,24 +23,42 @@ class Upgrade extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->currentVersion = config('constants.coolify.version');
|
$this->refreshUpgradeState();
|
||||||
$this->devMode = isDev();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkUpdate()
|
public function checkUpdate()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->refreshUpgradeState();
|
||||||
$this->currentVersion = config('constants.coolify.version');
|
|
||||||
$this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false);
|
|
||||||
if (isDev()) {
|
|
||||||
$this->isUpgradeAvailable = true;
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
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()
|
public function upgrade()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
+19
-12
@@ -71,25 +71,31 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
static::deleting(function ($team) {
|
static::deleting(function (Team $team) {
|
||||||
$keys = $team->privateKeys;
|
foreach ($team->privateKeys as $key) {
|
||||||
foreach ($keys as $key) {
|
|
||||||
$key->delete();
|
$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();
|
$source->delete();
|
||||||
}
|
}
|
||||||
$tags = Tag::whereTeamId($team->id)->get();
|
|
||||||
foreach ($tags as $tag) {
|
foreach (Tag::whereTeamId($team->id)->get() as $tag) {
|
||||||
$tag->delete();
|
$tag->delete();
|
||||||
}
|
}
|
||||||
$shared_variables = $team->environment_variables();
|
|
||||||
foreach ($shared_variables as $shared_variable) {
|
foreach ($team->environment_variables()->get() as $sharedVariable) {
|
||||||
$shared_variable->delete();
|
$sharedVariable->delete();
|
||||||
}
|
}
|
||||||
$s3s = $team->s3s;
|
|
||||||
foreach ($s3s as $s3) {
|
foreach ($team->s3s as $s3) {
|
||||||
$s3->delete();
|
$s3->delete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -338,4 +344,5 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
|||||||
{
|
{
|
||||||
return $this->hasOne(WebhookNotificationSettings::class);
|
return $this->hasOne(WebhookNotificationSettings::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,10 +203,24 @@ class ValidationPatterns
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern for port mappings (e.g. 3000:3000, 8080:80, 8000-8010:8000-8010)
|
* Pattern for port mappings with optional IP binding and protocol suffix on either side.
|
||||||
* Each entry requires host:container format, where each side can be a number or a range (number-number)
|
* 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
|
* Get validation rules for container name fields
|
||||||
@@ -230,7 +244,7 @@ class ValidationPatterns
|
|||||||
public static function portMappingMessages(string $field = 'portsMappings'): array
|
public static function portMappingMessages(string $field = 'portsMappings'): array
|
||||||
{
|
{
|
||||||
return [
|
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).',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+6
-8
@@ -72,7 +72,6 @@
|
|||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67b6b6210af47319c74c5666388d71bc1bc58276",
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67b6b6210af47319c74c5666388d71bc1bc58276",
|
||||||
"reference": "67b6b6210af47319c74c5666388d71bc1bc58276",
|
"reference": "67b6b6210af47319c74c5666388d71bc1bc58276",
|
||||||
|
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -157,7 +156,6 @@
|
|||||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.374.2"
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.374.2"
|
||||||
},
|
},
|
||||||
"time": "2026-03-27T18:05:55+00:00"
|
"time": "2026-03-27T18:05:55+00:00"
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@@ -5158,16 +5156,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpseclib/phpseclib",
|
"name": "phpseclib/phpseclib",
|
||||||
"version": "3.0.50",
|
"version": "3.0.51",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -5248,7 +5246,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
"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": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -5264,7 +5262,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-03-19T02:57:58+00:00"
|
"time": "2026-04-10T01:33:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.472',
|
'version' => '4.0.0-beta.473',
|
||||||
'helper_version' => '1.0.13',
|
'helper_version' => '1.0.13',
|
||||||
'realtime_version' => '1.0.12',
|
'realtime_version' => '1.0.13',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'autoupdate' => env('AUTOUPDATE'),
|
'autoupdate' => env('AUTOUPDATE'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12'
|
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.13'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.13'
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
+12
-9
@@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xterm/addon-fit": "0.11.0",
|
"@xterm/addon-fit": "0.11.0",
|
||||||
"@xterm/xterm": "6.0.0",
|
"@xterm/xterm": "6.0.0",
|
||||||
"axios": "1.13.6",
|
"axios": "1.15.0",
|
||||||
"cookie": "1.1.1",
|
"cookie": "1.1.1",
|
||||||
"dotenv": "17.3.1",
|
"dotenv": "17.3.1",
|
||||||
"node-pty": "1.1.0",
|
"node-pty": "1.1.0",
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.6",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
||||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
@@ -344,10 +344,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.19.0",
|
"version": "8.19.0",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"@xterm/addon-fit": "0.11.0",
|
"@xterm/addon-fit": "0.11.0",
|
||||||
"@xterm/xterm": "6.0.0",
|
"@xterm/xterm": "6.0.0",
|
||||||
"cookie": "1.1.1",
|
"cookie": "1.1.1",
|
||||||
"axios": "1.13.6",
|
"axios": "1.15.0",
|
||||||
"dotenv": "17.3.1",
|
"dotenv": "17.3.1",
|
||||||
"node-pty": "1.1.0",
|
"node-pty": "1.1.0",
|
||||||
"ws": "8.19.0"
|
"ws": "8.19.0"
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.12'
|
image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.13'
|
||||||
ports:
|
ports:
|
||||||
- "${SOKETI_PORT:-6001}:6001"
|
- "${SOKETI_PORT:-6001}:6001"
|
||||||
- "6002:6002"
|
- "6002:6002"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
soketi:
|
soketi:
|
||||||
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.12'
|
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.13'
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: coolify-realtime
|
container_name: coolify-realtime
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.472"
|
"version": "4.0.0-beta.473"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0"
|
"version": "4.0.0"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"version": "1.0.13"
|
"version": "1.0.13"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.12"
|
"version": "1.0.13"
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"version": "0.0.21"
|
"version": "0.0.21"
|
||||||
|
|||||||
Generated
+14
-11
@@ -16,7 +16,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "4.1.18",
|
"@tailwindcss/postcss": "4.1.18",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"axios": "1.13.2",
|
"axios": "1.15.0",
|
||||||
"laravel-echo": "2.2.7",
|
"laravel-echo": "2.2.7",
|
||||||
"laravel-vite-plugin": "2.0.1",
|
"laravel-vite-plugin": "2.0.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
@@ -1474,15 +1474,15 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.2",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
|
||||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
@@ -2501,11 +2501,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pusher-js": {
|
"node_modules/pusher-js": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "4.1.18",
|
"@tailwindcss/postcss": "4.1.18",
|
||||||
"@vitejs/plugin-vue": "6.0.3",
|
"@vitejs/plugin-vue": "6.0.3",
|
||||||
"axios": "1.13.2",
|
"axios": "1.15.0",
|
||||||
"laravel-echo": "2.2.7",
|
"laravel-echo": "2.2.7",
|
||||||
"laravel-vite-plugin": "2.0.1",
|
"laravel-vite-plugin": "2.0.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
|
|||||||
@@ -145,6 +145,10 @@
|
|||||||
@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;
|
@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 {
|
@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;
|
@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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,44 @@
|
|||||||
<div x-data="{
|
<div x-data="{
|
||||||
dropdownOpen: false
|
dropdownOpen: false,
|
||||||
}" class="relative" @click.outside="dropdownOpen = false">
|
panelStyles: '',
|
||||||
<button @click="dropdownOpen=true"
|
open() {
|
||||||
|
this.dropdownOpen = true;
|
||||||
|
this.updatePanelPosition();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.dropdownOpen = false;
|
||||||
|
},
|
||||||
|
updatePanelPosition() {
|
||||||
|
if (window.innerWidth >= 768) {
|
||||||
|
this.panelStyles = '';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const triggerRect = this.$refs.trigger.getBoundingClientRect();
|
||||||
|
const panelRect = this.$refs.panel.getBoundingClientRect();
|
||||||
|
const viewportPadding = 8;
|
||||||
|
let left = triggerRect.left;
|
||||||
|
|
||||||
|
if ((left + panelRect.width + viewportPadding) > window.innerWidth) {
|
||||||
|
left = window.innerWidth - panelRect.width - viewportPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = Math.max(viewportPadding, left);
|
||||||
|
|
||||||
|
let top = triggerRect.bottom + 4;
|
||||||
|
const maxTop = window.innerHeight - panelRect.height - viewportPadding;
|
||||||
|
|
||||||
|
if (top > maxTop) {
|
||||||
|
top = Math.max(viewportPadding, triggerRect.top - panelRect.height - viewportPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.panelStyles = `position: fixed; left: ${left}px; top: ${top}px;`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}" class="relative" @click.outside="close()" x-on:resize.window="if (dropdownOpen) updatePanelPosition()">
|
||||||
|
<button x-ref="trigger" @click="dropdownOpen ? close() : open()"
|
||||||
class="inline-flex items-center justify-start pr-8 transition-colors focus:outline-hidden disabled:opacity-50 disabled:pointer-events-none">
|
class="inline-flex items-center justify-start pr-8 transition-colors focus:outline-hidden disabled:opacity-50 disabled:pointer-events-none">
|
||||||
<span class="flex flex-col items-start h-full leading-none">
|
<span class="flex flex-col items-start h-full leading-none">
|
||||||
{{ $title }}
|
{{ $title }}
|
||||||
@@ -13,11 +50,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div x-show="dropdownOpen" @click.away="dropdownOpen=false" x-transition:enter="ease-out duration-200"
|
<div x-ref="panel" x-show="dropdownOpen" @click.away="close()" x-transition:enter="ease-out duration-200"
|
||||||
x-transition:enter-start="-translate-y-2" x-transition:enter-end="translate-y-0"
|
x-transition:enter-start="-translate-y-2" x-transition:enter-end="translate-y-0"
|
||||||
class="absolute top-0 z-50 mt-6 min-w-max" x-cloak>
|
: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>
|
||||||
<div
|
<div
|
||||||
class="p-1 mt-1 bg-white border rounded-sm shadow-sm dark:bg-coolgray-200 dark:border-coolgray-300 border-neutral-300">
|
class="border border-neutral-300 bg-white p-1 shadow-sm dark:border-coolgray-300 dark:bg-coolgray-200">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
<div @class([
|
<div @class([
|
||||||
'flex flex-row items-center gap-4 pr-2 py-1 form-control min-w-fit',
|
'form-control flex max-w-full flex-row items-center gap-4 py-1 pr-2',
|
||||||
'w-full' => $fullWidth,
|
'w-full' => $fullWidth,
|
||||||
'dark:hover:bg-coolgray-100 cursor-pointer' => !$disabled,
|
'dark:hover:bg-coolgray-100 cursor-pointer' => !$disabled,
|
||||||
])>
|
])>
|
||||||
<label @class(['flex gap-4 items-center px-0 min-w-fit label w-full'])>
|
<label @class(['label flex w-full max-w-full min-w-0 items-center gap-4 px-0'])>
|
||||||
<span class="flex grow gap-2">
|
<span class="flex min-w-0 grow gap-2 break-words">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
@if ($disabled)
|
@if ($disabled)
|
||||||
<span class="opacity-60">{!! $label !!}</span>
|
<span class="opacity-60">{!! $label !!}</span>
|
||||||
@@ -29,16 +29,16 @@
|
|||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
@if ($instantSave)
|
@if ($instantSave)
|
||||||
<input type="checkbox" @disabled($disabled) {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input type="checkbox" @disabled($disabled) {{ $attributes->class([$defaultClass, 'shrink-0']) }}
|
||||||
wire:loading.attr="disabled"
|
wire:loading.attr="disabled"
|
||||||
wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}'
|
||||||
wire:model={{ $modelBinding }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
wire:model={{ $modelBinding }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
||||||
@else
|
@else
|
||||||
@if ($domValue)
|
@if ($domValue)
|
||||||
<input type="checkbox" @disabled($disabled) {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input type="checkbox" @disabled($disabled) {{ $attributes->class([$defaultClass, 'shrink-0']) }}
|
||||||
value={{ $domValue }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
value={{ $domValue }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
||||||
@else
|
@else
|
||||||
<input type="checkbox" @disabled($disabled) {{ $attributes->merge(['class' => $defaultClass]) }}
|
<input type="checkbox" @disabled($disabled) {{ $attributes->class([$defaultClass, 'shrink-0']) }}
|
||||||
wire:model={{ $value ?? $modelBinding }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
wire:model={{ $value ?? $modelBinding }} id="{{ $htmlId }}" @if ($checked) checked @endif />
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -129,7 +129,11 @@
|
|||||||
}"
|
}"
|
||||||
@keydown.escape.window="if (modalOpen) { modalOpen = false; resetModal(); }" :class="{ 'z-40': modalOpen }"
|
@keydown.escape.window="if (modalOpen) { modalOpen = false; resetModal(); }" :class="{ 'z-40': modalOpen }"
|
||||||
class="relative w-auto h-auto">
|
class="relative w-auto h-auto">
|
||||||
@if ($customButton)
|
@if (isset($trigger))
|
||||||
|
<div @click="modalOpen=true">
|
||||||
|
{{ $trigger }}
|
||||||
|
</div>
|
||||||
|
@elseif ($customButton)
|
||||||
@if ($buttonFullWidth)
|
@if ($buttonFullWidth)
|
||||||
<x-forms.button @click="modalOpen=true" class="w-full">
|
<x-forms.button @click="modalOpen=true" class="w-full">
|
||||||
{{ $customButton }}
|
{{ $customButton }}
|
||||||
|
|||||||
@@ -29,9 +29,47 @@
|
|||||||
$currentProjectUuid = data_get($resource, 'environment.project.uuid');
|
$currentProjectUuid = data_get($resource, 'environment.project.uuid');
|
||||||
$currentEnvironmentUuid = data_get($resource, 'environment.uuid');
|
$currentEnvironmentUuid = data_get($resource, 'environment.uuid');
|
||||||
$currentResourceUuid = data_get($resource, 'uuid');
|
$currentResourceUuid = data_get($resource, 'uuid');
|
||||||
|
$resourceUuid = data_get($resource, 'uuid');
|
||||||
|
$resourceType = $resource->getMorphClass();
|
||||||
|
$isApplication = $resourceType === 'App\Models\Application';
|
||||||
|
$isService = $resourceType === 'App\Models\Service';
|
||||||
|
$isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone');
|
||||||
|
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') &&
|
||||||
|
($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0);
|
||||||
|
$serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name');
|
||||||
|
$routeParams = [
|
||||||
|
'project_uuid' => $currentProjectUuid,
|
||||||
|
'environment_uuid' => $currentEnvironmentUuid,
|
||||||
|
];
|
||||||
|
if ($isApplication) {
|
||||||
|
$routeParams['application_uuid'] = $resourceUuid;
|
||||||
|
} elseif ($isService) {
|
||||||
|
$routeParams['service_uuid'] = $resourceUuid;
|
||||||
|
} else {
|
||||||
|
$routeParams['database_uuid'] = $resourceUuid;
|
||||||
|
}
|
||||||
@endphp
|
@endphp
|
||||||
<nav class="flex pt-2 pb-10">
|
<nav class="pt-2 pb-4 md:pb-10">
|
||||||
<ol class="flex flex-wrap items-center gap-y-1">
|
<div class="flex min-w-0 flex-col gap-1 md:hidden">
|
||||||
|
<div class="flex min-w-0 items-center text-xs text-neutral-400">
|
||||||
|
<a class="min-w-0 truncate text-neutral-300 hover:text-warning" {{ wireNavigate() }}
|
||||||
|
href="{{ $isApplication
|
||||||
|
? route('project.application.configuration', $routeParams)
|
||||||
|
: ($isService
|
||||||
|
? route('project.service.configuration', $routeParams)
|
||||||
|
: route('project.database.configuration', $routeParams)) }}"
|
||||||
|
title="{{ data_get($resource, 'name') }}{{ $serverName ? ' ('.$serverName.')' : '' }}">
|
||||||
|
{{ data_get($resource, 'name') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@if ($resource->getMorphClass() == 'App\Models\Service')
|
||||||
|
<x-status.services :service="$resource" />
|
||||||
|
@else
|
||||||
|
<x-status.index :resource="$resource" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol class="hidden flex-wrap items-center gap-y-1 md:flex">
|
||||||
<!-- Project Level -->
|
<!-- Project Level -->
|
||||||
<li class="inline-flex items-center" x-data="{ projectOpen: false, closeTimeout: null, toggle() { this.projectOpen = !this.projectOpen }, open() { clearTimeout(this.closeTimeout); this.projectOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.projectOpen = false }, 100) } }">
|
<li class="inline-flex items-center" x-data="{ projectOpen: false, closeTimeout: null, toggle() { this.projectOpen = !this.projectOpen }, open() { clearTimeout(this.closeTimeout); this.projectOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.projectOpen = false }, 100) } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
||||||
@@ -204,27 +242,6 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- Resource Level -->
|
<!-- Resource Level -->
|
||||||
@php
|
|
||||||
$resourceUuid = data_get($resource, 'uuid');
|
|
||||||
$resourceType = $resource->getMorphClass();
|
|
||||||
$isApplication = $resourceType === 'App\Models\Application';
|
|
||||||
$isService = $resourceType === 'App\Models\Service';
|
|
||||||
$isDatabase = str_contains($resourceType, 'Database') || str_contains($resourceType, 'Standalone');
|
|
||||||
$hasMultipleServers = $isApplication && method_exists($resource, 'additional_servers') &&
|
|
||||||
($resource->relationLoaded('additional_servers') ? $resource->additional_servers->count() > 0 : ($resource->additional_servers_count ?? 0) > 0);
|
|
||||||
$serverName = $hasMultipleServers ? null : data_get($resource, 'destination.server.name');
|
|
||||||
$routeParams = [
|
|
||||||
'project_uuid' => $currentProjectUuid,
|
|
||||||
'environment_uuid' => $currentEnvironmentUuid,
|
|
||||||
];
|
|
||||||
if ($isApplication) {
|
|
||||||
$routeParams['application_uuid'] = $resourceUuid;
|
|
||||||
} elseif ($isService) {
|
|
||||||
$routeParams['service_uuid'] = $resourceUuid;
|
|
||||||
} else {
|
|
||||||
$routeParams['database_uuid'] = $resourceUuid;
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
<li class="inline-flex items-center mr-2">
|
<li class="inline-flex items-center mr-2">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ $isApplication
|
href="{{ $isApplication
|
||||||
|
|||||||
@@ -3,35 +3,37 @@
|
|||||||
'lastDeploymentLink' => null,
|
'lastDeploymentLink' => null,
|
||||||
'resource' => null,
|
'resource' => null,
|
||||||
])
|
])
|
||||||
@if (str($resource->status)->startsWith('running'))
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
<x-status.running :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
@if (str($resource->status)->startsWith('running'))
|
||||||
@elseif(str($resource->status)->startsWith('degraded'))
|
<x-status.running :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<x-status.degraded :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
@elseif(str($resource->status)->startsWith('degraded'))
|
||||||
@elseif(str($resource->status)->startsWith('restarting') || str($resource->status)->startsWith('starting'))
|
<x-status.degraded :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<x-status.restarting :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
@elseif(str($resource->status)->startsWith('restarting') || str($resource->status)->startsWith('starting'))
|
||||||
@else
|
<x-status.restarting :status="$resource->status" :title="$title" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<x-status.stopped :status="$resource->status" />
|
@else
|
||||||
@endif
|
<x-status.stopped :status="$resource->status" />
|
||||||
@if (isset($resource->restart_count) && $resource->restart_count > 0 && !str($resource->status)->startsWith('exited'))
|
@endif
|
||||||
<div class="flex items-center pl-2">
|
@if (isset($resource->restart_count) && $resource->restart_count > 0 && !str($resource->status)->startsWith('exited'))
|
||||||
<span class="text-xs dark:text-warning" title="Container has restarted {{ $resource->restart_count }} time{{ $resource->restart_count > 1 ? 's' : '' }}. Last restart: {{ $resource->last_restart_at?->diffForHumans() }}">
|
<div class="flex items-center">
|
||||||
({{ $resource->restart_count }}x restarts)
|
<span class="text-xs dark:text-warning" title="Container has restarted {{ $resource->restart_count }} time{{ $resource->restart_count > 1 ? 's' : '' }}. Last restart: {{ $resource->last_restart_at?->diffForHumans() }}">
|
||||||
</span>
|
({{ $resource->restart_count }}x restarts)
|
||||||
</div>
|
</span>
|
||||||
@endif
|
</div>
|
||||||
@if (!str($resource->status)->contains('exited') && $showRefreshButton)
|
@endif
|
||||||
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'
|
@if (!str($resource->status)->contains('exited') && $showRefreshButton)
|
||||||
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'
|
||||||
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
<path
|
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
<path
|
||||||
</svg>
|
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
||||||
</button>
|
</svg>
|
||||||
<button wire:loading.delay.shortest wire:target="manualCheckStatus" title="Refreshing Status" wire:click='manualCheckStatus'
|
</button>
|
||||||
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
<button wire:loading.delay.shortest wire:target="manualCheckStatus" title="Refreshing Status" wire:click='manualCheckStatus'
|
||||||
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
<path
|
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
<path
|
||||||
</svg>
|
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
||||||
</button>
|
</svg>
|
||||||
@endif
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
@php
|
@php
|
||||||
$displayStatus = formatContainerStatus($complexStatus);
|
$displayStatus = formatContainerStatus($complexStatus);
|
||||||
@endphp
|
@endphp
|
||||||
@if (str($displayStatus)->lower()->contains('running'))
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
<x-status.running :status="$displayStatus" />
|
@if (str($displayStatus)->lower()->contains('running'))
|
||||||
@elseif(str($displayStatus)->lower()->contains('starting'))
|
<x-status.running :status="$displayStatus" />
|
||||||
<x-status.restarting :status="$displayStatus" />
|
@elseif(str($displayStatus)->lower()->contains('starting'))
|
||||||
@elseif(str($displayStatus)->lower()->contains('restarting'))
|
<x-status.restarting :status="$displayStatus" />
|
||||||
<x-status.restarting :status="$displayStatus" />
|
@elseif(str($displayStatus)->lower()->contains('restarting'))
|
||||||
@elseif(str($displayStatus)->lower()->contains('degraded'))
|
<x-status.restarting :status="$displayStatus" />
|
||||||
<x-status.degraded :status="$displayStatus" />
|
@elseif(str($displayStatus)->lower()->contains('degraded'))
|
||||||
@else
|
<x-status.degraded :status="$displayStatus" />
|
||||||
<x-status.stopped :status="$displayStatus" />
|
@else
|
||||||
@endif
|
<x-status.stopped :status="$displayStatus" />
|
||||||
@if (!str($complexStatus)->contains('exited') && $showRefreshButton)
|
@endif
|
||||||
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'
|
@if (!str($complexStatus)->contains('exited') && $showRefreshButton)
|
||||||
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
<button wire:loading.remove.delay.shortest wire:target="manualCheckStatus" title="Refresh Status" wire:click='manualCheckStatus'
|
||||||
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
<path
|
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
<path
|
||||||
</svg>
|
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
||||||
</button>
|
</svg>
|
||||||
<button wire:loading.delay.shortest wire:target="manualCheckStatus" title="Refreshing Status" wire:click='manualCheckStatus'
|
</button>
|
||||||
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
<button wire:loading.delay.shortest wire:target="manualCheckStatus" title="Refreshing Status" wire:click='manualCheckStatus'
|
||||||
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
<path
|
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
<path
|
||||||
</svg>
|
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
|
||||||
</button>
|
</svg>
|
||||||
@endif
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<x-modal-input buttonTitle="Add" title="New Project">
|
<x-modal-input buttonTitle="Add" title="New Project">
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-center size-4 text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
class="flex items-center justify-center size-4 text-black dark:text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||||
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
stroke-width="2" stroke="currentColor">
|
stroke-width="2" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
<x-modal-input buttonTitle="Add" title="New Server" :closeOutside="false">
|
<x-modal-input buttonTitle="Add" title="New Server" :closeOutside="false">
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-center size-4 text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
class="flex items-center justify-center size-4 text-black dark:text-white rounded hover:bg-coolgray-400 dark:hover:bg-coolgray-300 cursor-pointer">
|
||||||
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="size-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
stroke-width="2" stroke="currentColor">
|
stroke-width="2" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if (!isCloud())
|
@if (!isCloud())
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSave()" id="useInstanceEmailSettings"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSave()" id="useInstanceEmailSettings"
|
||||||
label="Use system wide (transactional) email settings" />
|
label="Use system wide (transactional) email settings" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<h3 class="text-lg font-medium mb-3">Deployments</h3>
|
<h3 class="text-lg font-medium mb-3">Deployments</h3>
|
||||||
<div class="flex flex-col gap-1.5 pl-1">
|
<div class="flex flex-col gap-1.5 pl-1">
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessTelegramNotifications"
|
||||||
label="Deployment Success" />
|
label="Deployment Success" />
|
||||||
</div>
|
</div>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
id="telegramNotificationsDeploymentSuccessThreadId" />
|
id="telegramNotificationsDeploymentSuccessThreadId" />
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureTelegramNotifications"
|
||||||
label="Deployment Failure" />
|
label="Deployment Failure" />
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
id="telegramNotificationsDeploymentFailureThreadId" />
|
id="telegramNotificationsDeploymentFailureThreadId" />
|
||||||
</div>
|
</div>
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="statusChangeTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="statusChangeTelegramNotifications"
|
||||||
label="Container Status Changes"
|
label="Container Status Changes"
|
||||||
helper="Send a notification when a container status changes. It will send a notification for Stopped and Restarted events of a container." />
|
helper="Send a notification when a container status changes. It will send a notification for Stopped and Restarted events of a container." />
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<h3 class="text-lg font-medium mb-3">Backups</h3>
|
<h3 class="text-lg font-medium mb-3">Backups</h3>
|
||||||
<div class="flex flex-col gap-1.5 pl-1">
|
<div class="flex flex-col gap-1.5 pl-1">
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessTelegramNotifications"
|
||||||
label="Backup Success" />
|
label="Backup Success" />
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureTelegramNotifications"
|
||||||
label="Backup Failure" />
|
label="Backup Failure" />
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<h3 class="text-lg font-medium mb-3">Scheduled Tasks</h3>
|
<h3 class="text-lg font-medium mb-3">Scheduled Tasks</h3>
|
||||||
<div class="flex flex-col gap-1.5 pl-1">
|
<div class="flex flex-col gap-1.5 pl-1">
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessTelegramNotifications"
|
||||||
label="Scheduled Task Success" />
|
label="Scheduled Task Success" />
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureTelegramNotifications"
|
||||||
label="Scheduled Task Failure" />
|
label="Scheduled Task Failure" />
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<h3 class="text-lg font-medium mb-3">Server</h3>
|
<h3 class="text-lg font-medium mb-3">Server</h3>
|
||||||
<div class="flex flex-col gap-1.5 pl-1">
|
<div class="flex flex-col gap-1.5 pl-1">
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessTelegramNotifications"
|
||||||
label="Docker Cleanup Success" />
|
label="Docker Cleanup Success" />
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureTelegramNotifications"
|
||||||
label="Docker Cleanup Failure" />
|
label="Docker Cleanup Failure" />
|
||||||
</div>
|
</div>
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageTelegramNotifications"
|
||||||
label="Server Disk Usage" />
|
label="Server Disk Usage" />
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableTelegramNotifications"
|
||||||
label="Server Reachable" />
|
label="Server Reachable" />
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableTelegramNotifications"
|
||||||
label="Server Unreachable" />
|
label="Server Unreachable" />
|
||||||
</div>
|
</div>
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchTelegramNotifications"
|
||||||
label="Server Patching" />
|
label="Server Patching" />
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pl-1 flex gap-2">
|
<div class="pl-1 flex gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="traefikOutdatedTelegramNotifications"
|
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="traefikOutdatedTelegramNotifications"
|
||||||
label="Traefik Proxy Outdated" />
|
label="Traefik Proxy Outdated" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div>
|
<div class="flex h-[calc(100vh-10rem)] min-h-[50rem] flex-col overflow-hidden">
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify
|
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@@ -165,13 +165,13 @@
|
|||||||
this.scheduleScroll();
|
this.scheduleScroll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}">
|
}" class="flex flex-1 min-h-0 flex-col overflow-hidden">
|
||||||
<livewire:project.application.deployment-navbar
|
<livewire:project.application.deployment-navbar
|
||||||
:application_deployment_queue="$application_deployment_queue" />
|
:application_deployment_queue="$application_deployment_queue" />
|
||||||
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 relative'">
|
<div id="screen" :class="fullscreen ? 'fullscreen flex flex-col' : 'mt-4 flex flex-1 min-h-0 flex-col overflow-hidden'">
|
||||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||||
class="flex flex-col w-full bg-white dark:text-white dark:bg-coolgray-100 dark:border-coolgray-300"
|
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' : 'border border-dotted rounded-sm'">
|
:class="fullscreen ? 'h-full' : 'flex-1 border border-dotted rounded-sm'">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b dark:border-coolgray-300 border-neutral-200 shrink-0">
|
class="flex flex-wrap items-center justify-between gap-2 px-4 py-2 border-b dark:border-coolgray-300 border-neutral-200 shrink-0">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -328,8 +328,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="logsContainer"
|
<div id="logsContainer"
|
||||||
class="flex flex-col overflow-y-auto p-2 px-4 min-h-4 scrollbar"
|
class="flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar">
|
||||||
:class="fullscreen ? 'flex-1' : 'max-h-[30rem]'">
|
|
||||||
<div id="logs" class="flex flex-col font-logs">
|
<div id="logs" class="flex flex-col font-logs">
|
||||||
<div x-show="searchQuery.trim() && matchCount === 0"
|
<div x-show="searchQuery.trim() && matchCount === 0"
|
||||||
class="text-gray-500 dark:text-gray-400 py-2">
|
class="text-gray-500 dark:text-gray-400 py-2">
|
||||||
@@ -363,4 +362,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -276,7 +276,7 @@
|
|||||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($baseDirectory . $dockerComposeLocation, '/') }}</span>"
|
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($baseDirectory . $dockerComposeLocation, '/') }}</span>"
|
||||||
x-model="composeLocation" @blur="normalizeComposeLocation()" />
|
x-model="composeLocation" @blur="normalizeComposeLocation()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox instantSave id="isPreserveRepositoryEnabled"
|
<x-forms.checkbox instantSave id="isPreserveRepositoryEnabled"
|
||||||
label="Preserve Repository During Deployment"
|
label="Preserve Repository During Deployment"
|
||||||
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
|
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
x-bind:disabled="!canUpdate" />
|
x-bind:disabled="!canUpdate" />
|
||||||
|
|
||||||
@if ($buildPack !== 'dockercompose')
|
@if ($buildPack !== 'dockercompose')
|
||||||
<div class="pt-2 w-96">
|
<div class="pt-2 w-full sm:w-96">
|
||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
||||||
instantSave id="isBuildServerEnabled" label="Use a Build Server?"
|
instantSave id="isBuildServerEnabled" label="Use a Build Server?"
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox label="Escape special characters in labels?"
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
id="isContainerLabelEscapeEnabled" instantSave
|
id="isContainerLabelEscapeEnabled" instantSave
|
||||||
@@ -521,7 +521,7 @@
|
|||||||
|
|
||||||
<h3 class="pt-8">HTTP Basic Authentication</h3>
|
<h3 class="pt-8">HTTP Basic Authentication</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
|
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
|
||||||
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
|
label="Enable" id="isHttpBasicAuthEnabled" x-bind:disabled="!canUpdate" />
|
||||||
</div>
|
</div>
|
||||||
@@ -543,7 +543,7 @@
|
|||||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
||||||
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
|
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
|
||||||
@endif
|
@endif
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox label="Readonly labels"
|
<x-forms.checkbox label="Readonly labels"
|
||||||
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
|
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
|
||||||
id="isContainerLabelReadonlyEnabled" instantSave
|
id="isContainerLabelReadonlyEnabled" instantSave
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
<nav wire:poll.10000ms="checkStatus" class="pb-6">
|
<nav wire:poll.10000ms="checkStatus" class="pb-6">
|
||||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
<nav
|
||||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||||
|
<a class="shrink-0 {{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.configuration', $parameters) }}">
|
href="{{ route('project.application.configuration', $parameters) }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
<a class="shrink-0 {{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.deployment.index', $parameters) }}">
|
href="{{ route('project.application.deployment.index', $parameters) }}">
|
||||||
Deployments
|
Deployments
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.application.logs', $parameters) }}">
|
href="{{ route('project.application.logs', $parameters) }}">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
Logs
|
Logs
|
||||||
@@ -23,100 +24,160 @@
|
|||||||
</a>
|
</a>
|
||||||
@if (!$application->destination->server->isSwarm())
|
@if (!$application->destination->server->isSwarm())
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.application.command', $parameters) }}">
|
href="{{ route('project.application.command', $parameters) }}">
|
||||||
Terminal
|
Terminal
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
@endif
|
@endif
|
||||||
<x-applications.links :application="$application" />
|
<div class="shrink-0">
|
||||||
|
<x-applications.links :application="$application" />
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex flex-wrap gap-2 items-center">
|
<div class="flex flex-wrap gap-2 items-center">
|
||||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||||
<div>Please load a Compose file.</div>
|
<div>Please load a Compose file.</div>
|
||||||
@else
|
@else
|
||||||
@if (!$application->destination->server->isSwarm())
|
<div class="md:hidden">
|
||||||
<div>
|
<x-dropdown>
|
||||||
<x-applications.advanced :application="$application" />
|
<x-slot:title>
|
||||||
</div>
|
Actions
|
||||||
@endif
|
</x-slot>
|
||||||
<div class="flex flex-wrap gap-2">
|
@if (!str($application->status)->startsWith('exited'))
|
||||||
@if (!str($application->status)->startsWith('exited'))
|
@if (!$application->destination->server->isSwarm())
|
||||||
@if (!$application->destination->server->isSwarm())
|
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
Redeploy
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
</div>
|
||||||
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
@endif
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
@if ($application->destination->server->isSwarm())
|
||||||
<path
|
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
Update Service
|
||||||
</path>
|
</div>
|
||||||
<path d="M7.05 11.038v-3.988"></path>
|
@else
|
||||||
</svg>
|
<div class="dropdown-item dropdown-item-touch" wire:click='restart'>
|
||||||
Redeploy
|
Restart
|
||||||
</x-forms.button>
|
</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||||
|
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||||
|
'This application will be stopped.',
|
||||||
|
'All non-persistent data of this application will be deleted.',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
|
<x-slot:trigger>
|
||||||
|
<div class="dropdown-item dropdown-item-touch text-error">
|
||||||
|
Stop
|
||||||
|
</div>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@else
|
||||||
|
<div class="dropdown-item dropdown-item-touch" wire:click='deploy'>
|
||||||
|
Deploy
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
|
||||||
@if ($application->destination->server->isSwarm())
|
@if (!$application->destination->server->isSwarm())
|
||||||
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
@if ($application->status === 'running')
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
<div class="dropdown-item dropdown-item-touch" wire:click='force_deploy_without_cache'>
|
||||||
stroke-linejoin="round" stroke-width="2">
|
Force deploy (without cache)
|
||||||
<path
|
</div>
|
||||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
||||||
<path d="M20 4v5h-5" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
Update Service
|
|
||||||
</x-forms.button>
|
|
||||||
@else
|
@else
|
||||||
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
<div class="dropdown-item dropdown-item-touch" wire:click='deploy(true)'>
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
Force deploy (without cache)
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
</div>
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" stroke-width="2">
|
|
||||||
<path
|
|
||||||
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
|
||||||
<path d="M20 4v5h-5" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
Restart
|
|
||||||
</x-forms.button>
|
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
</x-dropdown>
|
||||||
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
</div>
|
||||||
'This application will be stopped.',
|
|
||||||
'All non-persistent data of this application will be deleted.',
|
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
@if (!$application->destination->server->isSwarm())
|
||||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
<div>
|
||||||
<x-slot:button-title>
|
<x-applications.advanced :application="$application" />
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
</div>
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
||||||
<path
|
|
||||||
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
Stop
|
|
||||||
</x-slot:button-title>
|
|
||||||
</x-modal-confirmation>
|
|
||||||
@else
|
|
||||||
<x-forms.button wire:click='deploy'>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
Deploy
|
|
||||||
</x-forms.button>
|
|
||||||
@endif
|
@endif
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
@if (!str($application->status)->startsWith('exited'))
|
||||||
|
@if (!$application->destination->server->isSwarm())
|
||||||
|
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
|
||||||
|
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path
|
||||||
|
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||||
|
</path>
|
||||||
|
<path d="M7.05 11.038v-3.988"></path>
|
||||||
|
</svg>
|
||||||
|
Redeploy
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
|
@if ($application->destination->server->isSwarm())
|
||||||
|
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Update Service
|
||||||
|
</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||||
|
submitAction="stop" :checkboxes="$checkboxes" :actions="[
|
||||||
|
'This application will be stopped.',
|
||||||
|
'All non-persistent data of this application will be deleted.',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path
|
||||||
|
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@else
|
||||||
|
<x-forms.button wire:click='deploy'>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
|
||||||
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
Deploy
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,18 +10,18 @@
|
|||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav
|
<nav
|
||||||
class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
|
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
<a class="shrink-0 {{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.database.configuration', $parameters) }}">
|
href="{{ route('project.database.configuration', $parameters) }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.database.logs', $parameters) }}">
|
href="{{ route('project.database.logs', $parameters) }}">
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.database.command', $parameters) }}">
|
href="{{ route('project.database.command', $parameters) }}">
|
||||||
Terminal
|
Terminal
|
||||||
</a>
|
</a>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
<a class="shrink-0 {{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||||
Backups
|
Backups
|
||||||
</a>
|
</a>
|
||||||
@@ -39,57 +39,97 @@
|
|||||||
</nav>
|
</nav>
|
||||||
@if ($database->destination->server->isFunctional())
|
@if ($database->destination->server->isFunctional())
|
||||||
<div class="flex flex-wrap gap-2 items-center">
|
<div class="flex flex-wrap gap-2 items-center">
|
||||||
@if (!str($database->status)->startsWith('exited'))
|
<div class="md:hidden">
|
||||||
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
<x-dropdown>
|
||||||
:actions="[
|
<x-slot:title>
|
||||||
'This database will be unavailable during the restart.',
|
Actions
|
||||||
'If the database is currently in use data could be lost.',
|
</x-slot>
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
@if (!str($database->status)->startsWith('exited'))
|
||||||
:dispatchEvent="true" dispatchEventType="restartEvent">
|
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
||||||
<x-slot:button-title>
|
:actions="[
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
'This database will be unavailable during the restart.',
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
'If the database is currently in use data could be lost.',
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
||||||
stroke-width="2">
|
:dispatchEvent="true" dispatchEventType="restartEvent">
|
||||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
<x-slot:trigger>
|
||||||
<path d="M20 4v5h-5" />
|
<div class="dropdown-item dropdown-item-touch">
|
||||||
</g>
|
Restart
|
||||||
</svg>
|
</div>
|
||||||
Restart
|
</x-slot:trigger>
|
||||||
</x-slot:button-title>
|
</x-modal-confirmation>
|
||||||
</x-modal-confirmation>
|
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||||
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
:checkboxes="$checkboxes" :actions="[
|
||||||
:checkboxes="$checkboxes" :actions="[
|
'This database will be stopped.',
|
||||||
'This database will be stopped.',
|
'If the database is currently in use data could be lost.',
|
||||||
'If the database is currently in use data could be lost.',
|
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
||||||
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
step1ButtonText="Continue" step2ButtonText="Confirm">
|
<x-slot:trigger>
|
||||||
<x-slot:button-title>
|
<div class="dropdown-item dropdown-item-touch text-error">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
Stop
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
</div>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@else
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</x-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||||
|
@if (!str($database->status)->startsWith('exited'))
|
||||||
|
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
|
||||||
|
:actions="[
|
||||||
|
'This database will be unavailable during the restart.',
|
||||||
|
'If the database is currently in use data could be lost.',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
|
||||||
|
:dispatchEvent="true" dispatchEventType="restartEvent">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
|
||||||
|
:checkboxes="$checkboxes" :actions="[
|
||||||
|
'This database will be stopped.',
|
||||||
|
'If the database is currently in use data could be lost.',
|
||||||
|
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@else
|
||||||
|
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<path d="M7 4v16l13 -8z" />
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
</svg>
|
||||||
Stop
|
Start
|
||||||
</x-slot:button-title>
|
</button>
|
||||||
</x-modal-confirmation>
|
@endif
|
||||||
@else
|
</div>
|
||||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
Start
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
$wire.$on('startEvent', () => {
|
$wire.$on('startEvent', () => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if ($resource instanceof \App\Models\Application)
|
@if ($resource instanceof \App\Models\Application)
|
||||||
@can('update', $resource)
|
@can('update', $resource)
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||||
id="isPreviewSuffixEnabled"
|
id="isPreviewSuffixEnabled"
|
||||||
helper="When enabled, a -pr-N suffix is added to this volume's path for preview deployments (e.g. ./scripts becomes ./scripts-pr-1). Disable this for volumes that contain shared config or scripts from your repository."></x-forms.checkbox>
|
helper="When enabled, a -pr-N suffix is added to this volume's path for preview deployments (e.g. ./scripts becomes ./scripts-pr-1). Disable this for volumes that contain shared config or scripts from your repository."></x-forms.checkbox>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
@if (!$fileStorage->is_directory)
|
@if (!$fileStorage->is_directory)
|
||||||
@can('update', $resource)
|
@can('update', $resource)
|
||||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
|
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
|
||||||
id="isBasedOnGit"></x-forms.checkbox>
|
id="isBasedOnGit"></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
||||||
id="isBasedOnGit"></x-forms.checkbox>
|
id="isBasedOnGit"></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endcan
|
@endcan
|
||||||
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
<x-forms.checkbox disabled label="Is this based on the Git repository?"
|
||||||
id="isBasedOnGit"></x-forms.checkbox>
|
id="isBasedOnGit"></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,120 +9,198 @@
|
|||||||
<h1>{{ $title }}</h1>
|
<h1>{{ $title }}</h1>
|
||||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||||
<div class="navbar-main" x-data">
|
<div class="navbar-main" x-data">
|
||||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
<nav
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
class="scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible">
|
||||||
|
<a class="shrink-0 {{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.configuration', $parameters) }}">
|
href="{{ route('project.service.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.service.logs', $parameters) }}">
|
href="{{ route('project.service.logs', $parameters) }}">
|
||||||
<button>Logs</button>
|
<button>Logs</button>
|
||||||
</a>
|
</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
<a class="shrink-0 {{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('project.service.command', $parameters) }}">
|
href="{{ route('project.service.command', $parameters) }}">
|
||||||
<button>Terminal</button>
|
<button>Terminal</button>
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
<x-services.links :service="$service" />
|
<div class="shrink-0">
|
||||||
|
<x-services.links :service="$service" />
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@if ($service->isDeployable)
|
@if ($service->isDeployable)
|
||||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
<div class="order-first flex flex-wrap items-center gap-2 sm:order-last">
|
||||||
<x-services.advanced :service="$service" />
|
<div class="md:hidden">
|
||||||
@if (str($service->status)->contains('running'))
|
<x-dropdown>
|
||||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
<x-slot:title>
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
Actions
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
</x-slot>
|
||||||
stroke-width="2">
|
@if (str($service->status)->contains('running'))
|
||||||
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('restartEvent')">
|
||||||
<path d="M20 4v5h-5" />
|
Restart
|
||||||
</g>
|
</div>
|
||||||
</svg>
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
Restart
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
</x-forms.button>
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
<x-slot:trigger>
|
||||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
<div class="dropdown-item dropdown-item-touch text-error">
|
||||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
Stop
|
||||||
<x-slot:button-title>
|
</div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
</x-slot:trigger>
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
</x-modal-confirmation>
|
||||||
stroke-linejoin="round">
|
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('pullAndRestartEvent')">
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
Pull Latest Images & Restart
|
||||||
</path>
|
</div>
|
||||||
<path
|
@elseif (str($service->status)->contains('degraded'))
|
||||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('restartEvent')">
|
||||||
</path>
|
Restart
|
||||||
|
</div>
|
||||||
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
|
<x-slot:trigger>
|
||||||
|
<div class="dropdown-item dropdown-item-touch text-error">
|
||||||
|
Stop
|
||||||
|
</div>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||||
|
Force Restart
|
||||||
|
</div>
|
||||||
|
@elseif (str($service->status)->contains('exited'))
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||||
|
Deploy
|
||||||
|
</div>
|
||||||
|
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||||
|
Force Deploy
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" wire:click='stop(true)'>
|
||||||
|
Force Cleanup Containers
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
|
<x-slot:trigger>
|
||||||
|
<div class="dropdown-item dropdown-item-touch text-error">
|
||||||
|
Stop
|
||||||
|
</div>
|
||||||
|
</x-slot:trigger>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('startEvent')">
|
||||||
|
Deploy
|
||||||
|
</div>
|
||||||
|
<div class="mx-2 my-1 border-t border-neutral-200 dark:border-coolgray-300"></div>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" @click="$wire.dispatch('forceDeployEvent')">
|
||||||
|
Force Deploy
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-item dropdown-item-touch" wire:click='stop(true)'>
|
||||||
|
Force Cleanup Containers
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</x-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="hidden flex-wrap items-center gap-2 md:flex">
|
||||||
|
<x-services.advanced :service="$service" />
|
||||||
|
@if (str($service->status)->contains('running'))
|
||||||
|
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||||
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M19.933 13.041 a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
Stop
|
Restart
|
||||||
</x-slot:button-title>
|
</x-forms.button>
|
||||||
</x-modal-confirmation>
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
@elseif (str($service->status)->contains('degraded'))
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<x-slot:button-title>
|
||||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
stroke-width="2">
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
stroke-linejoin="round">
|
||||||
<path d="M20 4v5h-5" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
</g>
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
</svg>
|
</path>
|
||||||
Restart
|
<path
|
||||||
</x-forms.button>
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
</path>
|
||||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
</svg>
|
||||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
Stop
|
||||||
<x-slot:button-title>
|
</x-slot:button-title>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
</x-modal-confirmation>
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
@elseif (str($service->status)->contains('degraded'))
|
||||||
stroke-linejoin="round">
|
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
</path>
|
stroke-width="2">
|
||||||
<path
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<path d="M20 4v5h-5" />
|
||||||
</path>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
Stop
|
Restart
|
||||||
</x-slot:button-title>
|
</x-forms.button>
|
||||||
</x-modal-confirmation>
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
@elseif (str($service->status)->contains('exited'))
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
<x-slot:button-title>
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
stroke-linejoin="round">
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
stroke-linejoin="round">
|
||||||
<path d="M7 4v16l13 -8z" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
</svg>
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
Deploy
|
</path>
|
||||||
</button>
|
<path
|
||||||
@else
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
</path>
|
||||||
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
</svg>
|
||||||
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
Stop
|
||||||
<x-slot:button-title>
|
</x-slot:button-title>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
</x-modal-confirmation>
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
@elseif (str($service->status)->contains('exited'))
|
||||||
|
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<path d="M7 4v16l13 -8z" />
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
</svg>
|
||||||
Stop
|
Deploy
|
||||||
</x-slot:button-title>
|
</button>
|
||||||
</x-modal-confirmation>
|
@else
|
||||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
|
||||||
stroke-linejoin="round">
|
<x-slot:button-title>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
<path d="M7 4v16l13 -8z" />
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
</svg>
|
stroke-linejoin="round">
|
||||||
Deploy
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
</button>
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
@endif
|
</path>
|
||||||
|
<path
|
||||||
|
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</x-slot:button-title>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
Deploy
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pt-2">Advanced</h3>
|
<h3 class="pt-2">Advanced</h3>
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
||||||
label="Exclude from service status"
|
label="Exclude from service status"
|
||||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
placeholder="My super WordPress site" />
|
placeholder="My super WordPress site" />
|
||||||
<x-forms.input canGate="update" :canResource="$service" id="description" label="Description" />
|
<x-forms.input canGate="update" :canResource="$service" id="description" label="Description" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$service" instantSave id="connectToDockerNetwork"
|
<x-forms.checkbox canGate="update" :canResource="$service" instantSave id="connectToDockerNetwork"
|
||||||
label="Connect To Predefined Network"
|
label="Connect To Predefined Network"
|
||||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
|
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||||
@@ -46,4 +46,4 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@if (!$isService)
|
@if (!$isService)
|
||||||
@can('update', $resource)
|
@can('update', $resource)
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||||
id="isPreviewSuffixEnabled"
|
id="isPreviewSuffixEnabled"
|
||||||
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (!$isService)
|
@if (!$isService)
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
<x-forms.checkbox instantSave canGate="update" :canResource="$resource" label="Add suffix for PR deployments"
|
||||||
id="isPreviewSuffixEnabled"
|
id="isPreviewSuffixEnabled"
|
||||||
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
helper="When enabled, a -pr-N suffix is added to this volume's name for preview deployments (e.g. myvolume becomes myvolume-pr-1). Disable this for volumes that should be shared between the main and preview deployments."></x-forms.checkbox>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</x-callout>
|
</x-callout>
|
||||||
@endif
|
@endif
|
||||||
<h3>Advanced</h3>
|
<h3>Advanced</h3>
|
||||||
<div class="pb-6 w-96">
|
<div class="pb-6 w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$server"
|
<x-forms.checkbox canGate="update" :canResource="$server"
|
||||||
helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.<br>For applications, labels needs to be regenerated manually. <br>Resources needs to be restarted."
|
helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.<br>For applications, labels needs to be regenerated manually. <br>Resources needs to be restarted."
|
||||||
id="generateExactLabels"
|
id="generateExactLabels"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
<x-forms.checkbox canGate="update" :canResource="$server" wire:model.live="isSentinelEnabled"
|
<x-forms.checkbox canGate="update" :canResource="$server" wire:model.live="isSentinelEnabled"
|
||||||
label="Enable Sentinel" />
|
label="Enable Sentinel" />
|
||||||
@if ($server->isSentinelEnabled())
|
@if ($server->isSentinelEnabled())
|
||||||
|
|||||||
@@ -274,7 +274,7 @@
|
|||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@if (!$server->isLocalhost())
|
@if (!$server->isLocalhost())
|
||||||
<div class="w-96">
|
<div class="w-full sm:w-96">
|
||||||
@if ($isBuildServerLocked)
|
@if ($isBuildServerLocked)
|
||||||
<x-forms.checkbox disabled instantSave id="isBuildServer"
|
<x-forms.checkbox disabled instantSave id="isBuildServer"
|
||||||
helper="You can't use this server as a build server because it has defined resources."
|
helper="You can't use this server as a build server because it has defined resources."
|
||||||
|
|||||||
@@ -271,7 +271,7 @@
|
|||||||
<div>You need to register a GitHub App before using this source.</div>
|
<div>You need to register a GitHub App before using this source.</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 pt-4 w-96">
|
<div class="flex w-full flex-col gap-2 pt-4 sm:w-96">
|
||||||
<x-forms.checkbox disabled id="default_permissions" label="Mandatory"
|
<x-forms.checkbox disabled id="default_permissions" label="Mandatory"
|
||||||
helper="Contents: read<br>Metadata: read<br>Email: read" />
|
helper="Contents: read<br>Metadata: read<br>Email: read" />
|
||||||
<x-forms.checkbox id="preview_deployment_permissions" label="Preview Deployments "
|
<x-forms.checkbox id="preview_deployment_permissions" label="Preview Deployments "
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->team = Team::factory()->create();
|
||||||
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
|
||||||
|
InstanceSettings::unguarded(function () {
|
||||||
|
InstanceSettings::query()->create([
|
||||||
|
'id' => 0,
|
||||||
|
'is_registration_enabled' => true,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
session(['currentTeam' => $this->team]);
|
||||||
|
|
||||||
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
|
||||||
|
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||||
|
$this->application = Application::factory()->create([
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
'name' => 'Pure Dockerfile Example',
|
||||||
|
'status' => 'running',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the breadcrumb trail on mobile while keeping the current status visible', function () {
|
||||||
|
$response = $this->get(route('project.application.configuration', [
|
||||||
|
'project_uuid' => $this->project->uuid,
|
||||||
|
'environment_uuid' => $this->environment->uuid,
|
||||||
|
'application_uuid' => $this->application->uuid,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('flex min-w-0 flex-col gap-1 md:hidden', false);
|
||||||
|
$response->assertSee('flex min-w-0 items-center text-xs text-neutral-400', false);
|
||||||
|
$response->assertSee('hidden flex-wrap items-center gap-y-1 md:flex', false);
|
||||||
|
$response->assertSee('flex flex-wrap items-center gap-1', false);
|
||||||
|
$response->assertSee(
|
||||||
|
'scrollbar flex min-h-10 w-full flex-nowrap items-center gap-6 overflow-x-scroll overflow-y-hidden pb-1 whitespace-nowrap md:w-auto md:overflow-visible',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
$response->assertSee('shrink-0', false);
|
||||||
|
$response->assertSee('Actions');
|
||||||
|
$response->assertSee('dropdown-item-touch', false);
|
||||||
|
$response->assertSee('hidden flex-wrap items-center gap-2 md:flex', false);
|
||||||
|
$response->assertSee('window.innerWidth >= 768', false);
|
||||||
|
$response->assertSee(':style="panelStyles"', false);
|
||||||
|
$response->assertSee('absolute top-full z-50 mt-1 min-w-max max-w-[calc(100vw-1rem)] md:top-0 md:mt-6', false);
|
||||||
|
$response->assertSee('Pure Dockerfile Example');
|
||||||
|
$response->assertSee('Running');
|
||||||
|
$response->assertSee('pt-2 pb-4 md:pb-10', false);
|
||||||
|
|
||||||
|
expect($response->getContent())->not->toContain('hidden pt-2 pb-10 md:flex');
|
||||||
|
});
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->team = Team::factory()->create();
|
||||||
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
|
||||||
|
InstanceSettings::unguarded(function () {
|
||||||
|
InstanceSettings::query()->create([
|
||||||
|
'id' => 0,
|
||||||
|
'is_registration_enabled' => true,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
session(['currentTeam' => $this->team]);
|
||||||
|
|
||||||
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
|
||||||
|
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||||
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||||
|
$this->application = Application::factory()->create([
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
'status' => 'running',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders deployment logs in a full-height layout', function () {
|
||||||
|
$deployment = ApplicationDeploymentQueue::create([
|
||||||
|
'application_id' => $this->application->id,
|
||||||
|
'deployment_uuid' => 'deploy-layout-test',
|
||||||
|
'server_id' => $this->server->id,
|
||||||
|
'status' => ApplicationDeploymentStatus::FINISHED->value,
|
||||||
|
'logs' => json_encode([
|
||||||
|
[
|
||||||
|
'command' => null,
|
||||||
|
'output' => 'rolling update started',
|
||||||
|
'type' => 'stdout',
|
||||||
|
'timestamp' => now()->toISOString(),
|
||||||
|
'hidden' => false,
|
||||||
|
'batch' => 1,
|
||||||
|
'order' => 1,
|
||||||
|
],
|
||||||
|
], JSON_THROW_ON_ERROR),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->get(route('project.application.deployment.show', [
|
||||||
|
'project_uuid' => $this->project->uuid,
|
||||||
|
'environment_uuid' => $this->environment->uuid,
|
||||||
|
'application_uuid' => $this->application->uuid,
|
||||||
|
'deployment_uuid' => $deployment->deployment_uuid,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('rolling update started');
|
||||||
|
$response->assertSee('flex h-[calc(100vh-10rem)] min-h-40 flex-col overflow-hidden', false);
|
||||||
|
$response->assertSee('flex flex-1 min-h-0 flex-col overflow-hidden', false);
|
||||||
|
$response->assertSee('mt-4 flex flex-1 min-h-0 flex-col overflow-hidden', false);
|
||||||
|
$response->assertSee('flex min-h-0 flex-col w-full overflow-hidden bg-white', false);
|
||||||
|
$response->assertSee('flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar', false);
|
||||||
|
|
||||||
|
expect($response->getContent())->not->toContain('max-h-[30rem]');
|
||||||
|
});
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
InstanceSettings::unguarded(function () {
|
||||||
|
InstanceSettings::query()->create([
|
||||||
|
'id' => 0,
|
||||||
|
'is_registration_enabled' => true,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->user = User::factory()->create([
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->team = Team::factory()->create([
|
||||||
|
'show_boarding' => false,
|
||||||
|
]);
|
||||||
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||||
|
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
session(['currentTeam' => $this->team]);
|
||||||
|
|
||||||
|
$this->privateKey = PrivateKey::create([
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
'name' => 'Test Key',
|
||||||
|
'description' => 'Test SSH key',
|
||||||
|
'private_key' => '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||||
|
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||||
|
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||||
|
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->server = Server::factory()->create([
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
'private_key_id' => $this->privateKey->id,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::TRAEFIK->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->destination = StandaloneDocker::query()
|
||||||
|
->where('server_id', $this->server->id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$this->project = Project::factory()->create([
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->environment = Environment::factory()->create([
|
||||||
|
'project_id' => $this->project->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->application = Application::factory()->create([
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
'status' => 'running',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders responsive checkbox classes on the application configuration page', function () {
|
||||||
|
$response = $this->get(route('project.application.configuration', [
|
||||||
|
'project_uuid' => $this->project->uuid,
|
||||||
|
'environment_uuid' => $this->environment->uuid,
|
||||||
|
'application_uuid' => $this->application->uuid,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('Use a Build Server?');
|
||||||
|
$response->assertSee('form-control flex max-w-full flex-row items-center gap-4 py-1 pr-2', false);
|
||||||
|
$response->assertSee('label flex w-full max-w-full min-w-0 items-center gap-4 px-0', false);
|
||||||
|
$response->assertSee('flex min-w-0 grow gap-2 break-words', false);
|
||||||
|
$response->assertSee('shrink-0', false);
|
||||||
|
$response->assertSee('pt-2 w-full sm:w-96', false);
|
||||||
|
|
||||||
|
expect($response->getContent())->not->toContain('min-w-fit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders responsive checkbox classes on the server page', function () {
|
||||||
|
$response = $this->get(route('server.show', [
|
||||||
|
'server_uuid' => $this->server->uuid,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('Use it as a build server?');
|
||||||
|
$response->assertSee('form-control flex max-w-full flex-row items-center gap-4 py-1 pr-2', false);
|
||||||
|
$response->assertSee('label flex w-full max-w-full min-w-0 items-center gap-4 px-0', false);
|
||||||
|
$response->assertSee('flex min-w-0 grow gap-2 break-words', false);
|
||||||
|
$response->assertSee('shrink-0', false);
|
||||||
|
$response->assertSee('w-full sm:w-96', false);
|
||||||
|
|
||||||
|
expect($response->getContent())->not->toContain('min-w-fit');
|
||||||
|
});
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Livewire\Upgrade;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('initializes latest version during mount from cached versions data', function () {
|
||||||
|
config(['constants.coolify.version' => '4.0.0-beta.998']);
|
||||||
|
InstanceSettings::create([
|
||||||
|
'id' => 0,
|
||||||
|
'new_version_available' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Cache::shouldReceive('remember')
|
||||||
|
->once()
|
||||||
|
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||||
|
->andReturn([
|
||||||
|
'coolify' => [
|
||||||
|
'v4' => [
|
||||||
|
'version' => '4.0.0-beta.999',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test(Upgrade::class)
|
||||||
|
->assertSet('currentVersion', '4.0.0-beta.998')
|
||||||
|
->assertSet('latestVersion', '4.0.0-beta.999')
|
||||||
|
->assertSet('isUpgradeAvailable', true)
|
||||||
|
->assertSee('4.0.0-beta.998')
|
||||||
|
->assertSee('4.0.0-beta.999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to 0.0.0 during mount when cached versions data is unavailable', function () {
|
||||||
|
InstanceSettings::create([
|
||||||
|
'id' => 0,
|
||||||
|
'new_version_available' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Cache::shouldReceive('remember')
|
||||||
|
->once()
|
||||||
|
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||||
|
->andReturn(null);
|
||||||
|
|
||||||
|
Livewire::test(Upgrade::class)
|
||||||
|
->assertSet('latestVersion', '0.0.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears stale upgrade availability when current version already matches latest version', function () {
|
||||||
|
config(['constants.coolify.version' => '4.0.0-beta.999']);
|
||||||
|
InstanceSettings::create([
|
||||||
|
'id' => 0,
|
||||||
|
'new_version_available' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Cache::shouldReceive('remember')
|
||||||
|
->once()
|
||||||
|
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||||
|
->andReturn([
|
||||||
|
'coolify' => [
|
||||||
|
'v4' => [
|
||||||
|
'version' => '4.0.0-beta.999',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test(Upgrade::class)
|
||||||
|
->assertSet('latestVersion', '4.0.0-beta.999')
|
||||||
|
->assertSet('isUpgradeAvailable', false);
|
||||||
|
|
||||||
|
expect(InstanceSettings::findOrFail(0)->new_version_available)->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears stale upgrade availability when current version is newer than cached latest version', function () {
|
||||||
|
config(['constants.coolify.version' => '4.0.0-beta.1000']);
|
||||||
|
InstanceSettings::create([
|
||||||
|
'id' => 0,
|
||||||
|
'new_version_available' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Cache::shouldReceive('remember')
|
||||||
|
->once()
|
||||||
|
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||||
|
->andReturn([
|
||||||
|
'coolify' => [
|
||||||
|
'v4' => [
|
||||||
|
'version' => '4.0.0-beta.999',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test(Upgrade::class)
|
||||||
|
->assertSet('latestVersion', '4.0.0-beta.999')
|
||||||
|
->assertSet('isUpgradeAvailable', false);
|
||||||
|
|
||||||
|
expect(InstanceSettings::findOrFail(0)->new_version_available)->toBeFalse();
|
||||||
|
});
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Environment;
|
||||||
|
use App\Models\GithubApp;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Create root team and admin user (instance admin)
|
||||||
|
$this->rootTeam = Team::factory()->create(['id' => 0, 'name' => 'Root Team']);
|
||||||
|
$this->adminUser = User::factory()->create();
|
||||||
|
$this->rootTeam->members()->attach($this->adminUser->id, ['role' => 'owner']);
|
||||||
|
$this->actingAs($this->adminUser);
|
||||||
|
session(['currentTeam' => $this->rootTeam]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deletes a user whose team has a github app with applications', function () {
|
||||||
|
// Create the user to be deleted with their own team
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
$targetTeam = $targetUser->teams()->first(); // created by User::created event
|
||||||
|
|
||||||
|
// Create a private key for the team
|
||||||
|
$privateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
|
||||||
|
// Create a server and destination for the team
|
||||||
|
$server = Server::factory()->create([
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
]);
|
||||||
|
$destination = StandaloneDocker::factory()->create(['server_id' => $server->id]);
|
||||||
|
|
||||||
|
// Create a project and environment
|
||||||
|
$project = Project::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||||
|
|
||||||
|
// Create a GitHub App owned by the target team
|
||||||
|
$githubApp = GithubApp::create([
|
||||||
|
'name' => 'Test GitHub App',
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
'api_url' => 'https://api.github.com',
|
||||||
|
'html_url' => 'https://github.com',
|
||||||
|
'is_public' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create an application that uses the GitHub App as its source
|
||||||
|
$application = Application::factory()->create([
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
'source_id' => $githubApp->id,
|
||||||
|
'source_type' => GithubApp::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete the user — this should NOT throw a GithubApp exception
|
||||||
|
$targetUser->delete();
|
||||||
|
|
||||||
|
// Assert user is deleted
|
||||||
|
expect(User::find($targetUser->id))->toBeNull();
|
||||||
|
|
||||||
|
// Assert the GitHub App is deleted
|
||||||
|
expect(GithubApp::find($githubApp->id))->toBeNull();
|
||||||
|
|
||||||
|
// Assert the application is deleted
|
||||||
|
expect(Application::find($application->id))->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not delete system-wide github apps when deleting a different team', function () {
|
||||||
|
// Create a system-wide GitHub App owned by the root team
|
||||||
|
$rootPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$systemGithubApp = GithubApp::create([
|
||||||
|
'name' => 'System GitHub App',
|
||||||
|
'team_id' => $this->rootTeam->id,
|
||||||
|
'private_key_id' => $rootPrivateKey->id,
|
||||||
|
'api_url' => 'https://api.github.com',
|
||||||
|
'html_url' => 'https://github.com',
|
||||||
|
'is_public' => false,
|
||||||
|
'is_system_wide' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create a target user with their own team
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
$targetTeam = $targetUser->teams()->first();
|
||||||
|
|
||||||
|
// Create an application on the target team that uses the system-wide GitHub App
|
||||||
|
$privateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$server = Server::factory()->create([
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
]);
|
||||||
|
$destination = StandaloneDocker::factory()->create(['server_id' => $server->id]);
|
||||||
|
$project = Project::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||||
|
|
||||||
|
$application = Application::factory()->create([
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
'source_id' => $systemGithubApp->id,
|
||||||
|
'source_type' => GithubApp::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete the target user — should NOT throw or delete the system-wide GitHub App
|
||||||
|
$targetUser->delete();
|
||||||
|
|
||||||
|
// Assert user is deleted
|
||||||
|
expect(User::find($targetUser->id))->toBeNull();
|
||||||
|
|
||||||
|
// Assert the system-wide GitHub App still exists
|
||||||
|
expect(GithubApp::find($systemGithubApp->id))->not->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfers instance-wide github app to root team when owning user is deleted', function () {
|
||||||
|
// Create a user whose team owns an instance-wide GitHub App
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
$targetTeam = $targetUser->teams()->first();
|
||||||
|
|
||||||
|
$targetPrivateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$instanceWideApp = GithubApp::create([
|
||||||
|
'name' => 'Instance-Wide GitHub App',
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $targetPrivateKey->id,
|
||||||
|
'api_url' => 'https://api.github.com',
|
||||||
|
'html_url' => 'https://github.com',
|
||||||
|
'is_public' => false,
|
||||||
|
'is_system_wide' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create an application on the ROOT team that uses this instance-wide GitHub App
|
||||||
|
$rootPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootServer = Server::factory()->create([
|
||||||
|
'team_id' => $this->rootTeam->id,
|
||||||
|
'private_key_id' => $rootPrivateKey->id,
|
||||||
|
]);
|
||||||
|
$rootDestination = StandaloneDocker::factory()->create(['server_id' => $rootServer->id]);
|
||||||
|
$rootProject = Project::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootEnvironment = Environment::factory()->create(['project_id' => $rootProject->id]);
|
||||||
|
|
||||||
|
$otherTeamApp = Application::factory()->create([
|
||||||
|
'environment_id' => $rootEnvironment->id,
|
||||||
|
'destination_id' => $rootDestination->id,
|
||||||
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
'source_id' => $instanceWideApp->id,
|
||||||
|
'source_type' => GithubApp::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete the user — should succeed and transfer the instance-wide app to root team
|
||||||
|
$targetUser->delete();
|
||||||
|
|
||||||
|
// Assert user is deleted
|
||||||
|
expect(User::find($targetUser->id))->toBeNull();
|
||||||
|
|
||||||
|
// Assert the instance-wide GitHub App is preserved and transferred to root team
|
||||||
|
$instanceWideApp->refresh();
|
||||||
|
expect($instanceWideApp)->not->toBeNull();
|
||||||
|
expect($instanceWideApp->team_id)->toBe($this->rootTeam->id);
|
||||||
|
|
||||||
|
// Assert the other team's application still has its source intact
|
||||||
|
$otherTeamApp->refresh();
|
||||||
|
expect($otherTeamApp->source_id)->toBe($instanceWideApp->id);
|
||||||
|
expect($otherTeamApp->source_type)->toBe(GithubApp::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfers instance-wide github app to root team when team is deleted directly', function () {
|
||||||
|
// Create a team that owns an instance-wide GitHub App
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
$targetTeam = $targetUser->teams()->first();
|
||||||
|
|
||||||
|
$targetPrivateKey = PrivateKey::factory()->create(['team_id' => $targetTeam->id]);
|
||||||
|
$instanceWideApp = GithubApp::create([
|
||||||
|
'name' => 'Instance-Wide GitHub App',
|
||||||
|
'team_id' => $targetTeam->id,
|
||||||
|
'private_key_id' => $targetPrivateKey->id,
|
||||||
|
'api_url' => 'https://api.github.com',
|
||||||
|
'html_url' => 'https://github.com',
|
||||||
|
'is_public' => false,
|
||||||
|
'is_system_wide' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create an application on the ROOT team that uses this instance-wide GitHub App
|
||||||
|
$rootPrivateKey = PrivateKey::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootServer = Server::factory()->create([
|
||||||
|
'team_id' => $this->rootTeam->id,
|
||||||
|
'private_key_id' => $rootPrivateKey->id,
|
||||||
|
]);
|
||||||
|
$rootDestination = StandaloneDocker::factory()->create(['server_id' => $rootServer->id]);
|
||||||
|
$rootProject = Project::factory()->create(['team_id' => $this->rootTeam->id]);
|
||||||
|
$rootEnvironment = Environment::factory()->create(['project_id' => $rootProject->id]);
|
||||||
|
|
||||||
|
$otherTeamApp = Application::factory()->create([
|
||||||
|
'environment_id' => $rootEnvironment->id,
|
||||||
|
'destination_id' => $rootDestination->id,
|
||||||
|
'destination_type' => StandaloneDocker::class,
|
||||||
|
'source_id' => $instanceWideApp->id,
|
||||||
|
'source_type' => GithubApp::class,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete the team directly — should transfer instance-wide app to root team
|
||||||
|
$targetTeam->delete();
|
||||||
|
|
||||||
|
// Assert the instance-wide GitHub App is preserved and transferred to root team
|
||||||
|
$instanceWideApp->refresh();
|
||||||
|
expect($instanceWideApp)->not->toBeNull();
|
||||||
|
expect($instanceWideApp->team_id)->toBe($this->rootTeam->id);
|
||||||
|
|
||||||
|
// Assert the other team's application still has its source intact
|
||||||
|
$otherTeamApp->refresh();
|
||||||
|
expect($otherTeamApp->source_id)->toBe($instanceWideApp->id);
|
||||||
|
expect($otherTeamApp->source_type)->toBe(GithubApp::class);
|
||||||
|
});
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.472"
|
"version": "4.0.0-beta.473"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0"
|
"version": "4.0.0"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"version": "1.0.13"
|
"version": "1.0.13"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.12"
|
"version": "1.0.13"
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"version": "0.0.21"
|
"version": "0.0.21"
|
||||||
|
|||||||
Reference in New Issue
Block a user