commit
5e7edc9757
34 changed files with 1643 additions and 358 deletions
|
|
@ -190,6 +190,7 @@ public function applications(Request $request)
|
|||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -217,6 +218,35 @@ public function applications(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_public_application(Request $request)
|
||||
|
|
@ -310,6 +340,7 @@ public function create_public_application(Request $request)
|
|||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -337,6 +368,35 @@ public function create_public_application(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_private_gh_app_application(Request $request)
|
||||
|
|
@ -430,6 +490,7 @@ public function create_private_gh_app_application(Request $request)
|
|||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -457,6 +518,35 @@ public function create_private_gh_app_application(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_private_deploy_key_application(Request $request)
|
||||
|
|
@ -534,6 +624,7 @@ public function create_private_deploy_key_application(Request $request)
|
|||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -561,6 +652,35 @@ public function create_private_deploy_key_application(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_dockerfile_application(Request $request)
|
||||
|
|
@ -635,6 +755,7 @@ public function create_dockerfile_application(Request $request)
|
|||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -662,6 +783,35 @@ public function create_dockerfile_application(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_dockerimage_application(Request $request)
|
||||
|
|
@ -699,6 +849,7 @@ public function create_dockerimage_application(Request $request)
|
|||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -726,6 +877,35 @@ public function create_dockerimage_application(Request $request)
|
|||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_dockercompose_application(Request $request)
|
||||
|
|
@ -746,7 +926,7 @@ private function create_application(Request $request, $type)
|
|||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override'];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -1380,7 +1560,7 @@ private function create_application(Request $request, $type)
|
|||
'domains' => data_get($application, 'domains'),
|
||||
]))->setStatusCode(201);
|
||||
} elseif ($type === 'dockercompose') {
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw', 'force_domain_override'];
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
|
|
@ -1810,6 +1990,7 @@ public function delete_by_uuid(Request $request)
|
|||
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
|
|
@ -1843,6 +2024,35 @@ public function delete_by_uuid(Request $request)
|
|||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 409,
|
||||
description: 'Domain conflicts detected.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Domain conflicts detected. Use force_domain_override=true to proceed.'],
|
||||
'warning' => ['type' => 'string', 'example' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.'],
|
||||
'conflicts' => [
|
||||
'type' => 'array',
|
||||
'items' => new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'domain' => ['type' => 'string', 'example' => 'example.com'],
|
||||
'resource_name' => ['type' => 'string', 'example' => 'My Application'],
|
||||
'resource_uuid' => ['type' => 'string', 'nullable' => true, 'example' => 'abc123-def456'],
|
||||
'resource_type' => ['type' => 'string', 'enum' => ['application', 'service', 'instance'], 'example' => 'application'],
|
||||
'message' => ['type' => 'string', 'example' => 'Domain example.com is already in use by application \'My Application\''],
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function update_by_uuid(Request $request)
|
||||
|
|
@ -1866,7 +2076,7 @@ public function update_by_uuid(Request $request)
|
|||
$this->authorize('update', $application);
|
||||
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network'];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password', 'connect_to_docker_network', 'force_domain_override'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
|
|
@ -1982,14 +2192,23 @@ public function update_by_uuid(Request $request)
|
|||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'domains' => 'One of the domain is already used.',
|
||||
],
|
||||
'errors' => ['domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// If there are conflicts and force is not enabled, return warning
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
|
||||
$dockerComposeDomainsJson = collect();
|
||||
|
|
@ -3102,131 +3321,6 @@ public function action_restart(Request $request)
|
|||
);
|
||||
}
|
||||
|
||||
// #[OA\Post(
|
||||
// summary: 'Execute Command',
|
||||
// description: "Execute a command on the application's current container.",
|
||||
// path: '/applications/{uuid}/execute',
|
||||
// operationId: 'execute-command-application',
|
||||
// security: [
|
||||
// ['bearerAuth' => []],
|
||||
// ],
|
||||
// tags: ['Applications'],
|
||||
// parameters: [
|
||||
// new OA\Parameter(
|
||||
// name: 'uuid',
|
||||
// in: 'path',
|
||||
// description: 'UUID of the application.',
|
||||
// required: true,
|
||||
// schema: new OA\Schema(
|
||||
// type: 'string',
|
||||
// format: 'uuid',
|
||||
// )
|
||||
// ),
|
||||
// ],
|
||||
// requestBody: new OA\RequestBody(
|
||||
// required: true,
|
||||
// description: 'Command to execute.',
|
||||
// content: new OA\MediaType(
|
||||
// mediaType: 'application/json',
|
||||
// schema: new OA\Schema(
|
||||
// type: 'object',
|
||||
// properties: [
|
||||
// 'command' => ['type' => 'string', 'description' => 'Command to execute.'],
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// responses: [
|
||||
// new OA\Response(
|
||||
// response: 200,
|
||||
// description: "Execute a command on the application's current container.",
|
||||
// content: [
|
||||
// new OA\MediaType(
|
||||
// mediaType: 'application/json',
|
||||
// schema: new OA\Schema(
|
||||
// type: 'object',
|
||||
// properties: [
|
||||
// 'message' => ['type' => 'string', 'example' => 'Command executed.'],
|
||||
// 'response' => ['type' => 'string'],
|
||||
// ]
|
||||
// )
|
||||
// ),
|
||||
// ]
|
||||
// ),
|
||||
// new OA\Response(
|
||||
// response: 401,
|
||||
// ref: '#/components/responses/401',
|
||||
// ),
|
||||
// new OA\Response(
|
||||
// response: 400,
|
||||
// ref: '#/components/responses/400',
|
||||
// ),
|
||||
// new OA\Response(
|
||||
// response: 404,
|
||||
// ref: '#/components/responses/404',
|
||||
// ),
|
||||
// ]
|
||||
// )]
|
||||
// public function execute_command_by_uuid(Request $request)
|
||||
// {
|
||||
// // TODO: Need to review this from security perspective, to not allow arbitrary command execution
|
||||
// $allowedFields = ['command'];
|
||||
// $teamId = getTeamIdFromToken();
|
||||
// if (is_null($teamId)) {
|
||||
// return invalidTokenResponse();
|
||||
// }
|
||||
// $uuid = $request->route('uuid');
|
||||
// if (! $uuid) {
|
||||
// return response()->json(['message' => 'UUID is required.'], 400);
|
||||
// }
|
||||
// $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
||||
// if (! $application) {
|
||||
// return response()->json(['message' => 'Application not found.'], 404);
|
||||
// }
|
||||
// $return = validateIncomingRequest($request);
|
||||
// if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
// return $return;
|
||||
// }
|
||||
// $validator = customApiValidator($request->all(), [
|
||||
// 'command' => 'string|required',
|
||||
// ]);
|
||||
|
||||
// $extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
// if ($validator->fails() || ! empty($extraFields)) {
|
||||
// $errors = $validator->errors();
|
||||
// if (! empty($extraFields)) {
|
||||
// foreach ($extraFields as $field) {
|
||||
// $errors->add($field, 'This field is not allowed.');
|
||||
// }
|
||||
// }
|
||||
|
||||
// return response()->json([
|
||||
// 'message' => 'Validation failed.',
|
||||
// 'errors' => $errors,
|
||||
// ], 422);
|
||||
// }
|
||||
|
||||
// $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
|
||||
// $status = getContainerStatus($application->destination->server, $container['Names']);
|
||||
|
||||
// if ($status !== 'running') {
|
||||
// return response()->json([
|
||||
// 'message' => 'Application is not running.',
|
||||
// ], 400);
|
||||
// }
|
||||
|
||||
// $commands = collect([
|
||||
// executeInDocker($container['Names'], $request->command),
|
||||
// ]);
|
||||
|
||||
// $res = instant_remote_process(command: $commands, server: $application->destination->server);
|
||||
|
||||
// return response()->json([
|
||||
// 'message' => 'Command executed.',
|
||||
// 'response' => $res,
|
||||
// ]);
|
||||
// }
|
||||
|
||||
private function validateDataApplications(Request $request, Server $server)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
|
|
@ -3286,14 +3380,23 @@ private function validateDataApplications(Request $request, Server $server)
|
|||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($fqdn, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'domains' => 'One of the domain is already used.',
|
||||
],
|
||||
'errors' => ['domains' => $result['error']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
// If there are conflicts and force is not enabled, return warning
|
||||
if ($result['hasConflicts'] && ! $request->boolean('force_domain_override')) {
|
||||
return response()->json([
|
||||
'message' => 'Domain conflicts detected. Use force_domain_override=true to proceed.',
|
||||
'conflicts' => $result['conflicts'],
|
||||
'warning' => 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.',
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,17 +15,15 @@ class CanAccessTerminal
|
|||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (! auth()->check()) {
|
||||
abort(401, 'Authentication required');
|
||||
}
|
||||
|
||||
// Only admins/owners can access terminal functionality
|
||||
if (! auth()->user()->can('canAccessTerminal')) {
|
||||
abort(403, 'Access to terminal functionality is restricted to team administrators');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
||||
// if (! auth()->check()) {
|
||||
// abort(401, 'Authentication required');
|
||||
// }
|
||||
|
||||
// // Only admins/owners can access terminal functionality
|
||||
// if (! auth()->user()->can('canAccessTerminal')) {
|
||||
// abort(403, 'Access to terminal functionality is restricted to team administrators');
|
||||
// }
|
||||
|
||||
// return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,9 +51,16 @@ class General extends Component
|
|||
|
||||
public $parsedServiceDomains = [];
|
||||
|
||||
public $domainConflicts = [];
|
||||
|
||||
public $showDomainConflictModal = false;
|
||||
|
||||
public $forceSaveDomains = false;
|
||||
|
||||
protected $listeners = [
|
||||
'resetDefaultLabels',
|
||||
'configurationChanged' => '$refresh',
|
||||
'confirmDomainUsage',
|
||||
];
|
||||
|
||||
protected function rules(): array
|
||||
|
|
@ -430,7 +437,7 @@ public function getWildcardDomain()
|
|||
|
||||
$server = data_get($this->application, 'destination.server');
|
||||
if ($server) {
|
||||
$fqdn = generateFqdn(server: $server, random: $this->application->uuid, parserVersion: $this->application->compose_parsing_version);
|
||||
$fqdn = generateUrl(server: $server, random: $this->application->uuid);
|
||||
$this->application->fqdn = $fqdn;
|
||||
$this->application->save();
|
||||
$this->resetDefaultLabels();
|
||||
|
|
@ -485,10 +492,33 @@ public function checkFqdns($showToaster = true)
|
|||
}
|
||||
}
|
||||
}
|
||||
check_domain_usage(resource: $this->application);
|
||||
|
||||
// Check for domain conflicts if not forcing save
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(resource: $this->application);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
|
||||
$this->application->fqdn = $domains->implode(',');
|
||||
$this->resetDefaultLabels(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function confirmDomainUsage()
|
||||
{
|
||||
$this->forceSaveDomains = true;
|
||||
$this->showDomainConflictModal = false;
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function setRedirect()
|
||||
|
|
@ -536,7 +566,9 @@ public function submit($showToaster = true)
|
|||
$this->application->parseHealthcheckFromDockerfile($this->application->dockerfile);
|
||||
}
|
||||
|
||||
$this->checkFqdns();
|
||||
if (! $this->checkFqdns()) {
|
||||
return; // Stop if there are conflicts and user hasn't confirmed
|
||||
}
|
||||
|
||||
$this->application->save();
|
||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||
|
|
@ -588,7 +620,20 @@ public function submit($showToaster = true)
|
|||
}
|
||||
}
|
||||
}
|
||||
check_domain_usage(resource: $this->application);
|
||||
// Check for domain conflicts if not forcing save
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(resource: $this->application);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
|
||||
$this->application->save();
|
||||
$this->resetDefaultLabels();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ class Previews extends Component
|
|||
|
||||
public int $rate_limit_remaining;
|
||||
|
||||
public $domainConflicts = [];
|
||||
|
||||
public $showDomainConflictModal = false;
|
||||
|
||||
public $forceSaveDomains = false;
|
||||
|
||||
public $pendingPreviewId = null;
|
||||
|
||||
protected $rules = [
|
||||
'application.previews.*.fqdn' => 'string|nullable',
|
||||
];
|
||||
|
|
@ -49,6 +57,16 @@ public function load_prs()
|
|||
}
|
||||
}
|
||||
|
||||
public function confirmDomainUsage()
|
||||
{
|
||||
$this->forceSaveDomains = true;
|
||||
$this->showDomainConflictModal = false;
|
||||
if ($this->pendingPreviewId) {
|
||||
$this->save_preview($this->pendingPreviewId);
|
||||
$this->pendingPreviewId = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function save_preview($preview_id)
|
||||
{
|
||||
try {
|
||||
|
|
@ -63,7 +81,20 @@ public function save_preview($preview_id)
|
|||
$this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$success = false;
|
||||
}
|
||||
check_domain_usage(resource: $this->application, domain: $preview->fqdn);
|
||||
// Check for domain conflicts if not forcing save
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(resource: $this->application, domain: $preview->fqdn);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
$this->pendingPreviewId = $preview_id;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $preview) {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public function generate()
|
|||
$random = new Cuid2;
|
||||
|
||||
// Generate a unique domain like main app services do
|
||||
$generated_fqdn = generateFqdn(server: $server, random: $random, parserVersion: $this->preview->application->compose_parsing_version);
|
||||
$generated_fqdn = generateUrl(server: $server, random: $random);
|
||||
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public function clone(string $type)
|
|||
$uuid = (string) new Cuid2;
|
||||
$url = $application->fqdn;
|
||||
if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||
$url = generateFqdn(server: $this->server, random: $uuid, parserVersion: $application->compose_parsing_version);
|
||||
$url = generateUrl(server: $this->server, random: $uuid);
|
||||
}
|
||||
|
||||
$newApplication = $application->replicate([
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public function submit()
|
|||
'health_check_enabled' => false,
|
||||
]);
|
||||
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->update([
|
||||
'name' => 'docker-image-'.$application->uuid,
|
||||
'fqdn' => $fqdn,
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ public function submit()
|
|||
$application['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->fqdn = $fqdn;
|
||||
|
||||
$application->name = generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ public function submit()
|
|||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->fqdn = $fqdn;
|
||||
$application->name = generate_random_name($application->uuid);
|
||||
$application->save();
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ public function submit()
|
|||
|
||||
$application->settings->is_static = $this->isStatic;
|
||||
$application->settings->save();
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->fqdn = $fqdn;
|
||||
$application->save();
|
||||
if ($this->checkCoolifyConfig) {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public function submit()
|
|||
'source_type' => GithubApp::class,
|
||||
]);
|
||||
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$fqdn = generateUrl(server: $destination->server, random: $application->uuid);
|
||||
$application->update([
|
||||
'name' => 'dockerfile-'.$application->uuid,
|
||||
'fqdn' => $fqdn,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ class EditDomain extends Component
|
|||
|
||||
public ServiceApplication $application;
|
||||
|
||||
public $domainConflicts = [];
|
||||
|
||||
public $showDomainConflictModal = false;
|
||||
|
||||
public $forceSaveDomains = false;
|
||||
|
||||
protected $rules = [
|
||||
'application.fqdn' => 'nullable',
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
|
|
@ -22,6 +28,13 @@ public function mount()
|
|||
$this->application = ServiceApplication::find($this->applicationId);
|
||||
}
|
||||
|
||||
public function confirmDomainUsage()
|
||||
{
|
||||
$this->forceSaveDomains = true;
|
||||
$this->showDomainConflictModal = false;
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
|
@ -37,7 +50,20 @@ public function submit()
|
|||
if ($warning) {
|
||||
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||
}
|
||||
check_domain_usage(resource: $this->application);
|
||||
// Check for domain conflicts if not forcing save
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(resource: $this->application);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ class ServiceApplicationView extends Component
|
|||
|
||||
public $delete_volumes = true;
|
||||
|
||||
public $domainConflicts = [];
|
||||
|
||||
public $showDomainConflictModal = false;
|
||||
|
||||
public $forceSaveDomains = false;
|
||||
|
||||
protected $rules = [
|
||||
'application.human_name' => 'nullable',
|
||||
'application.description' => 'nullable',
|
||||
|
|
@ -129,6 +135,13 @@ public function convertToDatabase()
|
|||
}
|
||||
}
|
||||
|
||||
public function confirmDomainUsage()
|
||||
{
|
||||
$this->forceSaveDomains = true;
|
||||
$this->showDomainConflictModal = false;
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
|
@ -145,7 +158,20 @@ public function submit()
|
|||
if ($warning) {
|
||||
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||
}
|
||||
check_domain_usage(resource: $this->application);
|
||||
// Check for domain conflicts if not forcing save
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(resource: $this->application);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ class ExecuteContainerCommand extends Component
|
|||
|
||||
public function mount()
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403);
|
||||
}
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->containers = collect();
|
||||
$this->servers = collect();
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public function cloneTo($destination_id)
|
|||
$url = $this->resource->fqdn;
|
||||
|
||||
if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) {
|
||||
$url = generateFqdn(server: $server, random: $uuid, parserVersion: $this->resource->compose_parsing_version);
|
||||
$url = generateUrl(server: $server, random: $uuid);
|
||||
}
|
||||
|
||||
$new_resource = $this->resource->replicate([
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ class Index extends Component
|
|||
#[Validate('required|string|timezone')]
|
||||
public string $instance_timezone;
|
||||
|
||||
public array $domainConflicts = [];
|
||||
|
||||
public bool $showDomainConflictModal = false;
|
||||
|
||||
public bool $forceSaveDomains = false;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.index');
|
||||
|
|
@ -81,6 +87,13 @@ public function instantSave($isSave = true)
|
|||
}
|
||||
}
|
||||
|
||||
public function confirmDomainUsage()
|
||||
{
|
||||
$this->forceSaveDomains = true;
|
||||
$this->showDomainConflictModal = false;
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
|
@ -108,7 +121,18 @@ public function submit()
|
|||
}
|
||||
}
|
||||
if ($this->fqdn) {
|
||||
check_domain_usage(domain: $this->fqdn);
|
||||
if (! $this->forceSaveDomains) {
|
||||
$result = checkDomainUsage(domain: $this->fqdn);
|
||||
if ($result['hasConflicts']) {
|
||||
$this->domainConflicts = $result['conflicts'];
|
||||
$this->showDomainConflictModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Reset the force flag after using it
|
||||
$this->forceSaveDomains = false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->instantSave(isSave: false);
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public function persistentStorages()
|
|||
|
||||
public function generate_preview_fqdn()
|
||||
{
|
||||
if (empty($this->fqdn) && $this->application->fqdn) {
|
||||
if ($this->application->fqdn) {
|
||||
if (str($this->application->fqdn)->contains(',')) {
|
||||
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
|
||||
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ public function view(User $user, Server $server): bool
|
|||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
// return $user->isAdmin();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,7 +37,8 @@ public function create(User $user): bool
|
|||
*/
|
||||
public function update(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,7 +46,8 @@ public function update(User $user, Server $server): bool
|
|||
*/
|
||||
public function delete(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +71,8 @@ public function forceDelete(User $user, Server $server): bool
|
|||
*/
|
||||
public function manageProxy(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,7 +80,8 @@ public function manageProxy(User $user, Server $server): bool
|
|||
*/
|
||||
public function manageSentinel(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,15 +89,8 @@ public function manageSentinel(User $user, Server $server): bool
|
|||
*/
|
||||
public function manageCaCertificate(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view terminal.
|
||||
*/
|
||||
public function viewTerminal(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,6 +98,7 @@ public function viewTerminal(User $user, Server $server): bool
|
|||
*/
|
||||
public function viewSecurity(User $user, Server $server): bool
|
||||
{
|
||||
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,7 @@ public function boot(): void
|
|||
|
||||
// Register gate for terminal access
|
||||
Gate::define('canAccessTerminal', function ($user) {
|
||||
// return $user->isAdmin() || $user->isOwner();
|
||||
return true;
|
||||
return $user->isAdmin() || $user->isOwner();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,4 +176,5 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
|
|||
$request->offsetUnset('private_key_uuid');
|
||||
$request->offsetUnset('use_build_server');
|
||||
$request->offsetUnset('is_static');
|
||||
$request->offsetUnset('force_domain_override');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,12 +256,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||
|
||||
if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) {
|
||||
$MINIO_BROWSER_REDIRECT_URL->update([
|
||||
'value' => generateFqdn(server: $server, random: 'console-'.$uuid, parserVersion: $resource->service->compose_parsing_version, forceHttps: true),
|
||||
'value' => generateUrl(server: $server, random: 'console-'.$uuid, forceHttps: true),
|
||||
]);
|
||||
}
|
||||
if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) {
|
||||
$MINIO_SERVER_URL->update([
|
||||
'value' => generateFqdn(server: $server, random: 'minio-'.$uuid, parserVersion: $resource->service->compose_parsing_version, forceHttps: true),
|
||||
'value' => generateUrl(server: $server, random: 'minio-'.$uuid, forceHttps: true),
|
||||
]);
|
||||
}
|
||||
$payload = collect([
|
||||
|
|
@ -279,12 +279,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||
|
||||
if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) {
|
||||
$LOGTO_ENDPOINT->update([
|
||||
'value' => generateFqdn(server: $server, random: 'logto-'.$uuid, parserVersion: $resource->service->compose_parsing_version),
|
||||
'value' => generateUrl(server: $server, random: 'logto-'.$uuid),
|
||||
]);
|
||||
}
|
||||
if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) {
|
||||
$LOGTO_ADMIN_ENDPOINT->update([
|
||||
'value' => generateFqdn(server: $server, random: 'logto-admin-'.$uuid, parserVersion: $resource->service->compose_parsing_version),
|
||||
'value' => generateUrl(server: $server, random: 'logto-admin-'.$uuid),
|
||||
]);
|
||||
}
|
||||
$payload = collect([
|
||||
|
|
|
|||
237
bootstrap/helpers/domains.php
Normal file
237
bootstrap/helpers/domains.php
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ServiceApplication;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
function checkDomainUsage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
||||
{
|
||||
$conflicts = [];
|
||||
|
||||
// Get the current team for filtering
|
||||
$currentTeam = null;
|
||||
if ($resource) {
|
||||
$currentTeam = $resource->team();
|
||||
}
|
||||
|
||||
if ($resource) {
|
||||
if ($resource->getMorphClass() === Application::class && $resource->build_pack === 'dockercompose') {
|
||||
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
|
||||
$domains = collect($domains);
|
||||
} else {
|
||||
$domains = collect($resource->fqdns);
|
||||
}
|
||||
} elseif ($domain) {
|
||||
$domains = collect([$domain]);
|
||||
} else {
|
||||
return ['conflicts' => [], 'hasConflicts' => false];
|
||||
}
|
||||
|
||||
$domains = $domains->map(function ($domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
|
||||
return str($domain);
|
||||
});
|
||||
|
||||
// Filter applications by team if we have a current team
|
||||
$appsQuery = Application::query();
|
||||
if ($currentTeam) {
|
||||
$appsQuery = $appsQuery->whereHas('environment.project', function ($query) use ($currentTeam) {
|
||||
$query->where('team_id', $currentTeam->id);
|
||||
});
|
||||
}
|
||||
$apps = $appsQuery->get();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if (data_get($resource, 'uuid')) {
|
||||
if ($resource->uuid !== $app->uuid) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_link' => $app->link(),
|
||||
'resource_type' => 'application',
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
|
||||
];
|
||||
}
|
||||
} elseif ($domain) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_link' => $app->link(),
|
||||
'resource_type' => 'application',
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter service applications by team if we have a current team
|
||||
$serviceAppsQuery = ServiceApplication::query();
|
||||
if ($currentTeam) {
|
||||
$serviceAppsQuery = $serviceAppsQuery->whereHas('service.environment.project', function ($query) use ($currentTeam) {
|
||||
$query->where('team_id', $currentTeam->id);
|
||||
});
|
||||
}
|
||||
$apps = $serviceAppsQuery->get();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if (data_get($resource, 'uuid')) {
|
||||
if ($resource->uuid !== $app->uuid) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->service->name,
|
||||
'resource_link' => $app->service->link(),
|
||||
'resource_type' => 'service',
|
||||
'message' => "Domain $naked_domain is already in use by service '{$app->service->name}'",
|
||||
];
|
||||
}
|
||||
} elseif ($domain) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->service->name,
|
||||
'resource_link' => $app->service->link(),
|
||||
'resource_type' => 'service',
|
||||
'message' => "Domain $naked_domain is already in use by service '{$app->service->name}'",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($resource) {
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$domain = data_get($settings, 'fqdn');
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => 'Coolify Instance',
|
||||
'resource_link' => '#',
|
||||
'resource_type' => 'instance',
|
||||
'message' => "Domain $naked_domain is already in use by this Coolify instance",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'conflicts' => $conflicts,
|
||||
'hasConflicts' => count($conflicts) > 0,
|
||||
];
|
||||
}
|
||||
|
||||
function checkIfDomainIsAlreadyUsedViaAPI(Collection|array $domains, ?string $teamId = null, ?string $uuid = null)
|
||||
{
|
||||
$conflicts = [];
|
||||
|
||||
if (is_null($teamId)) {
|
||||
return ['error' => 'Team ID is required.'];
|
||||
}
|
||||
if (is_array($domains)) {
|
||||
$domains = collect($domains);
|
||||
}
|
||||
|
||||
$domains = $domains->map(function ($domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
|
||||
return str($domain);
|
||||
});
|
||||
|
||||
// Check applications within the same team
|
||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid', 'name', 'id']);
|
||||
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->with('service:id,name')->get(['fqdn', 'uuid', 'id', 'service_id']);
|
||||
|
||||
if ($uuid) {
|
||||
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||
}
|
||||
|
||||
foreach ($applications as $app) {
|
||||
if (is_null($app->fqdn)) {
|
||||
continue;
|
||||
}
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->name,
|
||||
'resource_uuid' => $app->uuid,
|
||||
'resource_type' => 'application',
|
||||
'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($serviceApplications as $app) {
|
||||
if (str($app->fqdn)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => $app->service->name ?? 'Unknown Service',
|
||||
'resource_uuid' => $app->uuid,
|
||||
'resource_type' => 'service',
|
||||
'message' => "Domain $naked_domain is already in use by service '{$app->service->name}'",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check instance-level domain
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$domain = data_get($settings, 'fqdn');
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$conflicts[] = [
|
||||
'domain' => $naked_domain,
|
||||
'resource_name' => 'Coolify Instance',
|
||||
'resource_uuid' => null,
|
||||
'resource_type' => 'instance',
|
||||
'message' => "Domain $naked_domain is already in use by this Coolify instance",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'conflicts' => $conflicts,
|
||||
'hasConflicts' => count($conflicts) > 0,
|
||||
];
|
||||
}
|
||||
|
|
@ -1084,152 +1084,6 @@ function check_ip_against_allowlist($ip, $allowlist)
|
|||
|
||||
return false;
|
||||
}
|
||||
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null)
|
||||
{
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Team ID is required.'], 400);
|
||||
}
|
||||
if (is_array($domains)) {
|
||||
$domains = collect($domains);
|
||||
}
|
||||
|
||||
$domains = $domains->map(function ($domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
|
||||
return str($domain);
|
||||
});
|
||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||
if ($uuid) {
|
||||
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||
}
|
||||
$domainFound = false;
|
||||
foreach ($applications as $app) {
|
||||
if (is_null($app->fqdn)) {
|
||||
continue;
|
||||
}
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$domainFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($domainFound) {
|
||||
return true;
|
||||
}
|
||||
foreach ($serviceApplications as $app) {
|
||||
if (str($app->fqdn)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
$domainFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($domainFound) {
|
||||
return true;
|
||||
}
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$domain = data_get($settings, 'fqdn');
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
||||
{
|
||||
if ($resource) {
|
||||
if ($resource->getMorphClass() === \App\Models\Application::class && $resource->build_pack === 'dockercompose') {
|
||||
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
|
||||
$domains = collect($domains);
|
||||
} else {
|
||||
$domains = collect($resource->fqdns);
|
||||
}
|
||||
} elseif ($domain) {
|
||||
$domains = collect($domain);
|
||||
} else {
|
||||
throw new \RuntimeException('No resource or FQDN provided.');
|
||||
}
|
||||
$domains = $domains->map(function ($domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
|
||||
return str($domain);
|
||||
});
|
||||
$apps = Application::all();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if (data_get($resource, 'uuid')) {
|
||||
if ($resource->uuid !== $app->uuid) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->link()}'>{$app->name}</a>");
|
||||
}
|
||||
} elseif ($domain) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->link()}'>{$app->name}</a>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$apps = ServiceApplication::all();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if (data_get($resource, 'uuid')) {
|
||||
if ($resource->uuid !== $app->uuid) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->service->link()}'>{$app->service->name}</a>");
|
||||
}
|
||||
} elseif ($domain) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->service->link()}'>{$app->service->name}</a>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($resource) {
|
||||
$settings = instanceSettings();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$domain = data_get($settings, 'fqdn');
|
||||
if (str($domain)->endsWith('/')) {
|
||||
$domain = str($domain)->beforeLast('/');
|
||||
}
|
||||
$naked_domain = str($domain)->value();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
return [
|
||||
'coolify' => [
|
||||
'version' => '4.0.0-beta.424',
|
||||
'version' => '4.0.0-beta.425',
|
||||
'helper_version' => '1.0.10',
|
||||
'realtime_version' => '1.0.10',
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
|
|
|
|||
600
openapi.json
600
openapi.json
|
|
@ -357,6 +357,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -385,6 +389,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -709,6 +767,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -737,6 +799,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -1061,6 +1177,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1089,6 +1209,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -1342,6 +1516,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1370,6 +1548,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -1606,6 +1838,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1634,6 +1870,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -1709,6 +1999,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -1737,6 +2031,60 @@
|
|||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -2175,6 +2523,10 @@
|
|||
"connect_to_docker_network": {
|
||||
"type": "boolean",
|
||||
"description": "The flag to connect the service to the predefined Docker network."
|
||||
},
|
||||
"force_domain_override": {
|
||||
"type": "boolean",
|
||||
"description": "Force domain usage even if conflicts are detected. Default is false."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
@ -2206,6 +2558,60 @@
|
|||
},
|
||||
"404": {
|
||||
"$ref": "#\/components\/responses\/404"
|
||||
},
|
||||
"409": {
|
||||
"description": "Domain conflicts detected.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain conflicts detected. Use force_domain_override=true to proceed."
|
||||
},
|
||||
"warning": {
|
||||
"type": "string",
|
||||
"example": "Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior."
|
||||
},
|
||||
"conflicts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"example": "example.com"
|
||||
},
|
||||
"resource_name": {
|
||||
"type": "string",
|
||||
"example": "My Application"
|
||||
},
|
||||
"resource_uuid": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "abc123-def456"
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"application",
|
||||
"service",
|
||||
"instance"
|
||||
],
|
||||
"example": "application"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Domain example.com is already in use by application 'My Application'"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -5196,6 +5602,190 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"\/projects\/{uuid}\/environments": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Projects"
|
||||
],
|
||||
"summary": "List Environments",
|
||||
"description": "List all environments in a project.",
|
||||
"operationId": "get-environments",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "uuid",
|
||||
"in": "path",
|
||||
"description": "Project UUID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of environments",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#\/components\/schemas\/Environment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#\/components\/responses\/401"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"404": {
|
||||
"description": "Project not found."
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Projects"
|
||||
],
|
||||
"summary": "Create Environment",
|
||||
"description": "Create environment in project.",
|
||||
"operationId": "create-environment",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "uuid",
|
||||
"in": "path",
|
||||
"description": "Project UUID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Environment created.",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the environment."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Environment created.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"example": "env123",
|
||||
"description": "The UUID of the environment."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#\/components\/responses\/401"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#\/components\/responses\/400"
|
||||
},
|
||||
"404": {
|
||||
"description": "Project not found."
|
||||
},
|
||||
"409": {
|
||||
"description": "Environment with this name already exists."
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/projects\/{uuid}\/environments\/{environment_name_or_uuid}": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Projects"
|
||||
],
|
||||
"summary": "Delete Environment",
|
||||
"description": "Delete environment by name or UUID. Environment must be empty.",
|
||||
"operationId": "delete-environment",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "uuid",
|
||||
"in": "path",
|
||||
"description": "Project UUID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "environment_name_or_uuid",
|
||||
"in": "path",
|
||||
"description": "Environment name or UUID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Environment deleted.",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Environment deleted."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#\/components\/responses\/401"
|
||||
},
|
||||
"400": {
|
||||
"description": "Environment has resources, so it cannot be deleted."
|
||||
},
|
||||
"404": {
|
||||
"description": "Project or environment not found."
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/resources": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
|
@ -6412,13 +7002,6 @@
|
|||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"required": [
|
||||
"server_uuid",
|
||||
"project_uuid",
|
||||
"environment_name",
|
||||
"environment_uuid",
|
||||
"docker_compose_raw"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
|
|
@ -8026,6 +8609,9 @@
|
|||
"is_swarm_worker": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_terminal_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_usable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
|||
217
openapi.yaml
217
openapi.yaml
|
|
@ -262,6 +262,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -276,6 +279,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -515,6 +528,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -529,6 +545,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -768,6 +794,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -782,6 +811,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -968,6 +1007,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -982,6 +1024,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -1159,6 +1211,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1173,6 +1228,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -1230,6 +1295,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
|
|
@ -1244,6 +1312,16 @@ paths:
|
|||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -1560,6 +1638,9 @@ paths:
|
|||
connect_to_docker_network:
|
||||
type: boolean
|
||||
description: 'The flag to connect the service to the predefined Docker network.'
|
||||
force_domain_override:
|
||||
type: boolean
|
||||
description: 'Force domain usage even if conflicts are detected. Default is false.'
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
|
|
@ -1576,6 +1657,16 @@ paths:
|
|||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
$ref: '#/components/responses/404'
|
||||
'409':
|
||||
description: 'Domain conflicts detected.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Domain conflicts detected. Use force_domain_override=true to proceed.' }
|
||||
warning: { type: string, example: 'Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }
|
||||
conflicts: { type: array, items: { properties: { domain: { type: string, example: example.com }, resource_name: { type: string, example: 'My Application' }, resource_uuid: { type: string, nullable: true, example: abc123-def456 }, resource_type: { type: string, enum: [application, service, instance], example: application }, message: { type: string, example: "Domain example.com is already in use by application 'My Application'" } }, type: object } }
|
||||
type: object
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
|
|
@ -3570,6 +3661,124 @@ paths:
|
|||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/projects/{uuid}/environments':
|
||||
get:
|
||||
tags:
|
||||
- Projects
|
||||
summary: 'List Environments'
|
||||
description: 'List all environments in a project.'
|
||||
operationId: get-environments
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Project UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 'List of environments'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Environment'
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
description: 'Project not found.'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
post:
|
||||
tags:
|
||||
- Projects
|
||||
summary: 'Create Environment'
|
||||
description: 'Create environment in project.'
|
||||
operationId: create-environment
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Project UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
description: 'Environment created.'
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: 'The name of the environment.'
|
||||
type: object
|
||||
responses:
|
||||
'201':
|
||||
description: 'Environment created.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
uuid: { type: string, example: env123, description: 'The UUID of the environment.' }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
$ref: '#/components/responses/400'
|
||||
'404':
|
||||
description: 'Project not found.'
|
||||
'409':
|
||||
description: 'Environment with this name already exists.'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
'/projects/{uuid}/environments/{environment_name_or_uuid}':
|
||||
delete:
|
||||
tags:
|
||||
- Projects
|
||||
summary: 'Delete Environment'
|
||||
description: 'Delete environment by name or UUID. Environment must be empty.'
|
||||
operationId: delete-environment
|
||||
parameters:
|
||||
-
|
||||
name: uuid
|
||||
in: path
|
||||
description: 'Project UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
-
|
||||
name: environment_name_or_uuid
|
||||
in: path
|
||||
description: 'Environment name or UUID'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: 'Environment deleted.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
message: { type: string, example: 'Environment deleted.' }
|
||||
type: object
|
||||
'401':
|
||||
$ref: '#/components/responses/401'
|
||||
'400':
|
||||
description: 'Environment has resources, so it cannot be deleted.'
|
||||
'404':
|
||||
description: 'Project or environment not found.'
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
/resources:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -4289,12 +4498,6 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- server_uuid
|
||||
- project_uuid
|
||||
- environment_name
|
||||
- environment_uuid
|
||||
- docker_compose_raw
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
|
|
@ -5377,6 +5580,8 @@ components:
|
|||
type: boolean
|
||||
is_swarm_worker:
|
||||
type: boolean
|
||||
is_terminal_enabled:
|
||||
type: boolean
|
||||
is_usable:
|
||||
type: boolean
|
||||
logdrain_axiom_api_key:
|
||||
|
|
|
|||
91
resources/views/components/domain-conflict-modal.blade.php
Normal file
91
resources/views/components/domain-conflict-modal.blade.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
@props([
|
||||
'conflicts' => [],
|
||||
'showModal' => false,
|
||||
'confirmAction' => 'confirmDomainUsage',
|
||||
])
|
||||
|
||||
@if ($showModal && count($conflicts) > 0)
|
||||
<div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
|
||||
@keydown.escape.window="modalOpen = false; $wire.set('showDomainConflictModal', false)"
|
||||
:class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
|
||||
<template x-teleport="body">
|
||||
<div x-show="modalOpen"
|
||||
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
|
||||
<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">Domain Already In Use</h2>
|
||||
<button @click="modalOpen = false; $wire.set('showDomainConflictModal', false)"
|
||||
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">
|
||||
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-red-600" role="alert">
|
||||
<p class="font-bold">Warning: Domain Conflict Detected</p>
|
||||
<p>{{ $slot ?? 'The following domain(s) are already in use by other resources. Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h4 class="mb-2 font-semibold">Conflicting Resources:</h4>
|
||||
<ul class="space-y-2">
|
||||
@foreach ($conflicts as $conflict)
|
||||
<li class="flex items-start text-red-500">
|
||||
<div>
|
||||
<strong>{{ $conflict['domain'] }}</strong> is used by
|
||||
@if ($conflict['resource_type'] === 'instance')
|
||||
<strong>{{ $conflict['resource_name'] }}</strong>
|
||||
@else
|
||||
<a href="{{ $conflict['resource_link'] }}" target="_blank"
|
||||
class="underline hover:text-red-400">
|
||||
{{ $conflict['resource_name'] }}
|
||||
</a>
|
||||
@endif
|
||||
({{ $conflict['resource_type'] }})
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 text-yellow-800 dark:text-yellow-200 border-l-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg"
|
||||
role="alert">
|
||||
<p class="font-bold">What will happen if you continue?</p>
|
||||
@if (isset($consequences))
|
||||
{{ $consequences }}
|
||||
@else
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>Only one resource will be accessible at this domain</li>
|
||||
<li>The routing behavior will be unpredictable</li>
|
||||
<li>You may experience service disruptions</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||
<x-forms.button @click="modalOpen = false; $wire.set('showDomainConflictModal', false)"
|
||||
class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
|
||||
Cancel
|
||||
</x-forms.button>
|
||||
<x-forms.button wire:click="{{ $confirmAction }}" @click="modalOpen = false" class="w-auto"
|
||||
isError>
|
||||
I understand, proceed anyway
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -462,6 +462,12 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage" />
|
||||
|
||||
@script
|
||||
<script>
|
||||
$wire.$on('loadCompose', (isInit = true) => {
|
||||
|
|
|
|||
|
|
@ -219,4 +219,19 @@ class="flex items-end gap-2 pt-4">
|
|||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage">
|
||||
The preview deployment domain is already in use by other resources. Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>The preview deployment may not be accessible</li>
|
||||
<li>Conflicts with production or other preview deployments</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
<li>Unpredictable routing behavior</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class="button">+
|
|||
@if ($environment->isEmpty())
|
||||
@can('createAnyResource')
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
class="items-center justify-center box">+ Add New Resource</a>
|
||||
class="items-center justify-center box">+ Add Resource</a>
|
||||
@else
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-8 text-center border border-dashed border-neutral-300 dark:border-coolgray-300 rounded-lg">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
|
||||
<div class="pb-2">Note: If a service has a defined port, do not delete it. <br>If you want to use your custom
|
||||
domain, you can add it with a port.</div>
|
||||
<x-forms.input canGate="update" :canResource="$application" 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>
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
<div class="w-full">
|
||||
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
|
||||
<div class="pb-2">Note: If a service has a defined port, do not delete it. <br>If you want to use your custom
|
||||
domain, you can add it with a port.</div>
|
||||
<x-forms.input canGate="update" :canResource="$application" 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>
|
||||
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
|
||||
<x-domain-conflict-modal :conflicts="$domainConflicts" :showModal="$showDomainConflictModal" confirmAction="confirmDomainUsage">
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>Only one service will be accessible at this domain</li>
|
||||
<li>The routing behavior will be unpredictable</li>
|
||||
<li>You may experience service disruptions</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,4 +67,18 @@
|
|||
instantSave="instantSaveAdvanced" id="application.is_log_drain_enabled" label="Drain Logs" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage">
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>Only one service will be accessible at this domain</li>
|
||||
<li>The routing behavior will be unpredictable</li>
|
||||
<li>You may experience service disruptions</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,5 +79,19 @@ class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-co
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<x-domain-conflict-modal
|
||||
:conflicts="$domainConflicts"
|
||||
:showModal="$showDomainConflictModal"
|
||||
confirmAction="confirmDomainUsage">
|
||||
<x-slot:consequences>
|
||||
<ul class="mt-2 ml-4 list-disc">
|
||||
<li>The Coolify instance domain will conflict with existing resources</li>
|
||||
<li>SSL certificates might not work correctly</li>
|
||||
<li>Routing behavior will be unpredictable</li>
|
||||
<li>You may not be able to access the Coolify dashboard properly</li>
|
||||
</ul>
|
||||
</x-slot:consequences>
|
||||
</x-domain-conflict-modal>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.424"
|
||||
"version": "4.0.0-beta.425"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.425"
|
||||
"version": "4.0.0-beta.426"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.10"
|
||||
|
|
|
|||
Loading…
Reference in a new issue