mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-22 06:23:28 +00:00
Merge remote-tracking branch 'origin/next' into api-sensitive-data-scrubber
This commit is contained in:
@@ -215,6 +215,7 @@ class ApplicationsController extends Controller
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
@@ -382,6 +383,7 @@ class ApplicationsController extends Controller
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
@@ -548,6 +550,7 @@ class ApplicationsController extends Controller
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'static_image' => ['type' => 'string', 'enum' => ['nginx:alpine'], 'description' => 'The static image.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
@@ -741,6 +744,7 @@ class ApplicationsController extends Controller
|
||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
@@ -875,6 +879,7 @@ class ApplicationsController extends Controller
|
||||
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
|
||||
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
|
||||
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||
@@ -959,7 +964,7 @@ class ApplicationsController extends Controller
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', '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', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', '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', 'dockerfile_location', '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', 'autogenerate_domain', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled'];
|
||||
$allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', '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', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', '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', 'dockerfile_location', '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', 'autogenerate_domain', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled'];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
@@ -997,6 +1002,10 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$serverUuid = $request->server_uuid;
|
||||
$fqdn = $request->domains;
|
||||
if ($request->has('domains') && is_string($request->domains)) {
|
||||
$fqdn = ValidationPatterns::normalizeApplicationDomains($request->domains);
|
||||
$request->offsetSet('domains', $fqdn);
|
||||
}
|
||||
$autogenerateDomain = $request->boolean('autogenerate_domain', true);
|
||||
$instantDeploy = $request->instant_deploy;
|
||||
$githubAppUuid = $request->github_app_uuid;
|
||||
@@ -1005,6 +1014,7 @@ class ApplicationsController extends Controller
|
||||
$isSpa = $request->is_spa;
|
||||
$isAutoDeployEnabled = $request->is_auto_deploy_enabled;
|
||||
$isForceHttpsEnabled = $request->is_force_https_enabled;
|
||||
$isPreviewDeploymentsEnabled = $request->is_preview_deployments_enabled;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$customNginxConfiguration = $request->custom_nginx_configuration;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true);
|
||||
@@ -1076,7 +1086,7 @@ class ApplicationsController extends Controller
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain',
|
||||
'docker_compose_domains.*.name' => 'string|required',
|
||||
'docker_compose_domains.*.domain' => 'string|nullable',
|
||||
'docker_compose_domains.*.domain' => ValidationPatterns::applicationDomainRules(),
|
||||
];
|
||||
// ports_exposes is not required for dockercompose
|
||||
if ($request->build_pack === 'dockercompose') {
|
||||
@@ -1132,7 +1142,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
if (! isValidDomainUrl($url)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
@@ -1182,15 +1192,15 @@ class ApplicationsController extends Controller
|
||||
$request->offsetUnset('docker_compose_domains');
|
||||
}
|
||||
if ($dockerComposeDomainsJson->count() > 0) {
|
||||
$application->docker_compose_domains = $dockerComposeDomainsJson;
|
||||
$application->docker_compose_domains = json_encode($dockerComposeDomainsJson);
|
||||
}
|
||||
$repository_url_parsed = Url::fromString($request->git_repository);
|
||||
$git_host = $repository_url_parsed->getHost();
|
||||
if ($git_host === 'github.com') {
|
||||
$application->source_type = GithubApp::class;
|
||||
$application->source_id = GithubApp::find(0)->id;
|
||||
$application->git_repository = str($repository_url_parsed->getSegment(1).'/'.$repository_url_parsed->getSegment(2))->trim()->toString();
|
||||
}
|
||||
$application->git_repository = str($repository_url_parsed->getSegment(1).'/'.$repository_url_parsed->getSegment(2))->trim()->toString();
|
||||
$application->fqdn = $fqdn;
|
||||
$application->destination_id = $destination->id;
|
||||
$application->destination_type = $destination->getMorphClass();
|
||||
@@ -1212,6 +1222,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -1284,7 +1298,7 @@ class ApplicationsController extends Controller
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain',
|
||||
'docker_compose_domains.*.name' => 'string|required',
|
||||
'docker_compose_domains.*.domain' => 'string|nullable',
|
||||
'docker_compose_domains.*.domain' => ValidationPatterns::applicationDomainRules(),
|
||||
];
|
||||
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||
$validationMessages = [
|
||||
@@ -1373,7 +1387,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
if (! isValidDomainUrl($url)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
@@ -1423,7 +1437,7 @@ class ApplicationsController extends Controller
|
||||
$request->offsetUnset('docker_compose_domains');
|
||||
}
|
||||
if ($dockerComposeDomainsJson->count() > 0) {
|
||||
$application->docker_compose_domains = $dockerComposeDomainsJson;
|
||||
$application->docker_compose_domains = json_encode($dockerComposeDomainsJson);
|
||||
}
|
||||
$application->fqdn = $fqdn;
|
||||
$application->git_repository = str($gitRepository)->trim()->toString();
|
||||
@@ -1457,6 +1471,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -1524,7 +1542,7 @@ class ApplicationsController extends Controller
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain',
|
||||
'docker_compose_domains.*.name' => 'string|required',
|
||||
'docker_compose_domains.*.domain' => 'string|nullable',
|
||||
'docker_compose_domains.*.domain' => ValidationPatterns::applicationDomainRules(),
|
||||
];
|
||||
|
||||
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||
@@ -1586,7 +1604,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
if (! isValidDomainUrl($url)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
@@ -1636,7 +1654,7 @@ class ApplicationsController extends Controller
|
||||
$request->offsetUnset('docker_compose_domains');
|
||||
}
|
||||
if ($dockerComposeDomainsJson->count() > 0) {
|
||||
$application->docker_compose_domains = $dockerComposeDomainsJson;
|
||||
$application->docker_compose_domains = json_encode($dockerComposeDomainsJson);
|
||||
}
|
||||
$application->fqdn = $fqdn;
|
||||
$application->private_key_id = $privateKey->id;
|
||||
@@ -1666,6 +1684,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -1790,6 +1812,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -1909,6 +1935,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_force_https_enabled = $isForceHttpsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -2058,6 +2088,13 @@ class ApplicationsController extends Controller
|
||||
default: 100,
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'show_timestamps',
|
||||
in: 'query',
|
||||
description: 'Show timestamps in the logs.',
|
||||
required: false,
|
||||
schema: new OA\Schema(type: 'boolean', default: false),
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
@@ -2121,8 +2158,9 @@ class ApplicationsController extends Controller
|
||||
], 400);
|
||||
}
|
||||
|
||||
$lines = $request->query->get('lines', 100) ?: 100;
|
||||
$logs = getContainerLogs($application->destination->server, $container['ID'], $lines);
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($application->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
@@ -2269,6 +2307,7 @@ class ApplicationsController extends Controller
|
||||
'is_spa' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is a single-page application (SPA). Only relevant when is_static is true.'],
|
||||
'is_auto_deploy_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if auto-deploy is enabled on git push. Defaults to true.'],
|
||||
'is_force_https_enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if HTTPS is forced. Defaults to true.'],
|
||||
'is_preview_deployments_enabled' => ['type' => 'boolean', 'description' => 'Enable preview deployments for pull requests.'],
|
||||
'install_command' => ['type' => 'string', 'description' => 'The install command.'],
|
||||
'build_command' => ['type' => 'string', 'description' => 'The build command.'],
|
||||
'start_command' => ['type' => 'string', 'description' => 'The start command.'],
|
||||
@@ -2328,6 +2367,7 @@ class ApplicationsController extends Controller
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'default' => true, 'description' => 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'],
|
||||
'is_preserve_repository_enabled' => ['type' => 'boolean', 'description' => 'Preserve git repository during application update. If false, the existing repository will be removed and replaced with the new one. If true, the existing repository will be kept and the new one will be ignored. Default is false.'],
|
||||
'include_source_commit_in_build' => ['type' => 'boolean', 'description' => 'Include source commit information in the build. Default is false.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -2413,7 +2453,7 @@ class ApplicationsController extends Controller
|
||||
$this->authorize('update', $application);
|
||||
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', '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', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', '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', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', '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', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled'];
|
||||
$allowedFields = ['name', 'description', 'is_static', 'is_spa', 'is_auto_deploy_enabled', 'is_force_https_enabled', 'is_preview_deployments_enabled', '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', 'custom_network_aliases', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_type', 'health_check_command', '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', 'dockerfile_location', 'dockerfile_target_build', 'docker_compose_location', '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', 'is_container_label_escape_enabled', 'is_preserve_repository_enabled', 'include_source_commit_in_build'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
@@ -2423,11 +2463,13 @@ class ApplicationsController extends Controller
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain',
|
||||
'docker_compose_domains.*.name' => 'string|required',
|
||||
'docker_compose_domains.*.domain' => 'string|nullable',
|
||||
'docker_compose_domains.*.domain' => ValidationPatterns::applicationDomainRules(),
|
||||
'custom_nginx_configuration' => 'string|nullable',
|
||||
'is_http_basic_auth_enabled' => 'boolean|nullable',
|
||||
'is_preview_deployments_enabled' => 'boolean|nullable',
|
||||
'http_basic_auth_username' => 'string',
|
||||
'http_basic_auth_password' => 'string',
|
||||
'include_source_commit_in_build' => 'boolean',
|
||||
];
|
||||
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||
$validationMessages = [
|
||||
@@ -2527,29 +2569,7 @@ class ApplicationsController extends Controller
|
||||
$requestHasDomains = $request->has('domains');
|
||||
if ($requestHasDomains && $server->isProxyShouldRun()) {
|
||||
$uuid = $request->uuid;
|
||||
$urls = $request->domains;
|
||||
$urls = str($urls)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
$errors = [];
|
||||
$urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) {
|
||||
$url = trim($url);
|
||||
|
||||
// If "domains" is empty clear all URLs from the fqdn column
|
||||
if (blank($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = 'Invalid URL: '.$url;
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return str($url)->lower();
|
||||
});
|
||||
$errors = ValidationPatterns::validateApplicationDomains($request->domains);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
@@ -2557,6 +2577,9 @@ class ApplicationsController extends Controller
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
$domains = ValidationPatterns::normalizeApplicationDomains($request->domains);
|
||||
$request->offsetSet('domains', $domains);
|
||||
$urls = collect(ValidationPatterns::applicationDomainList($domains));
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
@@ -2601,7 +2624,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$errors = [];
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
if (! isValidDomainUrl($url)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
@@ -2660,10 +2683,12 @@ class ApplicationsController extends Controller
|
||||
$isSpa = $request->is_spa;
|
||||
$isAutoDeployEnabled = $request->is_auto_deploy_enabled;
|
||||
$isForceHttpsEnabled = $request->is_force_https_enabled;
|
||||
$isPreviewDeploymentsEnabled = $request->is_preview_deployments_enabled;
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$useBuildServer = $request->use_build_server;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled');
|
||||
$isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled');
|
||||
$includeSourceCommitInBuild = $request->boolean('include_source_commit_in_build');
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
@@ -2689,6 +2714,11 @@ class ApplicationsController extends Controller
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($isPreviewDeploymentsEnabled)) {
|
||||
$application->settings->is_preview_deployments_enabled = $isPreviewDeploymentsEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if (isset($connectToDockerNetwork)) {
|
||||
$application->settings->connect_to_docker_network = $connectToDockerNetwork;
|
||||
$application->settings->save();
|
||||
@@ -2702,6 +2732,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($request->has('include_source_commit_in_build')) {
|
||||
$application->settings->include_source_commit_in_build = $includeSourceCommitInBuild;
|
||||
$application->settings->save();
|
||||
}
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$data = $request->only($allowedFields);
|
||||
@@ -3669,7 +3703,7 @@ class ApplicationsController extends Controller
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'force_rebuild' => $force,
|
||||
'instant_deploy' => $instant_deploy,
|
||||
]);
|
||||
@@ -3677,7 +3711,7 @@ class ApplicationsController extends Controller
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Deployment request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
],
|
||||
200
|
||||
);
|
||||
@@ -3863,13 +3897,13 @@ class ApplicationsController extends Controller
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Restart request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -3916,36 +3950,16 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||
$uuid = $request->uuid;
|
||||
$urls = $request->domains;
|
||||
$urls = str($urls)->replaceEnd(',', '')->trim();
|
||||
$urls = str($urls)->replaceStart(',', '')->trim();
|
||||
$errors = [];
|
||||
$urls = str($urls)->trim()->explode(',')->map(function ($url) use (&$errors) {
|
||||
$url = trim($url);
|
||||
|
||||
// If "domains" is empty clear all URLs from the fqdn column
|
||||
if (blank($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = 'Invalid URL: '.$url;
|
||||
|
||||
return str($url)->lower();
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return str($url)->lower();
|
||||
});
|
||||
$errors = ValidationPatterns::validateApplicationDomains($request->domains);
|
||||
if (count($errors) > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
$normalizedDomains = ValidationPatterns::normalizeApplicationDomains($request->domains);
|
||||
$request->offsetSet('domains', $normalizedDomains);
|
||||
$urls = collect(ValidationPatterns::applicationDomainList($normalizedDomains));
|
||||
// Check for domain conflicts
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($urls, $teamId, $uuid);
|
||||
if (isset($result['error'])) {
|
||||
|
||||
@@ -2335,6 +2335,116 @@ class DatabasesController extends Controller
|
||||
return response()->json(['message' => 'Invalid database type requested.'], 400);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get database logs.',
|
||||
description: 'Get database logs by UUID.',
|
||||
path: '/databases/{uuid}/logs',
|
||||
operationId: 'get-database-logs-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the database.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'lines',
|
||||
in: 'query',
|
||||
description: 'Number of lines to show from the end of the logs.',
|
||||
required: false,
|
||||
schema: new OA\Schema(
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
default: 100,
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'show_timestamps',
|
||||
in: 'query',
|
||||
description: 'Show timestamps in the logs.',
|
||||
required: false,
|
||||
schema: new OA\Schema(type: 'boolean', default: false),
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get database logs by UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'logs' => ['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 logs_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
if (! $database) {
|
||||
return response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
|
||||
$containers = getCurrentDatabaseContainerStatus($database->destination->server, $database->id);
|
||||
|
||||
if ($containers->count() == 0) {
|
||||
return response()->json([
|
||||
'message' => 'Database is not running.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$container = $containers->first();
|
||||
|
||||
$status = getContainerStatus($database->destination->server, $container['Names']);
|
||||
if ($status !== 'running') {
|
||||
return response()->json([
|
||||
'message' => 'Database is not running.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($database->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
]);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
summary: 'Delete',
|
||||
description: 'Delete database by UUID.',
|
||||
|
||||
@@ -369,7 +369,7 @@ class DeployController extends Controller
|
||||
|
||||
$uuids = $request->input('uuid');
|
||||
$tags = $request->input('tag');
|
||||
$force = $request->input('force') ?? false;
|
||||
$force = $request->boolean('force');
|
||||
$pullRequestId = $request->input('pull_request_id', $request->input('pr'));
|
||||
$pr = $pullRequestId ? max((int) $pullRequestId, 0) : 0;
|
||||
$dockerTag = $request->string('docker_tag')->trim()->value() ?: null;
|
||||
@@ -429,7 +429,7 @@ class DeployController extends Controller
|
||||
}
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid]);
|
||||
} else {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||
}
|
||||
@@ -475,7 +475,7 @@ class DeployController extends Controller
|
||||
}
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $result;
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid]);
|
||||
}
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
@@ -533,7 +533,7 @@ class DeployController extends Controller
|
||||
'resource_type' => 'application',
|
||||
'application_uuid' => $resource->uuid,
|
||||
'application_name' => $resource->name,
|
||||
'deployment_uuid' => $deployment_uuid?->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'force_rebuild' => $force,
|
||||
'pull_request_id' => $pr,
|
||||
]);
|
||||
|
||||
@@ -136,7 +136,7 @@ class GithubController extends Controller
|
||||
'private_key_uuid' => ['type' => 'string', 'description' => 'UUID of an existing private key for GitHub App authentication.'],
|
||||
'is_system_wide' => ['type' => 'boolean', 'description' => 'Is this app system-wide (cloud only).'],
|
||||
],
|
||||
required: ['name', 'api_url', 'html_url', 'app_id', 'installation_id', 'client_id', 'client_secret', 'private_key_uuid'],
|
||||
required: ['name', 'html_url', 'app_id', 'installation_id', 'client_id', 'client_secret', 'private_key_uuid'],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -212,10 +212,14 @@ class GithubController extends Controller
|
||||
'is_system_wide',
|
||||
];
|
||||
|
||||
$request->merge([
|
||||
'organization' => normalizeGithubOrganization($request->input('organization')),
|
||||
]);
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
'organization' => 'nullable|string|max:255',
|
||||
'api_url' => ['required', 'string', 'url', new SafeExternalUrl],
|
||||
'organization' => ['nullable', 'string', 'max:255', 'regex:/\A[^\s\/?#]+\z/'],
|
||||
'api_url' => ['nullable', 'string', 'url', new SafeExternalUrl],
|
||||
'html_url' => ['required', 'string', 'url', new SafeExternalUrl],
|
||||
'custom_user' => 'nullable|string|max:255',
|
||||
'custom_port' => 'nullable|integer|min:1|max:65535',
|
||||
@@ -259,7 +263,9 @@ class GithubController extends Controller
|
||||
'uuid' => Str::uuid(),
|
||||
'name' => $request->input('name'),
|
||||
'organization' => $request->input('organization'),
|
||||
'api_url' => $request->input('api_url'),
|
||||
'api_url' => filled($request->input('api_url'))
|
||||
? $request->input('api_url')
|
||||
: githubApiUrlFromHtmlUrl($request->input('html_url')),
|
||||
'html_url' => $request->input('html_url'),
|
||||
'custom_user' => $request->input('custom_user', 'git'),
|
||||
'custom_port' => $request->input('custom_port', 22),
|
||||
@@ -596,13 +602,17 @@ class GithubController extends Controller
|
||||
|
||||
$payload = $request->only($allowedFields);
|
||||
|
||||
if (array_key_exists('organization', $payload)) {
|
||||
$payload['organization'] = normalizeGithubOrganization($payload['organization']);
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
$rules = [];
|
||||
if (isset($payload['name'])) {
|
||||
$rules['name'] = 'string';
|
||||
}
|
||||
if (isset($payload['organization'])) {
|
||||
$rules['organization'] = 'nullable|string';
|
||||
$rules['organization'] = ['nullable', 'string', 'regex:/\A[^\s\/?#]+\z/'];
|
||||
}
|
||||
if (isset($payload['api_url'])) {
|
||||
$rules['api_url'] = ['url', new SafeExternalUrl];
|
||||
@@ -646,6 +656,13 @@ class GithubController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (array_key_exists('organization', $payload)) {
|
||||
$payload['organization'] = normalizeGithubOrganization($payload['organization']);
|
||||
}
|
||||
if (isset($payload['html_url']) && ! filled($payload['api_url'] ?? null)) {
|
||||
$payload['api_url'] = githubApiUrlFromHtmlUrl($payload['html_url']);
|
||||
}
|
||||
|
||||
// Handle private_key_uuid -> private_key_id conversion
|
||||
if (isset($payload['private_key_uuid'])) {
|
||||
$privateKey = PrivateKey::where('team_id', $teamId)
|
||||
|
||||
@@ -112,19 +112,10 @@ class ServicesController extends Controller
|
||||
return str($urlValue)->replaceStart(',', '')->replaceEnd(',', '')->trim()->explode(',')->map(fn ($url) => trim($url))->filter();
|
||||
});
|
||||
|
||||
$urls = $urls->map(function ($url) use (&$errors) {
|
||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
$errors[] = "Invalid URL: {$url}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME) ?? '';
|
||||
if (! in_array(strtolower($scheme), ['http', 'https'])) {
|
||||
$errors[] = "Invalid URL scheme: {$scheme} for URL: {$url}. Only http and https are supported.";
|
||||
}
|
||||
|
||||
return $url;
|
||||
});
|
||||
$errors = ValidationPatterns::validateApplicationDomains($urls->implode(','));
|
||||
$urls = collect(ValidationPatterns::applicationDomainList(
|
||||
ValidationPatterns::normalizeApplicationDomains($urls->implode(','))
|
||||
));
|
||||
|
||||
$duplicates = $urls->duplicates()->unique()->values();
|
||||
if ($duplicates->isNotEmpty() && ! $forceDomainOverride) {
|
||||
@@ -153,10 +144,10 @@ class ServicesController extends Controller
|
||||
}
|
||||
|
||||
if (filled($containerUrls)) {
|
||||
$containerUrls = str($containerUrls)->replaceStart(',', '')->replaceEnd(',', '')->trim();
|
||||
$containerUrls = str($containerUrls)->explode(',')->map(fn ($url) => str(trim($url))->lower());
|
||||
$containerUrls = ValidationPatterns::normalizeApplicationDomains($containerUrls);
|
||||
$containerUrlCollection = collect(ValidationPatterns::applicationDomainList($containerUrls));
|
||||
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($containerUrls, $teamId, $application->uuid);
|
||||
$result = checkIfDomainIsAlreadyUsedViaAPI($containerUrlCollection, $teamId, $application->uuid);
|
||||
if (isset($result['error'])) {
|
||||
$errors[] = $result['error'];
|
||||
|
||||
@@ -168,8 +159,6 @@ class ServicesController extends Controller
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$containerUrls = $containerUrls->filter(fn ($u) => filled($u))->unique()->implode(',');
|
||||
} else {
|
||||
$containerUrls = null;
|
||||
}
|
||||
@@ -803,6 +792,125 @@ class ServicesController extends Controller
|
||||
return response()->json($this->removeSensitiveData($service));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get service logs.',
|
||||
description: 'Get logs for a specific service sub-resource by service UUID. The `sub_service_name` query parameter must match the `name` field of one of the service applications or databases returned by `GET /services/{uuid}`.',
|
||||
path: '/services/{uuid}/logs',
|
||||
operationId: 'get-service-logs-by-uuid',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'sub_service_name',
|
||||
in: 'query',
|
||||
description: 'Sub-service name from `GET /services/{uuid}` under `applications[].name` or `databases[].name`. Do not use `human_name` or the Docker container name with the service UUID suffix.',
|
||||
required: true,
|
||||
schema: new OA\Schema(type: 'string', example: 'appwrite-console'),
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'lines',
|
||||
in: 'query',
|
||||
description: 'Number of lines to show from the end of the logs.',
|
||||
required: false,
|
||||
schema: new OA\Schema(
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
default: 100,
|
||||
)
|
||||
),
|
||||
new OA\Parameter(
|
||||
name: 'show_timestamps',
|
||||
in: 'query',
|
||||
description: 'Show timestamps in the logs.',
|
||||
required: false,
|
||||
schema: new OA\Schema(type: 'boolean', default: false),
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get service logs by UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'logs' => ['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 logs_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$subServiceName = $request->query->get('sub_service_name');
|
||||
if (! $subServiceName) {
|
||||
return response()->json(['message' => 'Sub service name is required.'], 400);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$name = "{$subServiceName}-{$service->uuid}";
|
||||
$containers = getCurrentServiceSubContainerStatus($service->destination->server, $service->id, $name);
|
||||
$container = $containers->first();
|
||||
|
||||
if (! $container) {
|
||||
return response()->json(['message' => 'Container not found.'], 404);
|
||||
}
|
||||
|
||||
$status = getContainerStatus($service->destination->server, $container['Names']);
|
||||
if ($status !== 'running') {
|
||||
return response()->json([
|
||||
'message' => 'Container is not running.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$lines = normalizeLogLines($request->query('lines'));
|
||||
$showTimestamps = parseLogTimestampFlag($request->query('show_timestamps'));
|
||||
$logs = getContainerLogs($service->destination->server, $container['ID'], $lines, $showTimestamps);
|
||||
|
||||
return response()->json([
|
||||
'logs' => $logs,
|
||||
]);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
summary: 'Delete',
|
||||
description: 'Delete service by UUID.',
|
||||
|
||||
@@ -98,7 +98,7 @@ class Controller extends BaseController
|
||||
public function link()
|
||||
{
|
||||
$token = request()->get('token');
|
||||
if ($token) {
|
||||
if (is_string($token) && $token !== '') {
|
||||
try {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
} catch (DecryptException) {
|
||||
@@ -126,9 +126,8 @@ class Controller extends BaseController
|
||||
$invitation = TeamInvitation::query()
|
||||
->where('email', $email)
|
||||
->when($invitationUuid, fn ($query) => $query->where('uuid', $invitationUuid))
|
||||
->where('link', request()->fullUrl())
|
||||
->first();
|
||||
if (! $invitation || ! $invitation->isValid()) {
|
||||
if (! $invitation || ! $this->invitationLinkMatchesToken($invitation, $token) || ! $invitation->isValid()) {
|
||||
return redirect()->route('login')->with('error', 'Invitation has expired or been revoked.');
|
||||
}
|
||||
|
||||
@@ -139,10 +138,11 @@ class Controller extends BaseController
|
||||
}
|
||||
$invitation->delete();
|
||||
|
||||
Auth::login($user);
|
||||
$user->forceFill([
|
||||
'password' => Hash::make(Str::random(64)),
|
||||
])->save();
|
||||
|
||||
Auth::login($user);
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
@@ -152,6 +152,19 @@ class Controller extends BaseController
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
|
||||
private function invitationLinkMatchesToken(TeamInvitation $invitation, string $token): bool
|
||||
{
|
||||
$query = parse_url($invitation->link, PHP_URL_QUERY);
|
||||
if (! is_string($query)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parse_str($query, $parameters);
|
||||
$storedToken = $parameters['token'] ?? null;
|
||||
|
||||
return is_string($storedToken) && hash_equals($storedToken, $token);
|
||||
}
|
||||
|
||||
public function showInvitation()
|
||||
{
|
||||
$invitationUuid = request()->route('uuid');
|
||||
|
||||
@@ -162,7 +162,7 @@ class Bitbucket extends Controller
|
||||
'mode' => 'manual',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'commit' => $commit,
|
||||
'repository' => $full_name ?? null,
|
||||
]);
|
||||
|
||||
@@ -148,7 +148,7 @@ class Gitea extends Controller
|
||||
'mode' => 'manual',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'commit' => data_get($payload, 'after'),
|
||||
'repository' => $full_name ?? null,
|
||||
]);
|
||||
|
||||
@@ -190,7 +190,7 @@ class Gitlab extends Controller
|
||||
'mode' => 'manual',
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'commit' => data_get($payload, 'after'),
|
||||
'repository' => $full_name ?? null,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user