commit
						ea64e9d5ad
					
				
							
								
								
									
										22
									
								
								.tinkerwell/snippets/DeleteUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.tinkerwell/snippets/DeleteUser.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use App\Models\User; | ||||||
|  | 
 | ||||||
|  | $email = 'test@example.com'; | ||||||
|  | $user = User::whereEmail($email)->first(); | ||||||
|  | $teams = $user->teams; | ||||||
|  | foreach ($teams as $team) { | ||||||
|  |     $servers = $team->servers; | ||||||
|  |     if ($servers->count() > 0) { | ||||||
|  |         foreach ($servers as $server) { | ||||||
|  |             dump($server); | ||||||
|  |             $server->delete(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     dump($team); | ||||||
|  |     $team->delete(); | ||||||
|  | } | ||||||
|  | if ($user) { | ||||||
|  |     dump($user); | ||||||
|  |     $user->delete(); | ||||||
|  | } | ||||||
| @ -7,17 +7,34 @@ use Livewire\Component; | |||||||
| class StackForm extends Component | class StackForm extends Component | ||||||
| { | { | ||||||
|     public $service; |     public $service; | ||||||
|     public $isConfigurationRequired = false; |     public $fields = []; | ||||||
|     protected $listeners = ["saveCompose"]; |     protected $listeners = ["saveCompose"]; | ||||||
|     protected $rules = [ |     public $rules = [ | ||||||
|         'service.docker_compose_raw' => 'required', |         'service.docker_compose_raw' => 'required', | ||||||
|         'service.docker_compose' => 'required', |         'service.docker_compose' => 'required', | ||||||
|         'service.name' => 'required', |         'service.name' => 'required', | ||||||
|         'service.description' => 'nullable', |         'service.description' => 'nullable', | ||||||
|     ]; |     ]; | ||||||
|     public function mount () { |     public $validationAttributes = []; | ||||||
|         if ($this->service->applications->filter(fn($app) => str($app->image)->contains('minio/minio'))->count() > 0) { |     public function mount() | ||||||
|             $this->isConfigurationRequired = true; |     { | ||||||
|  |         $extraFields = $this->service->extraFields(); | ||||||
|  |         foreach ($extraFields as $serviceName => $fields) { | ||||||
|  |             foreach ($fields as $fieldKey => $field) { | ||||||
|  |                 $key = data_get($field, 'key'); | ||||||
|  |                 $value = data_get($field, 'value'); | ||||||
|  |                 $rules = data_get($field, 'rules'); | ||||||
|  |                 $isPassword = data_get($field, 'isPassword'); | ||||||
|  |                 $this->fields[$key] = [ | ||||||
|  |                     "serviceName" => $serviceName, | ||||||
|  |                     "key" => $key, | ||||||
|  |                     "name" => $fieldKey, | ||||||
|  |                     "value" => $value, | ||||||
|  |                     "isPassword" => $isPassword, | ||||||
|  |                 ]; | ||||||
|  |                 $this->rules["fields.$key.value"] = $rules; | ||||||
|  |                 $this->validationAttributes["fields.$key.value"] = $fieldKey; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     public function saveCompose($raw) |     public function saveCompose($raw) | ||||||
| @ -32,6 +49,7 @@ class StackForm extends Component | |||||||
|         try { |         try { | ||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $this->service->save(); |             $this->service->save(); | ||||||
|  |             $this->service->saveExtraFields($this->fields); | ||||||
|             $this->service->parse(); |             $this->service->parse(); | ||||||
|             $this->service->refresh(); |             $this->service->refresh(); | ||||||
|             $this->service->saveComposeConfigs(); |             $this->service->saveComposeConfigs(); | ||||||
|  | |||||||
| @ -934,7 +934,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         } |         } | ||||||
|         return implode(' ', $generated_healthchecks_commands); |         return implode(' ', $generated_healthchecks_commands); | ||||||
|     } |     } | ||||||
|  |     private function pull_latest_image($image) | ||||||
|  |     { | ||||||
|  |         $this->execute_remote_command( | ||||||
|  |             ["echo -n 'Pulling latest image ($image) from the registry.'"], | ||||||
| 
 | 
 | ||||||
|  |             [ | ||||||
|  |                 executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true | ||||||
|  |             ] | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|     private function build_image() |     private function build_image() | ||||||
|     { |     { | ||||||
|         if ($this->application->build_pack === 'static') { |         if ($this->application->build_pack === 'static') { | ||||||
| @ -948,6 +957,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ($this->application->settings->is_static || $this->application->build_pack === 'static') { |         if ($this->application->settings->is_static || $this->application->build_pack === 'static') { | ||||||
|  |             if ($this->application->static_image) { | ||||||
|  |                 $this->pull_latest_image($this->application->static_image); | ||||||
|  |             } | ||||||
|             if ($this->application->build_pack === 'static') { |             if ($this->application->build_pack === 'static') { | ||||||
|                 $dockerfile = base64_encode("FROM {$this->application->static_image} |                 $dockerfile = base64_encode("FROM {$this->application->static_image} | ||||||
| WORKDIR /usr/share/nginx/html/ | WORKDIR /usr/share/nginx/html/ | ||||||
| @ -1012,8 +1024,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
|                 ] |                 ] | ||||||
|             ); |             ); | ||||||
|         } else { |         } else { | ||||||
|  |             // Pure Dockerfile based deployment
 | ||||||
|             $this->execute_remote_command([ |             $this->execute_remote_command([ | ||||||
|                 executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true |                 executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -1049,6 +1062,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
| 
 | 
 | ||||||
|     private function start_by_compose_file() |     private function start_by_compose_file() | ||||||
|     { |     { | ||||||
|  |         if ( | ||||||
|  |             !$this->application->dockerfile && | ||||||
|  |             ( | ||||||
|  |                 $this->application->build_pack === 'dockerimage' || | ||||||
|  |                 $this->application->build_pack === 'dockerfile') | ||||||
|  |         ) { | ||||||
|  |             $this->execute_remote_command( | ||||||
|  |                 ["echo -n 'Pulling latest images from the registry.'"], | ||||||
|  |                 [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|         $this->execute_remote_command( |         $this->execute_remote_command( | ||||||
|             ["echo -n 'Starting application (could take a while).'"], |             ["echo -n 'Starting application (could take a while).'"], | ||||||
|             [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], |             [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], | ||||||
|  | |||||||
| @ -45,7 +45,168 @@ class Service extends BaseModel | |||||||
|     { |     { | ||||||
|         return 'service'; |         return 'service'; | ||||||
|     } |     } | ||||||
|  |     public function extraFields() | ||||||
|  |     { | ||||||
|  |         $fields = collect([]); | ||||||
|  |         $applications = $this->applications()->get(); | ||||||
|  |         foreach ($applications as $application) { | ||||||
|  |             $image = str($application->image)->before(':')->value(); | ||||||
|  |             switch ($image) { | ||||||
|  |                 case str($image)->contains('minio'): | ||||||
|  |                     $console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); | ||||||
|  |                     $s3_api_url = $this->environment_variables()->where('key', 'MINIO_SERVER_URL')->first(); | ||||||
|  |                     $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_MINIO')->first(); | ||||||
|  |                     $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_MINIO')->first(); | ||||||
|  |                     $fields->put('MinIO', [ | ||||||
|  |                         'Console URL' => [ | ||||||
|  |                             'key' => data_get($console_url, 'key'), | ||||||
|  |                             'value' => data_get($console_url, 'value'), | ||||||
|  |                             'rules' => 'required|url', | ||||||
|  |                         ], | ||||||
|  |                         'S3 API URL' => [ | ||||||
|  |                             'key' => data_get($s3_api_url, 'key'), | ||||||
|  |                             'value' => data_get($s3_api_url, 'value'), | ||||||
|  |                             'rules' => 'required|url', | ||||||
|  |                         ], | ||||||
|  |                         'Admin User' => [ | ||||||
|  |                             'key' => data_get($admin_user, 'key'), | ||||||
|  |                             'value' => data_get($admin_user, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                         'Admin Password' => [ | ||||||
|  |                             'key' => data_get($admin_password, 'key'), | ||||||
|  |                             'value' => data_get($admin_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                     ]); | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         $databases = $this->databases()->get(); | ||||||
| 
 | 
 | ||||||
|  |         foreach ($databases as $database) { | ||||||
|  |             $image = str($database->image)->before(':')->value(); | ||||||
|  |             switch ($image) { | ||||||
|  |                 case str($image)->contains('postgres'): | ||||||
|  |                     $userVariables = ['SERVICE_USER_POSTGRES', 'SERVICE_USER_POSTGRESQL']; | ||||||
|  |                     $passwordVariables = ['SERVICE_PASSWORD_POSTGRES', 'SERVICE_PASSWORD_POSTGRESQL']; | ||||||
|  |                     $dbNameVariables = ['POSTGRESQL_DATABASE', 'POSTGRES_DB']; | ||||||
|  |                     $postgres_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); | ||||||
|  |                     $postgres_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); | ||||||
|  |                     $postgres_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first(); | ||||||
|  |                     $fields->put('PostgreSQL', [ | ||||||
|  |                         'User' => [ | ||||||
|  |                             'key' => data_get($postgres_user, 'key'), | ||||||
|  |                             'value' => data_get($postgres_user, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                         'Password' => [ | ||||||
|  |                             'key' => data_get($postgres_password, 'key'), | ||||||
|  |                             'value' => data_get($postgres_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                         'Database Name' => [ | ||||||
|  |                             'key' => data_get($postgres_db_name, 'key'), | ||||||
|  |                             'value' => data_get($postgres_db_name, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                     ]); | ||||||
|  |                     break; | ||||||
|  |                 case str($image)->contains('mysql'): | ||||||
|  |                     $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS']; | ||||||
|  |                     $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS']; | ||||||
|  |                     $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT']; | ||||||
|  |                     $dbNameVariables = ['MYSQL_DATABASE']; | ||||||
|  |                     $mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); | ||||||
|  |                     $mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); | ||||||
|  |                     $mysql_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); | ||||||
|  |                     $mysql_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first(); | ||||||
|  |                     $fields->put('MySQL', [ | ||||||
|  |                         'User' => [ | ||||||
|  |                             'key' => data_get($mysql_user, 'key'), | ||||||
|  |                             'value' => data_get($mysql_user, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                         'Password' => [ | ||||||
|  |                             'key' => data_get($mysql_password, 'key'), | ||||||
|  |                             'value' => data_get($mysql_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                         'Root Password' => [ | ||||||
|  |                             'key' => data_get($mysql_root_password, 'key'), | ||||||
|  |                             'value' => data_get($mysql_root_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                         'Database Name' => [ | ||||||
|  |                             'key' => data_get($mysql_db_name, 'key'), | ||||||
|  |                             'value' => data_get($mysql_db_name, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                     ]); | ||||||
|  |                     break; | ||||||
|  |                 case str($image)->contains('mariadb'): | ||||||
|  |                     $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER']; | ||||||
|  |                     $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS']; | ||||||
|  |                     $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS']; | ||||||
|  |                     $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA']; | ||||||
|  |                     $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); | ||||||
|  |                     $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); | ||||||
|  |                     $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); | ||||||
|  |                     $mariadb_db_name = $this->environment_variables()->whereIn('key', $dbNameVariables)->first(); | ||||||
|  |                     $fields->put('MariaDB', [ | ||||||
|  |                         'User' => [ | ||||||
|  |                             'key' => data_get($mariadb_user, 'key'), | ||||||
|  |                             'value' => data_get($mariadb_user, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                         ], | ||||||
|  |                         'Password' => [ | ||||||
|  |                             'key' => data_get($mariadb_password, 'key'), | ||||||
|  |                             'value' => data_get($mariadb_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                         'Root Password' => [ | ||||||
|  |                             'key' => data_get($mariadb_root_password, 'key'), | ||||||
|  |                             'value' => data_get($mariadb_root_password, 'value'), | ||||||
|  |                             'rules' => 'required', | ||||||
|  |                             'isPassword' => true, | ||||||
|  |                         ], | ||||||
|  |                         'Database Name' => [ | ||||||
|  |                             'key' => data_get($mariadb_db_name, 'key'), | ||||||
|  |                             'value' => data_get($mariadb_db_name, 'value'), | ||||||
|  |                             'rules' => data_get($mariadb_db_name, 'value') && 'required', | ||||||
|  |                         ], | ||||||
|  |                     ]); | ||||||
|  | 
 | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return $fields; | ||||||
|  |     } | ||||||
|  |     public function saveExtraFields($fields) | ||||||
|  |     { | ||||||
|  |         foreach ($fields as $field) { | ||||||
|  |             $key = data_get($field, 'key'); | ||||||
|  |             $value = data_get($field, 'value'); | ||||||
|  |             $found = $this->environment_variables()->where('key', $key)->first(); | ||||||
|  |             if ($found) { | ||||||
|  |                 $found->value = $value; | ||||||
|  |                 $found->save(); | ||||||
|  |             } else { | ||||||
|  |                 $this->environment_variables()->create([ | ||||||
|  |                     'key' => $key, | ||||||
|  |                     'value' => $value, | ||||||
|  |                     'is_build_time' => false, | ||||||
|  |                     'service_id' => $this->id, | ||||||
|  |                     'is_preview' => false, | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     public function documentation() |     public function documentation() | ||||||
|     { |     { | ||||||
|         $services = getServiceTemplates(); |         $services = getServiceTemplates(); | ||||||
| @ -564,14 +725,20 @@ class Service extends BaseModel | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Add labels to the service
 |                 // Add labels to the service
 | ||||||
|  |                 if (!$isDatabase) { | ||||||
|  |                     if ($savedService->serviceType()) { | ||||||
|  |                         $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); | ||||||
|  |                     } else { | ||||||
|                         $fqdns = collect(data_get($savedService, 'fqdns')); |                         $fqdns = collect(data_get($savedService, 'fqdns')); | ||||||
|  |                     } | ||||||
|                     $defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); |                     $defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); | ||||||
|                     $serviceLabels = $serviceLabels->merge($defaultLabels); |                     $serviceLabels = $serviceLabels->merge($defaultLabels); | ||||||
|                 if (!$isDatabase && $fqdns->count() > 0) { |                     if ($fqdns->count() > 0) { | ||||||
|                         if ($fqdns) { |                         if ($fqdns) { | ||||||
|                             $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true)); |                             $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true)); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|                 data_set($service, 'labels', $serviceLabels->toArray()); |                 data_set($service, 'labels', $serviceLabels->toArray()); | ||||||
|                 data_forget($service, 'is_database'); |                 data_forget($service, 'is_database'); | ||||||
|                 data_set($service, 'restart', RESTART_MODE); |                 data_set($service, 'restart', RESTART_MODE); | ||||||
|  | |||||||
| @ -22,6 +22,16 @@ class ServiceApplication extends BaseModel | |||||||
|     { |     { | ||||||
|         return 'service'; |         return 'service'; | ||||||
|     } |     } | ||||||
|  |     public function serviceType() | ||||||
|  |     { | ||||||
|  |         $found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) { | ||||||
|  |             return str($this->image)->before(':')->value() === $service; | ||||||
|  |         })->first()); | ||||||
|  |         if ($found->isNotEmpty()) { | ||||||
|  |             return $found; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|     public function service() |     public function service() | ||||||
|     { |     { | ||||||
|         return $this->belongsTo(Service::class); |         return $this->belongsTo(Service::class); | ||||||
|  | |||||||
| @ -29,7 +29,6 @@ class ServiceDatabase extends BaseModel | |||||||
|         return "standalone-$image"; |         return "standalone-$image"; | ||||||
|     } |     } | ||||||
|     public function getServiceDatabaseUrl() { |     public function getServiceDatabaseUrl() { | ||||||
|         // $type = $this->databaseType();
 |  | ||||||
|         $port = $this->public_port; |         $port = $this->public_port; | ||||||
|         $realIp = $this->service->server->ip; |         $realIp = $this->service->server->ip; | ||||||
|         if ($realIp === 'host.docker.internal' || isDev()) { |         if ($realIp === 'host.docker.internal' || isDev()) { | ||||||
|  | |||||||
| @ -16,6 +16,11 @@ class Links extends Component | |||||||
|     { |     { | ||||||
|         $this->links = collect([]); |         $this->links = collect([]); | ||||||
|         $service->applications()->get()->map(function ($application) { |         $service->applications()->get()->map(function ($application) { | ||||||
|  |             $type = $application->serviceType(); | ||||||
|  |             if ($type) { | ||||||
|  |                 $links = generateServiceSpecificFqdns($application, false); | ||||||
|  |                 $this->links = $this->links->merge($links); | ||||||
|  |             } else { | ||||||
|                 if ($application->fqdn) { |                 if ($application->fqdn) { | ||||||
|                     $fqdns = collect(Str::of($application->fqdn)->explode(',')); |                     $fqdns = collect(Str::of($application->fqdn)->explode(',')); | ||||||
|                     $fqdns->map(function ($fqdn) { |                     $fqdns->map(function ($fqdn) { | ||||||
| @ -33,6 +38,7 @@ class Links extends Component | |||||||
|                         $this->links->push(base_url(withPort: false) . ":{$hostPort}"); |                         $this->links->push(base_url(withPort: false) . ":{$hostPort}"); | ||||||
|                     }); |                     }); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -23,3 +23,6 @@ const DATABASE_DOCKER_IMAGES = [ | |||||||
|     'influxdb', |     'influxdb', | ||||||
|     'clickhouse/clickhouse-server' |     'clickhouse/clickhouse-server' | ||||||
| ]; | ]; | ||||||
|  | const SPECIFIC_SERVICES = [ | ||||||
|  |     'quay.io/minio/minio', | ||||||
|  | ]; | ||||||
|  | |||||||
| @ -144,6 +144,39 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica | |||||||
|     } |     } | ||||||
|     return $labels; |     return $labels; | ||||||
| } | } | ||||||
|  | function generateServiceSpecificFqdns($service, $forTraefik = false) | ||||||
|  | { | ||||||
|  |     $variables = collect($service->service->environment_variables); | ||||||
|  |     $type = $service->serviceType(); | ||||||
|  |     $payload = collect([]); | ||||||
|  |     switch ($type) { | ||||||
|  |         case $type->contains('minio'): | ||||||
|  |             $MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); | ||||||
|  |             if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) { | ||||||
|  |                 $MINIO_BROWSER_REDIRECT_URL->update([ | ||||||
|  |                     "value" => generateFqdn($service->service->server, 'console-' . $service->uuid) | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |             $MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first(); | ||||||
|  |             if (is_null($MINIO_SERVER_URL?->value)) { | ||||||
|  |                 $MINIO_SERVER_URL->update([ | ||||||
|  |                     "value" => generateFqdn($service->service->server, 'minio-' . $service->uuid) | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |             if ($forTraefik) { | ||||||
|  |                 $payload = collect([ | ||||||
|  |                     $MINIO_BROWSER_REDIRECT_URL->value . ':9001', | ||||||
|  |                     $MINIO_SERVER_URL->value . ':9000', | ||||||
|  |                 ]); | ||||||
|  |             } else { | ||||||
|  |                 $payload = collect([ | ||||||
|  |                     $MINIO_BROWSER_REDIRECT_URL->value, | ||||||
|  |                     $MINIO_SERVER_URL->value, | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  |     return $payload; | ||||||
|  | } | ||||||
| function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null) | function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null) | ||||||
| { | { | ||||||
|     $labels = collect([]); |     $labels = collect([]); | ||||||
|  | |||||||
| @ -126,7 +126,6 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS | |||||||
| function updateCompose($resource) | function updateCompose($resource) | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|         ray($resource); |  | ||||||
|         $name = data_get($resource, 'name'); |         $name = data_get($resource, 'name'); | ||||||
|         $dockerComposeRaw = data_get($resource, 'service.docker_compose_raw'); |         $dockerComposeRaw = data_get($resource, 'service.docker_compose_raw'); | ||||||
|         $dockerCompose = Yaml::parse($dockerComposeRaw); |         $dockerCompose = Yaml::parse($dockerComposeRaw); | ||||||
|  | |||||||
| @ -27,7 +27,6 @@ use Illuminate\Support\Facades\Mail; | |||||||
| use Illuminate\Support\Facades\Route; | use Illuminate\Support\Facades\Route; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Illuminate\Support\Stringable; | use Illuminate\Support\Stringable; | ||||||
| use Nubs\RandomNameGenerator\All; |  | ||||||
| use Poliander\Cron\CronExpression; | use Poliander\Cron\CronExpression; | ||||||
| use Visus\Cuid2\Cuid2; | use Visus\Cuid2\Cuid2; | ||||||
| use phpseclib3\Crypt\RSA; | use phpseclib3\Crypt\RSA; | ||||||
| @ -173,7 +172,11 @@ function get_latest_version_of_coolify(): string | |||||||
| 
 | 
 | ||||||
| function generate_random_name(?string $cuid = null): string | function generate_random_name(?string $cuid = null): string | ||||||
| { | { | ||||||
|     $generator = All::create(); |     $generator = new \Nubs\RandomNameGenerator\All( | ||||||
|  |         [ | ||||||
|  |             new \Nubs\RandomNameGenerator\Alliteration(), | ||||||
|  |         ] | ||||||
|  |     ); | ||||||
|     if (is_null($cuid)) { |     if (is_null($cuid)) { | ||||||
|         $cuid = new Cuid2(7); |         $cuid = new Cuid2(7); | ||||||
|     } |     } | ||||||
| @ -444,20 +447,25 @@ function getServiceTemplates() | |||||||
|     if (isDev()) { |     if (isDev()) { | ||||||
|         $services = File::get(base_path('templates/service-templates.json')); |         $services = File::get(base_path('templates/service-templates.json')); | ||||||
|         $services = collect(json_decode($services))->sortKeys(); |         $services = collect(json_decode($services))->sortKeys(); | ||||||
|         $version = config('version'); |  | ||||||
|         $services = $services->map(function ($service) use ($version) { |  | ||||||
|             if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) { |  | ||||||
|                 $service->disabled = true; |  | ||||||
|             } |  | ||||||
|             return $service; |  | ||||||
|         }); |  | ||||||
|     } else { |     } else { | ||||||
|         $services = Http::get(config('constants.services.official')); |         try { | ||||||
|         if ($services->failed()) { |             $response = Http::retry(3, 50)->get(config('constants.services.official')); | ||||||
|             throw new \Exception($services->body()); |             if ($response->failed()) { | ||||||
|  |                 return collect([]); | ||||||
|             } |             } | ||||||
|         $services = collect($services->json())->sortKeys(); |             $services = $response->json(); | ||||||
|  |             $services = collect($services)->sortKeys(); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             $services = collect([]); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |     // $version = config('version');
 | ||||||
|  |     // $services = $services->map(function ($service) use ($version) {
 | ||||||
|  |     //     if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
 | ||||||
|  |     //         $service->disabled = true;
 | ||||||
|  |     //     }
 | ||||||
|  |     //     return $service;
 | ||||||
|  |     // });
 | ||||||
|     return $services; |     return $services; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -493,7 +501,8 @@ function queryResourcesByUuid(string $uuid) | |||||||
|     return $resource; |     return $resource; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function generateDeployWebhook($resource) { | function generateDeployWebhook($resource) | ||||||
|  | { | ||||||
|     $baseUrl = base_url(); |     $baseUrl = base_url(); | ||||||
|     $api = Url::fromString($baseUrl) . '/api/v1'; |     $api = Url::fromString($baseUrl) . '/api/v1'; | ||||||
|     $endpoint = '/deploy'; |     $endpoint = '/deploy'; | ||||||
| @ -501,6 +510,7 @@ function generateDeployWebhook($resource) { | |||||||
|     $url = $api . $endpoint . "?uuid=$uuid&force=false"; |     $url = $api . $endpoint . "?uuid=$uuid&force=false"; | ||||||
|     return $url; |     return $url; | ||||||
| } | } | ||||||
| function removeAnsiColors($text) { | function removeAnsiColors($text) | ||||||
|  | { | ||||||
|     return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); |     return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ return [ | |||||||
| 
 | 
 | ||||||
|     // The release version of your application
 |     // The release version of your application
 | ||||||
|     // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
 |     // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
 | ||||||
|     'release' => '4.0.0-beta.123', |     'release' => '4.0.0-beta.124', | ||||||
|     // When left empty or `null` the Laravel environment will be used
 |     // When left empty or `null` the Laravel environment will be used
 | ||||||
|     'environment' => config('app.env'), |     'environment' => config('app.env'), | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| <?php | <?php | ||||||
| 
 | 
 | ||||||
| return '4.0.0-beta.123'; | return '4.0.0-beta.124'; | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ services: | |||||||
|       - STRIPE_PRICE_ID_PRO_YEARLY |       - STRIPE_PRICE_ID_PRO_YEARLY | ||||||
|       - STRIPE_PRICE_ID_ULTIMATE_MONTHLY |       - STRIPE_PRICE_ID_ULTIMATE_MONTHLY | ||||||
|       - STRIPE_PRICE_ID_ULTIMATE_YEARLY |       - STRIPE_PRICE_ID_ULTIMATE_YEARLY | ||||||
|  |       - STRIPE_EXCLUDED_PLANS | ||||||
|       - PADDLE_VENDOR_ID |       - PADDLE_VENDOR_ID | ||||||
|       - PADDLE_WEBHOOK_SECRET |       - PADDLE_WEBHOOK_SECRET | ||||||
|       - PADDLE_VENDOR_AUTH_CODE |       - PADDLE_VENDOR_AUTH_CODE | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
|         href="{{ route('project.service', $parameters) }}"> |         href="{{ route('project.service', $parameters) }}"> | ||||||
|         <button>Configuration</button> |         <button>Configuration</button> | ||||||
|     </a> |     </a> | ||||||
|     <x-services.links :service="$service" /> |     <x-services.links /> | ||||||
|     <div class="flex-1"></div> |     <div class="flex-1"></div> | ||||||
|     @if (serviceStatus($service) === 'degraded') |     @if (serviceStatus($service) === 'degraded') | ||||||
|         <button wire:click='deploy' onclick="startService.showModal()" |         <button wire:click='deploy' onclick="startService.showModal()" | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
|     <div class="fixed z-50 top-[4.5rem] left-4" id="vue"> |     <div class="fixed z-50 top-[4.5rem] left-4" id="vue"> | ||||||
|         <magic-bar></magic-bar> |         <magic-bar></magic-bar> | ||||||
|     </div> |     </div> | ||||||
|     <main class="main max-w-screen-2xl"> |     <main class="pb-10 main max-w-screen-2xl"> | ||||||
|         {{ $slot }} |         {{ $slot }} | ||||||
|     </main> |     </main> | ||||||
| @endsection | @endsection | ||||||
|  | |||||||
| @ -67,7 +67,7 @@ | |||||||
|                         services, called resources. Any CPU intensive process will use the server's CPU where you |                         services, called resources. Any CPU intensive process will use the server's CPU where you | ||||||
|                         are deploying your resources.</p> |                         are deploying your resources.</p> | ||||||
|                     <p>Localhost is the server where Coolify is running on. It is not recommended to use one server |                     <p>Localhost is the server where Coolify is running on. It is not recommended to use one server | ||||||
|                         for everyting.</p> |                         for everything.</p> | ||||||
|                     <p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud |                     <p>Remote Server is a server reachable through SSH. It can be hosted at home, or from any cloud | ||||||
|                         provider.</p> |                         provider.</p> | ||||||
|                 </x-slot:explanation> |                 </x-slot:explanation> | ||||||
|  | |||||||
| @ -182,7 +182,7 @@ | |||||||
|                             </button> |                             </button> | ||||||
|                         @endif |                         @endif | ||||||
|                         @empty |                         @empty | ||||||
|                         <div>No service found.</div> |                         <div>No service found. Please try to reload the list!</div> | ||||||
|                     @endforelse |                     @endforelse | ||||||
|                 @endif |                 @endif | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -16,19 +16,21 @@ | |||||||
|                 <x-forms.input label="Description" id="application.description"></x-forms.input> |                 <x-forms.input label="Description" id="application.description"></x-forms.input> | ||||||
|             </div> |             </div> | ||||||
|             <div class="flex gap-2"> |             <div class="flex gap-2"> | ||||||
|  |                 @if (!$application->serviceType()?->contains(str($application->image)->before(':'))) | ||||||
|                     @if ($application->required_fqdn) |                     @if ($application->required_fqdn) | ||||||
|                         <x-forms.input required placeholder="https://app.coolify.io" label="Domains" |                         <x-forms.input required placeholder="https://app.coolify.io" label="Domains" | ||||||
|                         id="application.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> |                             id="application.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 |                     @else | ||||||
|                     <x-forms.input placeholder="https://app.coolify.io" label="Domains" |                         <x-forms.input placeholder="https://app.coolify.io" label="Domains" id="application.fqdn" | ||||||
|                         id="application.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>- 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 |                 @endif | ||||||
|                 <x-forms.input required |                 <x-forms.input required | ||||||
|                     helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" |                     helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" | ||||||
|                     label="Image" id="application.image"></x-forms.input> |                     label="Image" id="application.image"></x-forms.input> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
| 
 |  | ||||||
|         <h3 class="pt-2">Advanced</h3> |         <h3 class="pt-2">Advanced</h3> | ||||||
|         <div class="w-64"> |         <div class="w-64"> | ||||||
|             <x-forms.checkbox instantSave label="Exclude from service status" |             <x-forms.checkbox instantSave label="Exclude from service status" | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ | |||||||
|         <div class="w-full pl-8"> |         <div class="w-full pl-8"> | ||||||
|             <div x-cloak x-show="activeTab === 'service-stack'"> |             <div x-cloak x-show="activeTab === 'service-stack'"> | ||||||
|                 <livewire:project.service.stack-form :service="$service" /> |                 <livewire:project.service.stack-form :service="$service" /> | ||||||
|                 <div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3"> |                 <div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1"> | ||||||
|                     @foreach ($applications as $application) |                     @foreach ($applications as $application) | ||||||
|                         <div @class([ |                         <div @class([ | ||||||
|                             'border-l border-dashed border-red-500' => Str::of( |                             'border-l border-dashed border-red-500' => Str::of( | ||||||
| @ -58,7 +58,7 @@ | |||||||
|                                 @endif |                                 @endif | ||||||
|                                 <div class="text-xs">{{ $application->status }}</div> |                                 <div class="text-xs">{{ $application->status }}</div> | ||||||
|                             </a> |                             </a> | ||||||
|                             <a class="flex gap-2 p-1 mx-4 font-bold rounded group-hover:text-white hover:no-underline" |                             <a class="flex items-center gap-2 p-1 mx-4 font-bold rounded group-hover:text-white hover:no-underline" | ||||||
|                                 href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}"><span |                                 href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}"><span | ||||||
|                                     class="hover:text-warning">Logs</span></a> |                                     class="hover:text-warning">Logs</span></a> | ||||||
|                         </div> |                         </div> | ||||||
| @ -88,7 +88,7 @@ | |||||||
|                                 @endif |                                 @endif | ||||||
|                                 <div class="text-xs">{{ $database->status }}</div> |                                 <div class="text-xs">{{ $database->status }}</div> | ||||||
|                             </a> |                             </a> | ||||||
|                             <a class="flex gap-2 p-1 mx-4 font-bold rounded hover:no-underline group-hover:text-white" |                             <a class="flex items-center gap-2 p-1 mx-4 font-bold rounded hover:no-underline group-hover:text-white" | ||||||
|                                 href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}"><span |                                 href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}"><span | ||||||
|                                     class="hover:text-warning">Logs</span></a> |                                     class="hover:text-warning">Logs</span></a> | ||||||
|                         </div> |                         </div> | ||||||
|  | |||||||
| @ -12,9 +12,17 @@ | |||||||
|         <x-forms.input id="service.name" required label="Service Name" placeholder="My super wordpress site" /> |         <x-forms.input id="service.name" required label="Service Name" placeholder="My super wordpress site" /> | ||||||
|         <x-forms.input id="service.description" label="Description" /> |         <x-forms.input id="service.description" label="Description" /> | ||||||
|     </div> |     </div> | ||||||
|     {{-- @if ($isConfigurationRequired) |     @if ($fields) | ||||||
|         <div class="text-warning">This service requires additional confiugration. Please check our <a |         <div> | ||||||
|                 href="https://coolify.io/docs" class="text-white underline">documentation</a> for further information. |             <h3>Service Specific Configuration</h3> | ||||||
|         </div> |         </div> | ||||||
|     @endif --}} |         <div class="grid grid-cols-2 gap-2"> | ||||||
|  |             @foreach ($fields as $serviceName => $fields) | ||||||
|  |                 <x-forms.input type="{{ data_get($fields, 'isPassword') ? 'password' : 'text' }}" required | ||||||
|  |                     helper="Variable name: {{ $serviceName }}" | ||||||
|  |                     label="{{ data_get($fields, 'serviceName') }} {{ data_get($fields, 'name') }}" | ||||||
|  |                     id="fields.{{ $serviceName }}.value"></x-forms.input> | ||||||
|  |             @endforeach | ||||||
|  |         </div> | ||||||
|  |     @endif | ||||||
| </form> | </form> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| # documentation: https://fider.io/doc | # documentation: https://fider.io/docs | ||||||
| # slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services. | # slogan: Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services. | ||||||
| # tags: feedback, user-feedback | # tags: feedback, user-feedback | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,29 +13,3 @@ services: | |||||||
|       - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO |       - MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO | ||||||
|     volumes: |     volumes: | ||||||
|       - minio-data:/data |       - minio-data:/data | ||||||
| 
 |  | ||||||
| # services: |  | ||||||
| #   minio: |  | ||||||
| #     image: minio/minio |  | ||||||
| #     command: server /data --address ":9000" --console-address ":9001" |  | ||||||
| #     networks: |  | ||||||
| #       - coolify |  | ||||||
| #     environment: |  | ||||||
| #       - MINIO_SERVER_URL=http://minio.65.21.189.27.sslip.io |  | ||||||
| #       - MINIO_BROWSER_REDIRECT_URL=http://console.65.21.189.27.sslip.io |  | ||||||
| #       - MINIO_BROWSER=on |  | ||||||
| #       - MINIO_ROOT_USER=asd |  | ||||||
| #       - MINIO_ROOT_PASSWORD=asdasdasd |  | ||||||
| #     labels: |  | ||||||
| #       - "traefik.enable=true" |  | ||||||
| #       - "traefik.http.routers.minio-console.rule=Host(`console.65.21.189.27.sslip.io`)" |  | ||||||
| #       - "traefik.http.routers.minio-console.entrypoints=http" |  | ||||||
| #       - "traefik.http.routers.minio-console.service=minio-console" |  | ||||||
| #       - "traefik.http.services.minio-console.loadbalancer.server.port=9001" |  | ||||||
| #       - "traefik.http.routers.minio.rule=Host(`minio.65.21.189.27.sslip.io`)" |  | ||||||
| #       - "traefik.http.routers.minio.entrypoints=http" |  | ||||||
| #       - "traefik.http.routers.minio.service=minio" |  | ||||||
| #       - "traefik.http.services.minio.loadbalancer.server.port=9000" |  | ||||||
| # networks: |  | ||||||
| #   coolify: |  | ||||||
| #     external: true |  | ||||||
|  | |||||||
| @ -132,7 +132,7 @@ | |||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|     "fider": { |     "fider": { | ||||||
|         "documentation": "https:\/\/fider.io\/doc", |         "documentation": "https:\/\/fider.io\/docs", | ||||||
|         "slogan": "Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services.", |         "slogan": "Fider is an open-source feedback platform for collecting and managing user feedback, helping you prioritize improvements to your products and services.", | ||||||
|         "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogJ2dldGZpZGVyL2ZpZGVyOnN0YWJsZScKICAgIGVudmlyb25tZW50OgogICAgICBCQVNFX1VSTDogJFNFUlZJQ0VfRlFETl9GSURFUgogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIEpXVF9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0ZJREVSCiAgICAgIEVNQUlMX05PUkVQTFk6ICcke0VNQUlMX05PUkVQTFk6LW5vcmVwbHlAZXhhbXBsZS5jb219JwogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICcke0VNQUlMX1NNVFBfSE9TVDotc210cC5tYWlsZ3VuLmNvbX0nCiAgICAgIEVNQUlMX1NNVFBfUE9SVDogJyR7RU1BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICBFTUFJTF9TTVRQX1VTRVJOQU1FOiAnJHtFTUFJTF9TTVRQX1VTRVJOQU1FOi1wb3N0bWFzdGVyQG1haWxndW4uY29tfScKICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxMicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotZmlkZXJ9Jwo=", |         "compose": "c2VydmljZXM6CiAgZmlkZXI6CiAgICBpbWFnZTogJ2dldGZpZGVyL2ZpZGVyOnN0YWJsZScKICAgIGVudmlyb25tZW50OgogICAgICBCQVNFX1VSTDogJFNFUlZJQ0VfRlFETl9GSURFUgogICAgICBEQVRBQkFTRV9VUkw6ICdwb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfTVlTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUxAZGF0YWJhc2U6NTQzMi9maWRlcj9zc2xtb2RlPWRpc2FibGUnCiAgICAgIEpXVF9TRUNSRVQ6ICRTRVJWSUNFX1BBU1NXT1JEXzY0X0ZJREVSCiAgICAgIEVNQUlMX05PUkVQTFk6ICcke0VNQUlMX05PUkVQTFk6LW5vcmVwbHlAZXhhbXBsZS5jb219JwogICAgICBFTUFJTF9NQUlMR1VOX0FQSTogJEVNQUlMX01BSUxHVU5fQVBJCiAgICAgIEVNQUlMX01BSUxHVU5fRE9NQUlOOiAkRU1BSUxfTUFJTEdVTl9ET01BSU4KICAgICAgRU1BSUxfTUFJTEdVTl9SRUdJT046ICRFTUFJTF9NQUlMR1VOX1JFR0lPTgogICAgICBFTUFJTF9TTVRQX0hPU1Q6ICcke0VNQUlMX1NNVFBfSE9TVDotc210cC5tYWlsZ3VuLmNvbX0nCiAgICAgIEVNQUlMX1NNVFBfUE9SVDogJyR7RU1BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICBFTUFJTF9TTVRQX1VTRVJOQU1FOiAnJHtFTUFJTF9TTVRQX1VTRVJOQU1FOi1wb3N0bWFzdGVyQG1haWxndW4uY29tfScKICAgICAgRU1BSUxfU01UUF9QQVNTV09SRDogJEVNQUlMX1NNVFBfUEFTU1dPUkQKICAgICAgRU1BSUxfU01UUF9FTkFCTEVfU1RBUlRUTFM6ICRFTUFJTF9TTVRQX0VOQUJMRV9TVEFSVFRMUwogICAgICBFTUFJTF9BV1NTRVNfUkVHSU9OOiAkRU1BSUxfQVdTU0VTX1JFR0lPTgogICAgICBFTUFJTF9BV1NTRVNfQUNDRVNTX0tFWV9JRDogJEVNQUlMX0FXU1NFU19BQ0NFU1NfS0VZX0lECiAgICAgIEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWTogJEVNQUlMX0FXU1NFU19TRUNSRVRfQUNDRVNTX0tFWQogIGRhdGFiYXNlOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxMicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPU1RHUkVTX1VTRVI6ICRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICAgIFBPU1RHUkVTX0RCOiAnJHtQT1NUR1JFU19EQjotZmlkZXJ9Jwo=", | ||||||
|         "tags": [ |         "tags": [ | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
|             "version": "3.12.36" |             "version": "3.12.36" | ||||||
|         }, |         }, | ||||||
|         "v4": { |         "v4": { | ||||||
|             "version": "4.0.0-beta.123" |             "version": "4.0.0-beta.124" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user