Merge branch 'next' into add-emqx-as-a-service-template
This commit is contained in:
commit
8b4e8fd783
71 changed files with 1781 additions and 670 deletions
|
|
@ -88,6 +88,14 @@ private function processFile(string $file): false|array
|
||||||
$payload['envs'] = base64_encode($envFileContent);
|
$payload['envs'] = base64_encode($envFileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str($data->get('amd_only'))->toBoolean()) {
|
||||||
|
$payload['amd_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str($data->get('arm_only'))->toBoolean()) {
|
||||||
|
$payload['arm_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +168,14 @@ private function processFileWithFqdn(string $file): false|array
|
||||||
$payload['envs'] = base64_encode($modifiedEnvContent);
|
$payload['envs'] = base64_encode($modifiedEnvContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str($data->get('amd_only'))->toBoolean()) {
|
||||||
|
$payload['amd_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str($data->get('arm_only'))->toBoolean()) {
|
||||||
|
$payload['arm_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,6 +245,14 @@ private function processFileWithFqdnRaw(string $file): false|array
|
||||||
$payload['envs'] = $modifiedEnvContent;
|
$payload['envs'] = $modifiedEnvContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str($data->get('amd_only'))->toBoolean()) {
|
||||||
|
$payload['amd_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str($data->get('arm_only'))->toBoolean()) {
|
||||||
|
$payload['arm_only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ public function applications(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")'],
|
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -383,7 +383,7 @@ public function create_public_application(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")'],
|
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -549,7 +549,7 @@ public function create_private_gh_app_application(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")'],
|
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -2397,7 +2397,7 @@ public function delete_by_uuid(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")'],
|
'domain' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ public function services(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'],
|
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io").'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -843,7 +843,7 @@ public function delete_by_uuid(Request $request)
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
'name' => ['type' => 'string', 'description' => 'The service name as defined in docker-compose.'],
|
||||||
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").'],
|
'url' => ['type' => 'string', 'description' => 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io").'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -2877,7 +2877,7 @@ private function generate_healthcheck_commands()
|
||||||
$scheme = $this->sanitizeHealthCheckValue($this->application->health_check_scheme, '/^https?$/', 'http');
|
$scheme = $this->sanitizeHealthCheckValue($this->application->health_check_scheme, '/^https?$/', 'http');
|
||||||
$host = $this->sanitizeHealthCheckValue($this->application->health_check_host, '/^[a-zA-Z0-9.\-_]+$/', 'localhost');
|
$host = $this->sanitizeHealthCheckValue($this->application->health_check_host, '/^[a-zA-Z0-9.\-_]+$/', 'localhost');
|
||||||
$path = $this->application->health_check_path
|
$path = $this->application->health_check_path
|
||||||
? $this->sanitizeHealthCheckValue($this->application->health_check_path, '#^[a-zA-Z0-9/\-_.~%]+$#', '/')
|
? $this->sanitizeHealthCheckValue($this->application->health_check_path, '#^[a-zA-Z0-9/\-_.~%,;]+$#', '/')
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
$url = escapeshellarg("{$scheme}://{$host}:{$health_check_port}".($path ?? '/'));
|
$url = escapeshellarg("{$scheme}://{$host}:{$health_check_port}".($path ?? '/'));
|
||||||
|
|
|
||||||
|
|
@ -1496,7 +1496,10 @@ public function getServicesProperty()
|
||||||
'category' => 'Services',
|
'category' => 'Services',
|
||||||
'resourceType' => 'service',
|
'resourceType' => 'service',
|
||||||
'logo' => data_get($service, 'logo'),
|
'logo' => data_get($service, 'logo'),
|
||||||
]);
|
] + array_filter([
|
||||||
|
'amd_only' => data_get($service, 'amd_only') ? true : null,
|
||||||
|
'arm_only' => data_get($service, 'arm_only') ? true : null,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$cachedServices = $items->toArray();
|
$cachedServices = $items->toArray();
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class HealthChecks extends Component
|
||||||
#[Validate(['nullable', 'integer', 'min:1', 'max:65535'])]
|
#[Validate(['nullable', 'integer', 'min:1', 'max:65535'])]
|
||||||
public ?string $healthCheckPort = null;
|
public ?string $healthCheckPort = null;
|
||||||
|
|
||||||
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'])]
|
#[Validate(['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'])]
|
||||||
public string $healthCheckPath;
|
public string $healthCheckPath;
|
||||||
|
|
||||||
#[Validate(['integer'])]
|
#[Validate(['integer'])]
|
||||||
|
|
@ -62,7 +62,7 @@ class HealthChecks extends Component
|
||||||
'healthCheckEnabled' => 'boolean',
|
'healthCheckEnabled' => 'boolean',
|
||||||
'healthCheckType' => 'string|in:http,cmd',
|
'healthCheckType' => 'string|in:http,cmd',
|
||||||
'healthCheckCommand' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
|
'healthCheckCommand' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
|
||||||
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'],
|
'healthCheckPath' => ['required', 'string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'],
|
||||||
'healthCheckPort' => 'nullable|integer|min:1|max:65535',
|
'healthCheckPort' => 'nullable|integer|min:1|max:65535',
|
||||||
'healthCheckHost' => ['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
|
'healthCheckHost' => ['required', 'string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
|
||||||
'healthCheckMethod' => 'required|string|in:GET,HEAD,POST,OPTIONS',
|
'healthCheckMethod' => 'required|string|in:GET,HEAD,POST,OPTIONS',
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -71,25 +71,31 @@ protected static function booted()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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 @@ public function webhookNotificationSettings()
|
||||||
{
|
{
|
||||||
return $this->hasOne(WebhookNotificationSettings::class);
|
return $this->hasOne(WebhookNotificationSettings::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,10 +203,24 @@ public static function volumeNameMessages(string $field = 'name'): array
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern for port mappings (e.g. 3000:3000, 8080:80, 8000-8010:8000-8010)
|
* 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 @@ public static function portMappingRules(): array
|
||||||
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).',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ function sharedDataApplications()
|
||||||
'health_check_enabled' => 'boolean',
|
'health_check_enabled' => 'boolean',
|
||||||
'health_check_type' => 'string|in:http,cmd',
|
'health_check_type' => 'string|in:http,cmd',
|
||||||
'health_check_command' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
|
'health_check_command' => ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'],
|
||||||
'health_check_path' => ['string', 'regex:#^[a-zA-Z0-9/\-_.~%]+$#'],
|
'health_check_path' => ['string', 'regex:#^[a-zA-Z0-9/\-_.~%,;]+$#'],
|
||||||
'health_check_port' => 'integer|nullable|min:1|max:65535',
|
'health_check_port' => 'integer|nullable|min:1|max:65535',
|
||||||
'health_check_host' => ['string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
|
'health_check_host' => ['string', 'regex:/^[a-zA-Z0-9.\-_]+$/'],
|
||||||
'health_check_method' => 'string|in:GET,HEAD,POST,OPTIONS',
|
'health_check_method' => 'string|in:GET,HEAD,POST,OPTIONS',
|
||||||
|
|
|
||||||
|
|
@ -3660,13 +3660,21 @@ function convertGitUrl(string $gitRepository, string $deploymentType, GithubApp|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preg_match('/(?<=:)\d+(?=\/)/', $gitRepository, $matches);
|
$normalizedRepository = $repository;
|
||||||
|
|
||||||
if (count($matches) === 1) {
|
if (str($normalizedRepository)->contains('://')) {
|
||||||
$providerInfo['port'] = $matches[0];
|
$parsedRepository = parse_url($normalizedRepository);
|
||||||
$gitHost = str($gitRepository)->before(':');
|
|
||||||
$gitRepo = str($gitRepository)->after('/');
|
if ($parsedRepository !== false && array_key_exists('port', $parsedRepository)) {
|
||||||
$repository = "$gitHost:$gitRepo";
|
$providerInfo['port'] = (string) $parsedRepository['port'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preg_match('/^(?<host>[^:]+):(?<port>\d+)\/(?<path>.+)$/', $normalizedRepository, $matches);
|
||||||
|
|
||||||
|
if (! empty($matches['port'])) {
|
||||||
|
$providerInfo['port'] = $matches['port'];
|
||||||
|
$repository = "{$matches['host']}:{$matches['path']}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
14
composer.lock
generated
14
composer.lock
generated
|
|
@ -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.474',
|
||||||
'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'),
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- dev_coolify_data:/data/coolify
|
- dev_coolify_data:/data/coolify
|
||||||
|
- dev_coolify_data:/var/lib/docker/volumes/coolify_dev_coolify_data/_data
|
||||||
- dev_backups_data:/data/coolify/backups
|
- dev_backups_data:/data/coolify/backups
|
||||||
- dev_postgres_data:/data/coolify/_volumes/database
|
- dev_postgres_data:/data/coolify/_volumes/database
|
||||||
- dev_redis_data:/data/coolify/_volumes/redis
|
- dev_redis_data:/data/coolify/_volumes/redis
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
21
docker/coolify-realtime/package-lock.json
generated
21
docker/coolify-realtime/package-lock.json
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
12
openapi.json
12
openapi.json
|
|
@ -361,7 +361,7 @@
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -811,7 +811,7 @@
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -1261,7 +1261,7 @@
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -2692,7 +2692,7 @@
|
||||||
},
|
},
|
||||||
"domain": {
|
"domain": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -10811,7 +10811,7 @@
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -11142,7 +11142,7 @@
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comma-separated list of URLs (e.g. \"http:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
|
"description": "Comma-separated list of URLs (e.g. \"https:\/\/app.coolify.io,https:\/\/app2.coolify.io\")."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
|
||||||
12
openapi.yaml
12
openapi.yaml
|
|
@ -258,7 +258,7 @@ paths:
|
||||||
docker_compose_domains:
|
docker_compose_domains:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
||||||
watch_paths:
|
watch_paths:
|
||||||
type: string
|
type: string
|
||||||
description: 'The watch paths.'
|
description: 'The watch paths.'
|
||||||
|
|
@ -546,7 +546,7 @@ paths:
|
||||||
docker_compose_domains:
|
docker_compose_domains:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
||||||
watch_paths:
|
watch_paths:
|
||||||
type: string
|
type: string
|
||||||
description: 'The watch paths.'
|
description: 'The watch paths.'
|
||||||
|
|
@ -834,7 +834,7 @@ paths:
|
||||||
docker_compose_domains:
|
docker_compose_domains:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
||||||
watch_paths:
|
watch_paths:
|
||||||
type: string
|
type: string
|
||||||
description: 'The watch paths.'
|
description: 'The watch paths.'
|
||||||
|
|
@ -1735,7 +1735,7 @@ paths:
|
||||||
docker_compose_domains:
|
docker_compose_domains:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
description: 'Array of URLs to be applied to containers of a dockercompose application.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, domain: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io")' } }, type: object }
|
||||||
watch_paths:
|
watch_paths:
|
||||||
type: string
|
type: string
|
||||||
description: 'The watch paths.'
|
description: 'The watch paths.'
|
||||||
|
|
@ -6886,7 +6886,7 @@ paths:
|
||||||
urls:
|
urls:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a service.'
|
description: 'Array of URLs to be applied to containers of a service.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io").' } }, type: object }
|
||||||
force_domain_override:
|
force_domain_override:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -7075,7 +7075,7 @@ paths:
|
||||||
urls:
|
urls:
|
||||||
type: array
|
type: array
|
||||||
description: 'Array of URLs to be applied to containers of a service.'
|
description: 'Array of URLs to be applied to containers of a service.'
|
||||||
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "http://app.coolify.io,https://app2.coolify.io").' } }, type: object }
|
items: { properties: { name: { type: string, description: 'The service name as defined in docker-compose.' }, url: { type: string, description: 'Comma-separated list of URLs (e.g. "https://app.coolify.io,https://app2.coolify.io").' } }, type: object }
|
||||||
force_domain_override:
|
force_domain_override:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -539,6 +539,15 @@ install_docker_manually() {
|
||||||
echo "Docker installed successfully."
|
echo "Docker installed successfully."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_docker_from_rhel_repo() {
|
||||||
|
echo " - Installing Docker from the RHEL repository for Rocky Linux..."
|
||||||
|
rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo
|
||||||
|
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
systemctl --now enable docker
|
||||||
|
}
|
||||||
|
|
||||||
log_section "Step 3/9: Checking Docker installation"
|
log_section "Step 3/9: Checking Docker installation"
|
||||||
echo "3/9 Checking Docker installation..."
|
echo "3/9 Checking Docker installation..."
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
|
@ -579,6 +588,13 @@ if ! [ -x "$(command -v docker)" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
"rocky")
|
||||||
|
install_docker_from_rhel_repo
|
||||||
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
"almalinux" | "tencentos")
|
"almalinux" | "tencentos")
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.472"
|
"version": "4.0.0-beta.474"
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
|
|
||||||
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 @@ @utility dropdown-item {
|
||||||
@apply flex relative gap-2 justify-start items-center py-1 pr-4 pl-2 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-none data-disabled:pointer-events-none data-disabled:opacity-50 focus-visible:bg-neutral-100 dark:focus-visible:bg-coollabs;
|
@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 @@ class="inline-flex items-center justify-start pr-8 transition-colors focus:outli
|
||||||
</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 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
|
||||||
</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
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
||||||
</a>
|
</a>
|
||||||
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.index', $parameters) }}"><span class="menu-item-label">General</span></a>
|
href="{{ route('project.service.index', $parameters) }}"><span class="menu-item-label">General</span></a>
|
||||||
|
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
|
href="{{ route('project.service.index.advanced', $parameters) }}"><span class="menu-item-label">Advanced</span></a>
|
||||||
@if ($serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated)
|
@if ($serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated)
|
||||||
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.database.backups', $parameters) }}"><span class="menu-item-label">Backups</span></a>
|
href="{{ route('project.service.database.backups', $parameters) }}"><span class="menu-item-label">Backups</span></a>
|
||||||
|
|
|
||||||
|
|
@ -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 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<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" />
|
||||||
|
|
|
||||||
|
|
@ -835,6 +835,20 @@ class="h-5 w-5 text-warning-600 dark:text-warning-400"
|
||||||
<div class="font-medium text-neutral-900 dark:text-white truncate"
|
<div class="font-medium text-neutral-900 dark:text-white truncate"
|
||||||
x-text="item.name">
|
x-text="item.name">
|
||||||
</div>
|
</div>
|
||||||
|
<template x-if="item.amd_only">
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 shrink-0"
|
||||||
|
title="This service only supports AMD64/x86_64 architecture">
|
||||||
|
AMD only
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template x-if="item.arm_only">
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 shrink-0"
|
||||||
|
title="This service only supports ARM64/aarch64 architecture">
|
||||||
|
ARM only
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<span
|
<span
|
||||||
class="text-xs text-neutral-500 dark:text-neutral-400 shrink-0"
|
class="text-xs text-neutral-500 dark:text-neutral-400 shrink-0"
|
||||||
x-text="item.quickcommand"
|
x-text="item.quickcommand"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>Advanced configuration for your application.</div>
|
<div>Advanced configuration for your application.</div>
|
||||||
<div class="flex flex-col gap-1 pt-4">
|
<div class="flex flex-col gap-1 pt-4">
|
||||||
<h3>General</h3>
|
<h3>Build</h3>
|
||||||
@if ($application->git_based())
|
|
||||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
|
||||||
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
|
||||||
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
|
||||||
:canResource="$application" />
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="When enabled, anyone can trigger PR deployments. When disabled, only repository members, collaborators, and contributors can trigger PR deployments."
|
|
||||||
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
|
||||||
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
|
||||||
@endif
|
|
||||||
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave
|
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave
|
||||||
id="disableBuildCache" label="Disable Build Cache" canGate="update" :canResource="$application" />
|
id="disableBuildCache" label="Disable Build Cache" canGate="update" :canResource="$application" />
|
||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
|
|
@ -29,6 +17,55 @@
|
||||||
instantSave id="includeSourceCommitInBuild" label="Include Source Commit in Build" canGate="update"
|
instantSave id="includeSourceCommitInBuild" label="Include Source Commit in Build" canGate="update"
|
||||||
:canResource="$application" />
|
:canResource="$application" />
|
||||||
|
|
||||||
|
<h3 class="pt-4">Container</h3>
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
||||||
|
:canResource="$application" />
|
||||||
|
@if ($isConsistentContainerNameEnabled === false)
|
||||||
|
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
||||||
|
:canResource="$application" />
|
||||||
|
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($application->git_based())
|
||||||
|
<h3 class="pt-4">Deployment</h3>
|
||||||
|
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||||
|
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
|
||||||
|
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
|
||||||
|
:canResource="$application" />
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="When enabled, anyone can trigger PR deployments. When disabled, only repository members, collaborators, and contributors can trigger PR deployments."
|
||||||
|
instantSave id="isPrDeploymentsPublicEnabled" label="Allow Public PR Deployments" canGate="update"
|
||||||
|
:canResource="$application" :disabled="!$isPreviewDeploymentsEnabled" />
|
||||||
|
|
||||||
|
<h3 class="pt-4">Git</h3>
|
||||||
|
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
||||||
|
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
||||||
|
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
||||||
|
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
||||||
|
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
||||||
|
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
||||||
|
canGate="update" :canResource="$application" />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($application->build_pack === 'dockercompose')
|
||||||
|
<h3 class="pt-4">Docker Compose</h3>
|
||||||
|
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
||||||
|
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
||||||
|
canGate="update" :canResource="$application" />
|
||||||
|
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" 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>."
|
||||||
|
canGate="update" :canResource="$application" />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<h3 class="pt-4">Proxy</h3>
|
||||||
@if ($application->settings->is_container_label_readonly_enabled)
|
@if ($application->settings->is_container_label_readonly_enabled)
|
||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Your application will be available only on https if your domain starts with https://..."
|
helper="Your application will be available only on https if your domain starts with https://..."
|
||||||
|
|
@ -49,45 +86,10 @@
|
||||||
helper="Readonly labels are disabled. You need to set the labels in the labels section." disabled
|
helper="Readonly labels are disabled. You need to set the labels in the labels section." disabled
|
||||||
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
|
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
|
||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack === 'dockercompose')
|
|
||||||
<h3>Docker Compose</h3>
|
|
||||||
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
|
|
||||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
|
|
||||||
canGate="update" :canResource="$application" />
|
|
||||||
@endif
|
|
||||||
<h3 class="pt-4">Container Names</h3>
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
|
||||||
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
|
|
||||||
:canResource="$application" />
|
|
||||||
@if ($isConsistentContainerNameEnabled === false)
|
|
||||||
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
|
|
||||||
<x-forms.input
|
|
||||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
|
||||||
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
|
|
||||||
:canResource="$application" />
|
|
||||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
@if ($application->build_pack === 'dockercompose')
|
|
||||||
<h3 class="pt-4">Network</h3>
|
|
||||||
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" 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>."
|
|
||||||
canGate="update" :canResource="$application" />
|
|
||||||
@endif
|
|
||||||
<h3 class="pt-4">Logs</h3>
|
<h3 class="pt-4">Logs</h3>
|
||||||
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||||
instantSave id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$application" />
|
instantSave id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$application" />
|
||||||
@if ($application->git_based())
|
|
||||||
<h3>Git</h3>
|
|
||||||
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
|
|
||||||
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
|
|
||||||
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
|
|
||||||
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
|
|
||||||
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
|
|
||||||
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
|
|
||||||
canGate="update" :canResource="$application" />
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
|
||||||
</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 @@ class="shrink-0 text-gray-500">{{ $line['timestamp'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
@if (!isDatabaseImage(data_get($service, 'image')))
|
@if (!isDatabaseImage(data_get($service, 'image')))
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||||
label="Domains for {{ $serviceName }}"
|
label="Domains for {{ $serviceName }}"
|
||||||
id="parsedServiceDomains.{{ str($serviceName)->replace('-', '_')->replace('.', '_') }}.domain"
|
id="parsedServiceDomains.{{ str($serviceName)->replace('-', '_')->replace('.', '_') }}.domain"
|
||||||
x-bind:disabled="shouldDisable()"></x-forms.input>
|
x-bind:disabled="shouldDisable()"></x-forms.input>
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
x-bind:disabled="!canUpdate" />
|
x-bind:disabled="!canUpdate" />
|
||||||
@else
|
@else
|
||||||
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains"
|
<x-forms.input placeholder="https://coolify.io" wire:model="fqdn" label="Domains"
|
||||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."
|
||||||
x-bind:disabled="!canUpdate" />
|
x-bind:disabled="!canUpdate" />
|
||||||
@can('update', $application)
|
@can('update', $application)
|
||||||
<x-forms.button wire:click="getWildcardDomain">Generate Domain
|
<x-forms.button wire:click="getWildcardDomain">Generate Domain
|
||||||
|
|
@ -276,7 +276,7 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||||
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 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||||
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 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||||
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 @@ class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blu
|
||||||
|
|
||||||
<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 @@ class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blu
|
||||||
<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 @@ class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:o
|
||||||
$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 @@ class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:o
|
||||||
</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', () => {
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,34 @@ class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/1
|
||||||
</template>
|
</template>
|
||||||
</x-slot:logo>
|
</x-slot:logo>
|
||||||
</x-resource-view>
|
</x-resource-view>
|
||||||
|
<template x-if="service.amd_only">
|
||||||
|
<div class="absolute top-2 right-10 group">
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 text-xs rounded bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 cursor-pointer">
|
||||||
|
AMD only
|
||||||
|
</span>
|
||||||
|
<div class="info-helper-popup right-0 w-sm">
|
||||||
|
<div class="p-4">
|
||||||
|
This service only supports AMD64/x86_64 architecture. It will not work
|
||||||
|
on ARM-based servers (e.g., Apple Silicon, Raspberry Pi, AWS Graviton).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="service.arm_only">
|
||||||
|
<div class="absolute top-2 right-10 group">
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 text-xs rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200 cursor-pointer">
|
||||||
|
ARM only
|
||||||
|
</span>
|
||||||
|
<div class="info-helper-popup right-0 w-sm">
|
||||||
|
<div class="p-4">
|
||||||
|
This service only supports ARM64/aarch64 architecture. It will not work
|
||||||
|
on AMD64/x86_64-based servers.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template x-if="shouldShowDocIcon(service)">
|
<template x-if="shouldShowDocIcon(service)">
|
||||||
<a :href="getDocLink(service) || coolifyDocsUrl(service.name)" target="_blank"
|
<a :href="getDocLink(service) || coolifyDocsUrl(service.name)" target="_blank"
|
||||||
@click.stop @mouseenter="resolveDocLink(service)"
|
@click.stop @mouseenter="resolveDocLink(service)"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
|
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
|
||||||
@if($requiredPort)
|
@if($requiredPort)
|
||||||
<x-callout type="warning" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
<x-callout type="info" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
||||||
<br><br>
|
<br><br>
|
||||||
<strong>Example:</strong> http://app.coolify.io:{{ $requiredPort }}
|
<strong>Example:</strong> https://app.coolify.io:{{ $requiredPort }},https://www.app.coolify.io:{{ $requiredPort }}
|
||||||
</x-callout>
|
</x-callout>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<x-forms.input canGate="update" :canResource="$application" placeholder="https://app.coolify.io" label="Domains"
|
<x-forms.input canGate="update" :canResource="$application" placeholder="https://app.coolify.io" label="Domains"
|
||||||
id="fqdn"
|
id="fqdn"
|
||||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
||||||
</svg>
|
</svg>
|
||||||
<span class="menu-item-label">Back</span>
|
<span class="menu-item-label">Back</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="sub-menu-item menu-item-active" href="#"><span class="menu-item-label">General</span></a>
|
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
|
href="{{ route('project.service.index', $parameters) }}"><span class="menu-item-label">General</span></a>
|
||||||
|
<a class="sub-menu-item" wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
|
href="{{ route('project.service.index.advanced', $parameters) }}"><span class="menu-item-label">Advanced</span></a>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
|
@ -23,63 +26,9 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
||||||
{{ data_get_str($service, 'name')->limit(10) }} >
|
{{ data_get_str($service, 'name')->limit(10) }} >
|
||||||
{{ data_get_str($serviceApplication, 'name')->limit(10) }} | Coolify
|
{{ data_get_str($serviceApplication, 'name')->limit(10) }} | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<form wire:submit='submitApplication'>
|
@if ($currentRoute === 'project.service.index.advanced')
|
||||||
<div class="flex items-center gap-2 pb-4">
|
<h2>Advanced</h2>
|
||||||
@if ($serviceApplication->human_name)
|
<div class="w-full sm:w-96 flex flex-col gap-1 pt-4">
|
||||||
<h2>{{ Str::headline($serviceApplication->human_name) }}</h2>
|
|
||||||
@else
|
|
||||||
<h2>{{ Str::headline($serviceApplication->name) }}</h2>
|
|
||||||
@endif
|
|
||||||
<x-forms.button canGate="update" :canResource="$serviceApplication" type="submit">Save</x-forms.button>
|
|
||||||
@can('update', $serviceApplication)
|
|
||||||
<x-modal-confirmation wire:click="convertToDatabase" title="Convert to Database"
|
|
||||||
buttonTitle="Convert to Database" submitAction="convertToDatabase" :actions="['The selected resource will be converted to a service database.']"
|
|
||||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
|
||||||
shortConfirmationLabel="Service Application Name" />
|
|
||||||
@endcan
|
|
||||||
@can('delete', $serviceApplication)
|
|
||||||
<x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton
|
|
||||||
submitAction="deleteApplication" :actions="['The selected service application container will be stopped and permanently deleted.']"
|
|
||||||
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
|
||||||
shortConfirmationLabel="Service Application Name" />
|
|
||||||
@endcan
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
@if ($requiredPort && !$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
|
||||||
<x-callout type="warning" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
|
||||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
|
||||||
<br><br>
|
|
||||||
<strong>Example:</strong> http://app.coolify.io:{{ $requiredPort }}
|
|
||||||
</x-callout>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Name" id="humanName"
|
|
||||||
placeholder="Human readable name"></x-forms.input>
|
|
||||||
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Description"
|
|
||||||
id="description"></x-forms.input>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
@if (!$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
|
||||||
@if ($serviceApplication->required_fqdn)
|
|
||||||
<x-forms.input canGate="update" :canResource="$serviceApplication" required placeholder="https://app.coolify.io"
|
|
||||||
label="Domains" id="fqdn"
|
|
||||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
|
||||||
@else
|
|
||||||
<x-forms.input canGate="update" :canResource="$serviceApplication" placeholder="https://app.coolify.io"
|
|
||||||
label="Domains" id="fqdn"
|
|
||||||
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
|
|
||||||
@endif
|
|
||||||
@endif
|
|
||||||
<x-forms.input canGate="update" :canResource="$serviceApplication"
|
|
||||||
helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
|
||||||
label="Image" id="image"></x-forms.input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3 class="py-2 pt-4">Advanced</h3>
|
|
||||||
<div class="w-96 flex flex-col gap-1">
|
|
||||||
@if (str($serviceApplication->image)->contains('pocketbase'))
|
@if (str($serviceApplication->image)->contains('pocketbase'))
|
||||||
<x-forms.checkbox canGate="update" :canResource="$serviceApplication" instantSave="instantSaveApplicationSettings" id="isGzipEnabled"
|
<x-forms.checkbox canGate="update" :canResource="$serviceApplication" instantSave="instantSaveApplicationSettings" id="isGzipEnabled"
|
||||||
label="Enable Gzip Compression"
|
label="Enable Gzip Compression"
|
||||||
|
|
@ -99,77 +48,134 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
||||||
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||||
instantSave="instantSaveApplicationAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
|
instantSave="instantSaveApplicationAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
@else
|
||||||
|
<form wire:submit='submitApplication'>
|
||||||
|
<div class="flex items-center gap-2 pb-4">
|
||||||
|
@if ($serviceApplication->human_name)
|
||||||
|
<h2>{{ Str::headline($serviceApplication->human_name) }}</h2>
|
||||||
|
@else
|
||||||
|
<h2>{{ Str::headline($serviceApplication->name) }}</h2>
|
||||||
|
@endif
|
||||||
|
<x-forms.button canGate="update" :canResource="$serviceApplication" type="submit">Save</x-forms.button>
|
||||||
|
@can('update', $serviceApplication)
|
||||||
|
<x-modal-confirmation wire:click="convertToDatabase" title="Convert to Database"
|
||||||
|
buttonTitle="Convert to Database" submitAction="convertToDatabase" :actions="['The selected resource will be converted to a service database.']"
|
||||||
|
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||||
|
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||||
|
shortConfirmationLabel="Service Application Name" />
|
||||||
|
@endcan
|
||||||
|
@can('delete', $serviceApplication)
|
||||||
|
<x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton
|
||||||
|
submitAction="deleteApplication" :actions="['The selected service application container will be stopped and permanently deleted.']"
|
||||||
|
confirmationText="{{ Str::headline($serviceApplication->name) }}"
|
||||||
|
confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below"
|
||||||
|
shortConfirmationLabel="Service Application Name" />
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
@if ($requiredPort && !$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||||
|
<x-callout type="info" title="Required Port: {{ $requiredPort }}" class="mb-2">
|
||||||
|
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly. All domains must include this port number (or any other port if you know what you're doing).
|
||||||
|
<br><br>
|
||||||
|
<strong>Example:</strong> https://app.coolify.io:{{ $requiredPort }},https://www.app.coolify.io:{{ $requiredPort }}
|
||||||
|
</x-callout>
|
||||||
|
@endif
|
||||||
|
|
||||||
<x-domain-conflict-modal
|
<div class="flex gap-2">
|
||||||
:conflicts="$domainConflicts"
|
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Name" id="humanName"
|
||||||
:showModal="$showDomainConflictModal"
|
placeholder="Human readable name"></x-forms.input>
|
||||||
confirmAction="confirmDomainUsage">
|
<x-forms.input canGate="update" :canResource="$serviceApplication" label="Description"
|
||||||
<x-slot:consequences>
|
id="description"></x-forms.input>
|
||||||
<ul class="mt-2 ml-4 list-disc">
|
</div>
|
||||||
<li>Only one service will be accessible at this domain</li>
|
<div class="flex gap-2">
|
||||||
<li>The routing behavior will be unpredictable</li>
|
@if (!$serviceApplication->serviceType()?->contains(str($serviceApplication->image)->before(':')))
|
||||||
<li>You may experience service disruptions</li>
|
@if ($serviceApplication->required_fqdn)
|
||||||
<li>SSL certificates might not work correctly</li>
|
<x-forms.input canGate="update" :canResource="$serviceApplication" required placeholder="https://app.coolify.io"
|
||||||
</ul>
|
label="Domains" id="fqdn"
|
||||||
</x-slot:consequences>
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||||
</x-domain-conflict-modal>
|
@else
|
||||||
|
<x-forms.input canGate="update" :canResource="$serviceApplication" placeholder="https://app.coolify.io"
|
||||||
|
label="Domains" id="fqdn"
|
||||||
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- https://app.coolify.io,https://cloud.coolify.io/dashboard<br>- https://app.coolify.io/api/v3<br>- https://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container.<br>- https://app.coolify.io:8080/api -> app.coolify.io/api will point to port 8080 inside the container."></x-forms.input>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
<x-forms.input canGate="update" :canResource="$serviceApplication"
|
||||||
|
helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
||||||
|
label="Image" id="image"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
@if ($showPortWarningModal)
|
<x-domain-conflict-modal
|
||||||
<div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
|
:conflicts="$domainConflicts"
|
||||||
@keydown.escape.window="modalOpen = false; $wire.call('cancelRemovePort')"
|
:showModal="$showDomainConflictModal"
|
||||||
:class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
confirmAction="confirmDomainUsage">
|
||||||
<template x-teleport="body">
|
<x-slot:consequences>
|
||||||
<div x-show="modalOpen"
|
<ul class="mt-2 ml-4 list-disc">
|
||||||
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
|
<li>Only one service will be accessible at this domain</li>
|
||||||
<div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
<li>The routing behavior will be unpredictable</li>
|
||||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
<li>You may experience service disruptions</li>
|
||||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
<li>SSL certificates might not work correctly</li>
|
||||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
</ul>
|
||||||
x-transition:leave="ease-in duration-100"
|
</x-slot:consequences>
|
||||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
</x-domain-conflict-modal>
|
||||||
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
|
||||||
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
|
||||||
<div class="flex justify-between items-center pb-3">
|
|
||||||
<h2 class="pr-8 font-bold">Remove Required Port?</h2>
|
|
||||||
<button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
|
||||||
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
|
||||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="relative w-auto">
|
|
||||||
<x-callout type="warning" title="Port Requirement Warning" class="mb-4">
|
|
||||||
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly.
|
|
||||||
One or more of your domains are missing a port number.
|
|
||||||
</x-callout>
|
|
||||||
|
|
||||||
<x-callout type="danger" title="What will happen if you continue?" class="mb-4">
|
@if ($showPortWarningModal)
|
||||||
<ul class="mt-2 ml-4 list-disc">
|
<div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
|
||||||
<li>The service may become unreachable</li>
|
@keydown.escape.window="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||||
<li>The proxy may not be able to route traffic correctly</li>
|
:class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
||||||
<li>Environment variables may not be generated properly</li>
|
<template x-teleport="body">
|
||||||
<li>The service may fail to start or function</li>
|
<div x-show="modalOpen"
|
||||||
</ul>
|
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
|
||||||
</x-callout>
|
<div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
|
||||||
|
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
|
||||||
|
<div class="flex justify-between items-center pb-3">
|
||||||
|
<h2 class="pr-8 font-bold">Remove Required Port?</h2>
|
||||||
|
<button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||||
|
class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
|
||||||
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="relative w-auto">
|
||||||
|
<x-callout type="warning" title="Port Requirement Warning" class="mb-4">
|
||||||
|
This service requires port <strong>{{ $requiredPort }}</strong> to function correctly.
|
||||||
|
One or more of your domains are missing a port number.
|
||||||
|
</x-callout>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
<x-callout type="danger" title="What will happen if you continue?" class="mb-4">
|
||||||
<x-forms.button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
<ul class="mt-2 ml-4 list-disc">
|
||||||
class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
<li>The service may become unreachable</li>
|
||||||
Cancel - Keep Port
|
<li>The proxy may not be able to route traffic correctly</li>
|
||||||
</x-forms.button>
|
<li>Environment variables may not be generated properly</li>
|
||||||
<x-forms.button wire:click="confirmRemovePort" @click="modalOpen = false" class="w-auto"
|
<li>The service may fail to start or function</li>
|
||||||
isError>
|
</ul>
|
||||||
I understand, remove port anyway
|
</x-callout>
|
||||||
</x-forms.button>
|
|
||||||
|
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||||
|
<x-forms.button @click="modalOpen = false; $wire.call('cancelRemovePort')"
|
||||||
|
class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||||
|
Cancel - Keep Port
|
||||||
|
</x-forms.button>
|
||||||
|
<x-forms.button wire:click="confirmRemovePort" @click="modalOpen = false" class="w-auto"
|
||||||
|
isError>
|
||||||
|
I understand, remove port anyway
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@elseif ($resourceType === 'database')
|
@elseif ($resourceType === 'database')
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
|
|
@ -178,6 +184,17 @@ class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@if ($currentRoute === 'project.service.database.import')
|
@if ($currentRoute === 'project.service.database.import')
|
||||||
<livewire:project.database.import :resource="$serviceDatabase" :key="'import-' . $serviceDatabase->uuid" />
|
<livewire:project.database.import :resource="$serviceDatabase" :key="'import-' . $serviceDatabase->uuid" />
|
||||||
|
@elseif ($currentRoute === 'project.service.index.advanced')
|
||||||
|
<h2>Advanced</h2>
|
||||||
|
<div class="w-full sm:w-96 flex flex-col gap-1 pt-4">
|
||||||
|
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
||||||
|
label="Exclude from service status"
|
||||||
|
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||||
|
id="excludeFromStatus"></x-forms.checkbox>
|
||||||
|
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase"
|
||||||
|
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
||||||
|
instantSave="instantSaveLogDrain" id="isLogDrainEnabled" label="Drain Logs" />
|
||||||
|
</div>
|
||||||
@else
|
@else
|
||||||
<form wire:submit='submitDatabase'>
|
<form wire:submit='submitDatabase'>
|
||||||
<div class="flex items-center gap-2 pb-4">
|
<div class="flex items-center gap-2 pb-4">
|
||||||
|
|
@ -242,16 +259,6 @@ class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pt-2">Advanced</h3>
|
|
||||||
<div class="w-96">
|
|
||||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase" instantSave="instantSaveExclude"
|
|
||||||
label="Exclude from service status"
|
|
||||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
|
||||||
id="excludeFromStatus"></x-forms.checkbox>
|
|
||||||
<x-forms.checkbox canGate="update" :canResource="$serviceDatabase"
|
|
||||||
helper="Drain logs to your configured log drain endpoint in your Server settings."
|
|
||||||
instantSave="instantSaveLogDrain" id="isLogDrainEnabled" label="Drain Logs" />
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
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>
|
||||||
|
<h3>Network</h3>
|
||||||
|
</div>
|
||||||
|
<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 +49,4 @@ class="font-bold">{{ data_get($field, 'serviceName') }}</span>{{ data_get($field
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($is_shared)
|
@if ($is_shared)
|
||||||
|
<x-forms.checkbox instantSave id="is_buildtime"
|
||||||
|
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||||
|
label="Available at Buildtime" />
|
||||||
|
<x-forms.checkbox instantSave id="is_runtime"
|
||||||
|
helper="Make this variable available in the running container at runtime."
|
||||||
|
label="Available at Runtime" />
|
||||||
<x-forms.checkbox instantSave id="is_literal"
|
<x-forms.checkbox instantSave id="is_literal"
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
label="Is Literal?" />
|
label="Is Literal?" />
|
||||||
|
|
@ -89,6 +95,12 @@
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($is_shared)
|
@if ($is_shared)
|
||||||
|
<x-forms.checkbox disabled id="is_buildtime"
|
||||||
|
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||||
|
label="Available at Buildtime" />
|
||||||
|
<x-forms.checkbox disabled id="is_runtime"
|
||||||
|
helper="Make this variable available in the running container at runtime."
|
||||||
|
label="Available at Runtime" />
|
||||||
<x-forms.checkbox disabled id="is_literal"
|
<x-forms.checkbox disabled id="is_literal"
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
label="Is Literal?" />
|
label="Is Literal?" />
|
||||||
|
|
@ -209,6 +221,12 @@
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($is_shared)
|
@if ($is_shared)
|
||||||
|
<x-forms.checkbox instantSave id="is_buildtime"
|
||||||
|
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||||
|
label="Available at Buildtime" />
|
||||||
|
<x-forms.checkbox instantSave id="is_runtime"
|
||||||
|
helper="Make this variable available in the running container at runtime."
|
||||||
|
label="Available at Runtime" />
|
||||||
<x-forms.checkbox instantSave id="is_literal"
|
<x-forms.checkbox instantSave id="is_literal"
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
label="Is Literal?" />
|
label="Is Literal?" />
|
||||||
|
|
@ -283,6 +301,12 @@
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
@if ($is_shared)
|
@if ($is_shared)
|
||||||
|
<x-forms.checkbox disabled id="is_buildtime"
|
||||||
|
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
|
||||||
|
label="Available at Buildtime" />
|
||||||
|
<x-forms.checkbox disabled id="is_runtime"
|
||||||
|
helper="Make this variable available in the running container at runtime."
|
||||||
|
label="Available at Runtime" />
|
||||||
<x-forms.checkbox disabled id="is_literal"
|
<x-forms.checkbox disabled id="is_literal"
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
label="Is Literal?" />
|
label="Is Literal?" />
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,20 @@
|
||||||
label="CPU Weight" id="limitsCpuShares" />
|
label="CPU Weight" id="limitsCpuShares" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pt-4">Limit Memory</h3>
|
<h3 class="pt-4">Limit Memory</h3>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="flex gap-2">
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
<x-forms.input canGate="update" :canResource="$resource"
|
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>here</a>."
|
||||||
helper="Examples: 69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>here</a>."
|
label="Soft Memory Limit" id="limitsMemoryReservation" />
|
||||||
label="Soft Memory Limit" id="limitsMemoryReservation" />
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
<x-forms.input canGate="update" :canResource="$resource"
|
helper="Value between 0-100.<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_swappiness'>here</a>."
|
||||||
helper="0-100.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_swappiness'>here</a>."
|
type="number" min="0" max="100" label="Swappiness"
|
||||||
type="number" min="0" max="100" label="Swappiness"
|
id="limitsMemorySwappiness" />
|
||||||
id="limitsMemorySwappiness" />
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
</div>
|
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>here</a>."
|
||||||
<div class="flex gap-2">
|
label="Maximum Memory Limit" id="limitsMemory" />
|
||||||
<x-forms.input canGate="update" :canResource="$resource"
|
<x-forms.input canGate="update" :canResource="$resource"
|
||||||
helper="Examples: 69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>here</a>."
|
helper="<span class='text-helper'>Examples</span><br>• 69b (byte)<br>• 420k (kilobyte)<br>• 1337m (megabyte)<br>• 1g (gigabyte)<br><br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>here</a>."
|
||||||
label="Maximum Memory Limit" id="limitsMemory" />
|
label="Maximum Swap Limit" id="limitsMemorySwap" />
|
||||||
<x-forms.input canGate="update" :canResource="$resource"
|
|
||||||
helper="Examples:69b (byte) or 420k (kilobyte) or 1337m (megabyte) or 1g (gigabyte).<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>here</a>."
|
|
||||||
label="Maximum Swap Limit" id="limitsMemorySwap" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@
|
||||||
}">
|
}">
|
||||||
<h3 class="pt-4">Clone Resource</h3>
|
<h3 class="pt-4">Clone Resource</h3>
|
||||||
<div class="pb-2">Duplicate this resource to another server or network destination.</div>
|
<div class="pb-2">Duplicate this resource to another server or network destination.</div>
|
||||||
|
<x-callout type="info" title="Important" class="mb-4">
|
||||||
|
Cloning only duplicates resource configuration (such as environment variables, build settings etc..). It does not include any resource data, such as databases or stored files.
|
||||||
|
</x-callout>
|
||||||
|
|
||||||
@can('update', $resource)
|
@can('update', $resource)
|
||||||
<div class="space-y-4 pb-8">
|
<div class="space-y-4 pb-8">
|
||||||
|
|
|
||||||
|
|
@ -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 @@ class="w-full input opacity-50 cursor-not-allowed"
|
||||||
|
|
||||||
<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 @@ class=""
|
||||||
<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 "
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@
|
||||||
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.service.command')->middleware('can.access.terminal');
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('project.service.command')->middleware('can.access.terminal');
|
||||||
Route::get('/{stack_service_uuid}/backups', ServiceDatabaseBackups::class)->name('project.service.database.backups');
|
Route::get('/{stack_service_uuid}/backups', ServiceDatabaseBackups::class)->name('project.service.database.backups');
|
||||||
Route::get('/{stack_service_uuid}/import', ServiceIndex::class)->name('project.service.database.import')->middleware('can.update.resource');
|
Route::get('/{stack_service_uuid}/import', ServiceIndex::class)->name('project.service.database.import')->middleware('can.update.resource');
|
||||||
|
Route::get('/{stack_service_uuid}/advanced', ServiceIndex::class)->name('project.service.index.advanced');
|
||||||
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
Route::get('/{stack_service_uuid}', ServiceIndex::class)->name('project.service.index');
|
||||||
Route::get('/tasks/{task_uuid}', ServiceConfiguration::class)->name('project.service.scheduled-tasks');
|
Route::get('/tasks/{task_uuid}', ServiceConfiguration::class)->name('project.service.scheduled-tasks');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -539,6 +539,15 @@ install_docker_manually() {
|
||||||
echo "Docker installed successfully."
|
echo "Docker installed successfully."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_docker_from_rhel_repo() {
|
||||||
|
echo " - Installing Docker from the RHEL repository for Rocky Linux..."
|
||||||
|
rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo
|
||||||
|
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
systemctl --now enable docker
|
||||||
|
}
|
||||||
|
|
||||||
log_section "Step 3/9: Checking Docker installation"
|
log_section "Step 3/9: Checking Docker installation"
|
||||||
echo "3/9 Checking Docker installation..."
|
echo "3/9 Checking Docker installation..."
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
|
@ -579,6 +588,13 @@ if ! [ -x "$(command -v docker)" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
"rocky")
|
||||||
|
install_docker_from_rhel_repo
|
||||||
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
"almalinux" | "tencentos")
|
"almalinux" | "tencentos")
|
||||||
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
# tags: calcom,calendso,scheduling,open,source
|
# tags: calcom,calendso,scheduling,open,source
|
||||||
# logo: svgs/calcom.svg
|
# logo: svgs/calcom.svg
|
||||||
# port: 3000
|
# port: 3000
|
||||||
|
# amd_only: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
calcom:
|
calcom:
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,8 @@
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"logo": "svgs/calcom.svg",
|
"logo": "svgs/calcom.svg",
|
||||||
"minversion": "0.0.0",
|
"minversion": "0.0.0",
|
||||||
"port": "3000"
|
"port": "3000",
|
||||||
|
"amd_only": true
|
||||||
},
|
},
|
||||||
"calibre-web-automated-book-downloader": {
|
"calibre-web-automated-book-downloader": {
|
||||||
"documentation": "https://github.com/calibrain/calibre-web-automated-book-downloader?utm_source=coolify.io",
|
"documentation": "https://github.com/calibrain/calibre-web-automated-book-downloader?utm_source=coolify.io",
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,8 @@
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"logo": "svgs/calcom.svg",
|
"logo": "svgs/calcom.svg",
|
||||||
"minversion": "0.0.0",
|
"minversion": "0.0.0",
|
||||||
"port": "3000"
|
"port": "3000",
|
||||||
|
"amd_only": true
|
||||||
},
|
},
|
||||||
"calibre-web-automated-book-downloader": {
|
"calibre-web-automated-book-downloader": {
|
||||||
"documentation": "https://github.com/calibrain/calibre-web-automated-book-downloader?utm_source=coolify.io",
|
"documentation": "https://github.com/calibrain/calibre-web-automated-book-downloader?utm_source=coolify.io",
|
||||||
|
|
|
||||||
71
tests/Feature/BreadcrumbsVisibilityTest.php
Normal file
71
tests/Feature/BreadcrumbsVisibilityTest.php
Normal file
|
|
@ -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');
|
||||||
|
});
|
||||||
|
|
@ -60,3 +60,47 @@
|
||||||
'port' => '766',
|
'port' => '766',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForSourceAndSshUrlSchemeWithCustomPort', function () {
|
||||||
|
$result = convertGitUrl('ssh://git@192.168.56.11:22222/User/Repo.git', 'source', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'ssh://git@192.168.56.11:22222/User/Repo.git',
|
||||||
|
'port' => '22222',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForSourceAndSshUrlSchemeWithCustomPortAndIpv6Host', function () {
|
||||||
|
$result = convertGitUrl('ssh://git@[2001:db8::10]:22222/group/project.git', 'source', null);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'ssh://git@[2001:db8::10]:22222/group/project.git',
|
||||||
|
'port' => '22222',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndGithubAppWithCustomPort', function () {
|
||||||
|
$githubApp = new GithubApp([
|
||||||
|
'html_url' => 'https://github.example.com',
|
||||||
|
'custom_user' => 'git',
|
||||||
|
'custom_port' => 22222,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = convertGitUrl('andrasbacsai/coolify-examples.git', 'deploy_key', $githubApp);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'ssh://git@github.example.com:22222/andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => '22222',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convertGitUrlsForDeployKeyAndGithubAppWithCustomPortAndIpv6Host', function () {
|
||||||
|
$githubApp = new GithubApp([
|
||||||
|
'html_url' => 'https://[2001:db8::10]',
|
||||||
|
'custom_user' => 'git',
|
||||||
|
'custom_port' => 22222,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = convertGitUrl('andrasbacsai/coolify-examples.git', 'deploy_key', $githubApp);
|
||||||
|
expect($result)->toBe([
|
||||||
|
'repository' => 'ssh://git@[2001:db8::10]:22222/andrasbacsai/coolify-examples.git',
|
||||||
|
'port' => '22222',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
||||||
79
tests/Feature/DeploymentLogsLayoutTest.php
Normal file
79
tests/Feature/DeploymentLogsLayoutTest.php
Normal file
|
|
@ -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]');
|
||||||
|
});
|
||||||
113
tests/Feature/ResponsiveCheckboxLayoutTest.php
Normal file
113
tests/Feature/ResponsiveCheckboxLayoutTest.php
Normal file
|
|
@ -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');
|
||||||
|
});
|
||||||
100
tests/Feature/UpgradeComponentTest.php
Normal file
100
tests/Feature/UpgradeComponentTest.php
Normal file
|
|
@ -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();
|
||||||
|
});
|
||||||
218
tests/Feature/UserDeletionWithGithubAppTest.php
Normal file
218
tests/Feature/UserDeletionWithGithubAppTest.php
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
@ -99,3 +99,23 @@
|
||||||
// The malicious payload should be escaped (escapeshellarg wraps and escapes quotes)
|
// The malicious payload should be escaped (escapeshellarg wraps and escapes quotes)
|
||||||
expect($command)->toContain("'https://github.com/user/repo.git'\\''");
|
expect($command)->toContain("'https://github.com/user/repo.git'\\''");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves ssh scheme URLs with custom ports in deploy_key commands', function () {
|
||||||
|
$deploymentUuid = 'test-deployment-uuid';
|
||||||
|
|
||||||
|
$application = new Application;
|
||||||
|
$application->git_branch = 'master';
|
||||||
|
$application->git_repository = 'ssh://git@192.168.56.11:22222/User/Repo.git';
|
||||||
|
$application->private_key_id = 1;
|
||||||
|
|
||||||
|
$privateKey = new PrivateKey;
|
||||||
|
$privateKey->private_key = 'fake-private-key';
|
||||||
|
$application->setRelation('private_key', $privateKey);
|
||||||
|
|
||||||
|
$result = $application->generateGitLsRemoteCommands($deploymentUuid, false);
|
||||||
|
|
||||||
|
expect($result['commands'])
|
||||||
|
->toContain("'ssh://git@192.168.56.11:22222/User/Repo.git'")
|
||||||
|
->toContain('-p 22222')
|
||||||
|
->not->toContain('ssh:/git@192.168.56.11:22222/User/Repo.git');
|
||||||
|
});
|
||||||
|
|
|
||||||
28
tests/Unit/InstallScriptRockyDockerRepoTest.php
Normal file
28
tests/Unit/InstallScriptRockyDockerRepoTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function expectRockyInstallScriptToUseRhelRepo(string $path): void
|
||||||
|
{
|
||||||
|
$installScript = file_get_contents(base_path($path));
|
||||||
|
|
||||||
|
expect($installScript)
|
||||||
|
->toContain('install_docker_from_rhel_repo() {')
|
||||||
|
->toContain('echo " - Installing Docker from the RHEL repository for Rocky Linux..."')
|
||||||
|
->toContain('rm -f /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce-staging.repo')
|
||||||
|
->toContain('dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo')
|
||||||
|
->toContain('dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin')
|
||||||
|
->toContain('systemctl --now enable docker')
|
||||||
|
->toContain('"rocky")')
|
||||||
|
->toContain('install_docker_from_rhel_repo')
|
||||||
|
->not->toContain('dnf -y -q --setopt=install_weak_deps=False install dnf-plugins-core')
|
||||||
|
->not->toContain('dnf5 config-manager addrepo --overwrite --save-filename=docker-ce.repo --from-repofile=https://download.docker.com/linux/rhel/docker-ce.repo')
|
||||||
|
->not->toContain('dnf makecache')
|
||||||
|
->not->toContain('"ubuntu" | "debian" | "raspbian" | "centos" | "fedora" | "rhel" | "rocky" | "sles")');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('uses the rocky linux documented docker install flow in the stable install script', function () {
|
||||||
|
expectRockyInstallScriptToUseRhelRepo('scripts/install.sh');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the rocky linux documented docker install flow in the nightly install script', function () {
|
||||||
|
expectRockyInstallScriptToUseRhelRepo('other/nightly/install.sh');
|
||||||
|
});
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.472"
|
"version": "4.0.0-beta.474"
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue