mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 22:14:53 +00:00
Merge branch 'next' into next
This commit is contained in:
173 files changed
+4002
-611
No files matched your search
@@ -37,12 +37,13 @@ class CreateNewUser implements CreatesNewUsers
|
||||
if (User::count() == 0) {
|
||||
// If this is the first user, make them the root user
|
||||
// Team is already created in the database/seeders/ProductionSeeder.php
|
||||
$user = User::create([
|
||||
$user = (new User)->forceFill([
|
||||
'id' => 0,
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$user->save();
|
||||
$team = $user->teams()->first();
|
||||
|
||||
// Disable registration after first user is created
|
||||
|
||||
@@ -49,7 +49,7 @@ class InstallDocker
|
||||
}');
|
||||
$found = StandaloneDocker::where('server_id', $server->id);
|
||||
if ($found->count() == 0 && $server->id) {
|
||||
StandaloneDocker::create([
|
||||
StandaloneDocker::forceCreate([
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => $server->id,
|
||||
|
||||
@@ -40,10 +40,10 @@ class StartService
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||
if (data_get($service, 'connect_to_docker_network')) {
|
||||
$compose = data_get($service, 'docker_compose', []);
|
||||
$network = $service->destination->network;
|
||||
$safeNetwork = escapeshellarg($service->destination->network);
|
||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} {$safeNetwork} {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class Emails extends Command
|
||||
$application = Application::all()->first();
|
||||
$preview = ApplicationPreview::all()->first();
|
||||
if (! $preview) {
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 1,
|
||||
'pull_request_html_url' => 'http://example.com',
|
||||
|
||||
@@ -212,18 +212,19 @@ class Init extends Command
|
||||
$removeNetworks = $allNetworks->diff($networks);
|
||||
$commands = collect();
|
||||
foreach ($removeNetworks as $network) {
|
||||
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||
$safe = escapeshellarg($network);
|
||||
$out = instant_remote_process(["docker network inspect -f json {$safe} | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||
if (empty($out)) {
|
||||
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network disconnect {$safe} coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm {$safe} >/dev/null 2>&1 || true");
|
||||
} else {
|
||||
$data = collect(json_decode($out, true));
|
||||
if ($data->count() === 1) {
|
||||
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||
if ($isCoolifyProxyItself) {
|
||||
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network disconnect {$safe} coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm {$safe} >/dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@ class ApplicationsController extends Controller
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'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', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -395,6 +396,7 @@ class ApplicationsController extends Controller
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'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', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -560,6 +562,7 @@ class ApplicationsController extends Controller
|
||||
'force_domain_override' => ['type' => 'boolean', 'description' => 'Force domain usage even if conflicts are detected. Default is false.'],
|
||||
'autogenerate_domain' => ['type' => 'boolean', 'default' => true, 'description' => 'If true and domains is empty, auto-generate a domain using the server\'s wildcard domain or sslip.io fallback. Default: true.'],
|
||||
'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', 'default' => false, 'description' => 'Preserve repository during deployment.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -1006,7 +1009,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'];
|
||||
$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'];
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
@@ -1055,6 +1058,7 @@ class ApplicationsController extends Controller
|
||||
$connectToDockerNetwork = $request->connect_to_docker_network;
|
||||
$customNginxConfiguration = $request->custom_nginx_configuration;
|
||||
$isContainerLabelEscapeEnabled = $request->boolean('is_container_label_escape_enabled', true);
|
||||
$isPreserveRepositoryEnabled = $request->boolean('is_preserve_repository_enabled',false);
|
||||
|
||||
if (! is_null($customNginxConfiguration)) {
|
||||
if (! isBase64Encoded($customNginxConfiguration)) {
|
||||
@@ -1158,7 +1162,7 @@ class ApplicationsController extends Controller
|
||||
$application = new Application;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$application->fill($request->all());
|
||||
$application->fill($request->only($allowedFields));
|
||||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
$dockerComposeDomains = collect($request->docker_compose_domains);
|
||||
@@ -1267,6 +1271,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreserveRepositoryEnabled)) {
|
||||
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
$application->refresh();
|
||||
// Auto-generate domain if requested and no custom domain provided
|
||||
if ($autogenerateDomain && blank($fqdn)) {
|
||||
@@ -1385,7 +1393,7 @@ class ApplicationsController extends Controller
|
||||
$application = new Application;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$application->fill($request->all());
|
||||
$application->fill($request->only($allowedFields));
|
||||
|
||||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
@@ -1499,6 +1507,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreserveRepositoryEnabled)) {
|
||||
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
@@ -1585,7 +1597,7 @@ class ApplicationsController extends Controller
|
||||
$application = new Application;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$application->fill($request->all());
|
||||
$application->fill($request->only($allowedFields));
|
||||
|
||||
$dockerComposeDomainsJson = collect();
|
||||
if ($request->has('docker_compose_domains')) {
|
||||
@@ -1695,6 +1707,10 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if (isset($isPreserveRepositoryEnabled)) {
|
||||
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
if ($application->settings->is_container_label_readonly_enabled) {
|
||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||
$application->save();
|
||||
@@ -1772,7 +1788,7 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
|
||||
$application = new Application;
|
||||
$application->fill($request->all());
|
||||
$application->fill($request->only($allowedFields));
|
||||
$application->fqdn = $fqdn;
|
||||
$application->ports_exposes = $port;
|
||||
$application->build_pack = 'dockerfile';
|
||||
@@ -1884,7 +1900,7 @@ class ApplicationsController extends Controller
|
||||
$application = new Application;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$application->fill($request->all());
|
||||
$application->fill($request->only($allowedFields));
|
||||
$application->fqdn = $fqdn;
|
||||
$application->build_pack = 'dockerimage';
|
||||
$application->destination_id = $destination->id;
|
||||
@@ -2000,7 +2016,7 @@ class ApplicationsController extends Controller
|
||||
|
||||
$service = new Service;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$service->fill($request->all());
|
||||
$service->fill($request->only($allowedFields));
|
||||
|
||||
$service->docker_compose_raw = $dockerComposeRaw;
|
||||
$service->environment_id = $environment->id;
|
||||
@@ -2390,6 +2406,7 @@ class ApplicationsController extends Controller
|
||||
'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.'],
|
||||
'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.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
@@ -2475,7 +2492,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'];
|
||||
$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'];
|
||||
|
||||
$validationRules = [
|
||||
'name' => 'string|max:255',
|
||||
@@ -2722,7 +2739,7 @@ class ApplicationsController extends Controller
|
||||
$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');
|
||||
if (isset($useBuildServer)) {
|
||||
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||
$application->settings->save();
|
||||
@@ -2757,10 +2774,13 @@ class ApplicationsController extends Controller
|
||||
$application->settings->is_container_label_escape_enabled = $isContainerLabelEscapeEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
|
||||
if ($request->has('is_preserve_repository_enabled')) {
|
||||
$application->settings->is_preserve_repository_enabled = $isPreserveRepositoryEnabled;
|
||||
$application->settings->save();
|
||||
}
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$data = $request->all();
|
||||
$data = $request->only($allowedFields);
|
||||
if ($requestHasDomains && $server->isProxyShouldRun()) {
|
||||
data_set($data, 'fqdn', $domains);
|
||||
}
|
||||
|
||||
@@ -643,6 +643,7 @@ class DatabasesController extends Controller
|
||||
'database_backup_retention_amount_s3' => ['type' => 'integer', 'description' => 'Number of backups to retain in S3'],
|
||||
'database_backup_retention_days_s3' => ['type' => 'integer', 'description' => 'Number of days to retain backups in S3'],
|
||||
'database_backup_retention_max_storage_s3' => ['type' => 'integer', 'description' => 'Max storage (MB) for S3 backups'],
|
||||
'timeout' => ['type' => 'integer', 'description' => 'Backup job timeout in seconds (min: 60, max: 36000)', 'default' => 3600],
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -679,7 +680,7 @@ class DatabasesController extends Controller
|
||||
)]
|
||||
public function create_backup(Request $request)
|
||||
{
|
||||
$backupConfigFields = ['save_s3', 'enabled', 'dump_all', 'frequency', 'databases_to_backup', 'database_backup_retention_amount_locally', 'database_backup_retention_days_locally', 'database_backup_retention_max_storage_locally', 'database_backup_retention_amount_s3', 'database_backup_retention_days_s3', 'database_backup_retention_max_storage_s3', 's3_storage_uuid'];
|
||||
$backupConfigFields = ['save_s3', 'enabled', 'dump_all', 'frequency', 'databases_to_backup', 'database_backup_retention_amount_locally', 'database_backup_retention_days_locally', 'database_backup_retention_max_storage_locally', 'database_backup_retention_amount_s3', 'database_backup_retention_days_s3', 'database_backup_retention_max_storage_s3', 's3_storage_uuid', 'timeout'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -706,6 +707,7 @@ class DatabasesController extends Controller
|
||||
'database_backup_retention_amount_s3' => 'integer|min:0',
|
||||
'database_backup_retention_days_s3' => 'integer|min:0',
|
||||
'database_backup_retention_max_storage_s3' => 'integer|min:0',
|
||||
'timeout' => 'integer|min:60|max:36000',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@@ -880,6 +882,7 @@ class DatabasesController extends Controller
|
||||
'database_backup_retention_amount_s3' => ['type' => 'integer', 'description' => 'Retention amount of the backup in s3'],
|
||||
'database_backup_retention_days_s3' => ['type' => 'integer', 'description' => 'Retention days of the backup in s3'],
|
||||
'database_backup_retention_max_storage_s3' => ['type' => 'integer', 'description' => 'Max storage of the backup in S3'],
|
||||
'timeout' => ['type' => 'integer', 'description' => 'Backup job timeout in seconds (min: 60, max: 36000)', 'default' => 3600],
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -909,7 +912,7 @@ class DatabasesController extends Controller
|
||||
)]
|
||||
public function update_backup(Request $request)
|
||||
{
|
||||
$backupConfigFields = ['save_s3', 'enabled', 'dump_all', 'frequency', 'databases_to_backup', 'database_backup_retention_amount_locally', 'database_backup_retention_days_locally', 'database_backup_retention_max_storage_locally', 'database_backup_retention_amount_s3', 'database_backup_retention_days_s3', 'database_backup_retention_max_storage_s3', 's3_storage_uuid'];
|
||||
$backupConfigFields = ['save_s3', 'enabled', 'dump_all', 'frequency', 'databases_to_backup', 'database_backup_retention_amount_locally', 'database_backup_retention_days_locally', 'database_backup_retention_max_storage_locally', 'database_backup_retention_amount_s3', 'database_backup_retention_days_s3', 'database_backup_retention_max_storage_s3', 's3_storage_uuid', 'timeout'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -927,13 +930,14 @@ class DatabasesController extends Controller
|
||||
'dump_all' => 'boolean',
|
||||
's3_storage_uuid' => 'string|exists:s3_storages,uuid|nullable',
|
||||
'databases_to_backup' => 'string|nullable',
|
||||
'frequency' => 'string|in:every_minute,hourly,daily,weekly,monthly,yearly',
|
||||
'frequency' => 'string',
|
||||
'database_backup_retention_amount_locally' => 'integer|min:0',
|
||||
'database_backup_retention_days_locally' => 'integer|min:0',
|
||||
'database_backup_retention_max_storage_locally' => 'integer|min:0',
|
||||
'database_backup_retention_amount_s3' => 'integer|min:0',
|
||||
'database_backup_retention_days_s3' => 'integer|min:0',
|
||||
'database_backup_retention_max_storage_s3' => 'integer|min:0',
|
||||
'timeout' => 'integer|min:60|max:36000',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
@@ -960,6 +964,17 @@ class DatabasesController extends Controller
|
||||
|
||||
$this->authorize('update', $database);
|
||||
|
||||
// Validate frequency is a valid cron expression
|
||||
if ($request->filled('frequency')) {
|
||||
$isValid = validate_cron_expression($request->frequency);
|
||||
if (! $isValid) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['frequency' => ['Invalid cron expression or frequency format.']],
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->boolean('save_s3') && ! $request->filled('s3_storage_uuid')) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
@@ -1751,7 +1766,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('postgres_conf', $postgresConf);
|
||||
}
|
||||
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -1806,7 +1821,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('mariadb_conf', $mariadbConf);
|
||||
}
|
||||
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -1865,7 +1880,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('mysql_conf', $mysqlConf);
|
||||
}
|
||||
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -1921,7 +1936,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('redis_conf', $redisConf);
|
||||
}
|
||||
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_redis($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -1958,7 +1973,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -2007,7 +2022,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('keydb_conf', $keydbConf);
|
||||
}
|
||||
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -2043,7 +2058,7 @@ class DatabasesController extends Controller
|
||||
], 422);
|
||||
}
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
@@ -2101,7 +2116,7 @@ class DatabasesController extends Controller
|
||||
}
|
||||
$request->offsetSet('mongo_conf', $mongoConf);
|
||||
}
|
||||
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
|
||||
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->only($allowedFields));
|
||||
if ($instantDeploy) {
|
||||
StartDatabase::dispatch($database);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Database\StartDatabase;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
@@ -228,8 +231,8 @@ class DeployController extends Controller
|
||||
|
||||
// Check if deployment can be cancelled (must be queued or in_progress)
|
||||
$cancellableStatuses = [
|
||||
\App\Enums\ApplicationDeploymentStatus::QUEUED->value,
|
||||
\App\Enums\ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
ApplicationDeploymentStatus::QUEUED->value,
|
||||
ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
];
|
||||
|
||||
if (! in_array($deployment->status, $cancellableStatuses)) {
|
||||
@@ -246,11 +249,11 @@ class DeployController extends Controller
|
||||
|
||||
// Mark deployment as cancelled
|
||||
$deployment->update([
|
||||
'status' => \App\Enums\ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
|
||||
// Get the server
|
||||
$server = Server::find($build_server_id);
|
||||
$server = Server::whereTeamId($teamId)->find($build_server_id);
|
||||
|
||||
if ($server) {
|
||||
// Add cancellation log entry
|
||||
@@ -304,6 +307,8 @@ class DeployController extends Controller
|
||||
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||
new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'pull_request_id', in: 'query', description: 'Preview deployment identifier. Alias of pr.', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'docker_tag', in: 'query', description: 'Docker image tag for Docker Image preview deployments. Requires pull_request_id.', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
|
||||
responses: [
|
||||
@@ -354,7 +359,9 @@ class DeployController extends Controller
|
||||
$uuids = $request->input('uuid');
|
||||
$tags = $request->input('tag');
|
||||
$force = $request->input('force') ?? false;
|
||||
$pr = $request->input('pr') ? max((int) $request->input('pr'), 0) : 0;
|
||||
$pullRequestId = $request->input('pull_request_id', $request->input('pr'));
|
||||
$pr = $pullRequestId ? max((int) $pullRequestId, 0) : 0;
|
||||
$dockerTag = $request->string('docker_tag')->trim()->value() ?: null;
|
||||
|
||||
if ($uuids && $tags) {
|
||||
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||
@@ -362,16 +369,22 @@ class DeployController extends Controller
|
||||
if ($tags && $pr) {
|
||||
return response()->json(['message' => 'You can only use tag or pr, not both.'], 400);
|
||||
}
|
||||
if ($dockerTag && $pr === 0) {
|
||||
return response()->json(['message' => 'docker_tag requires pull_request_id.'], 400);
|
||||
}
|
||||
if ($dockerTag && $tags) {
|
||||
return response()->json(['message' => 'You can only use tag or docker_tag, not both.'], 400);
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
} elseif ($uuids) {
|
||||
return $this->by_uuids($uuids, $teamId, $force, $pr);
|
||||
return $this->by_uuids($uuids, $teamId, $force, $pr, $dockerTag);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||
}
|
||||
|
||||
private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0)
|
||||
private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0, ?string $dockerTag = null)
|
||||
{
|
||||
$uuids = explode(',', $uuid);
|
||||
$uuids = collect(array_filter($uuids));
|
||||
@@ -384,15 +397,22 @@ class DeployController extends Controller
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
$dockerTagForResource = $dockerTag;
|
||||
if ($pr !== 0) {
|
||||
$preview = $resource->previews()->where('pull_request_id', $pr)->first();
|
||||
$preview = null;
|
||||
if ($resource instanceof Application && $resource->build_pack === 'dockerimage') {
|
||||
$preview = $this->upsertDockerImagePreview($resource, $pr, $dockerTag);
|
||||
$dockerTagForResource = $preview?->docker_registry_image_tag;
|
||||
} else {
|
||||
$preview = $resource->previews()->where('pull_request_id', $pr)->first();
|
||||
}
|
||||
if (! $preview) {
|
||||
$deployments->push(['message' => "Pull request {$pr} not found for this resource.", 'resource_uuid' => $uuid]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$result = $this->deploy_resource($resource, $force, $pr);
|
||||
$result = $this->deploy_resource($resource, $force, $pr, $dockerTagForResource);
|
||||
if (isset($result['status']) && $result['status'] === 429) {
|
||||
return response()->json(['message' => $result['message']], 429)->header('Retry-After', 60);
|
||||
}
|
||||
@@ -465,7 +485,7 @@ class DeployController extends Controller
|
||||
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||
}
|
||||
|
||||
public function deploy_resource($resource, bool $force = false, int $pr = 0): array
|
||||
public function deploy_resource($resource, bool $force = false, int $pr = 0, ?string $dockerTag = null): array
|
||||
{
|
||||
$message = null;
|
||||
$deployment_uuid = null;
|
||||
@@ -477,9 +497,12 @@ class DeployController extends Controller
|
||||
// Check authorization for application deployment
|
||||
try {
|
||||
$this->authorize('deploy', $resource);
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
return ['message' => 'Unauthorized to deploy this application.', 'deployment_uuid' => null];
|
||||
}
|
||||
if ($dockerTag !== null && $resource->build_pack !== 'dockerimage') {
|
||||
return ['message' => 'docker_tag can only be used with Docker Image applications.', 'deployment_uuid' => null];
|
||||
}
|
||||
$deployment_uuid = new Cuid2;
|
||||
$result = queue_application_deployment(
|
||||
application: $resource,
|
||||
@@ -487,6 +510,7 @@ class DeployController extends Controller
|
||||
force_rebuild: $force,
|
||||
pull_request_id: $pr,
|
||||
is_api: true,
|
||||
docker_registry_image_tag: $dockerTag,
|
||||
);
|
||||
if ($result['status'] === 'queue_full') {
|
||||
return ['message' => $result['message'], 'deployment_uuid' => null, 'status' => 429];
|
||||
@@ -500,7 +524,7 @@ class DeployController extends Controller
|
||||
// Check authorization for service deployment
|
||||
try {
|
||||
$this->authorize('deploy', $resource);
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
return ['message' => 'Unauthorized to deploy this service.', 'deployment_uuid' => null];
|
||||
}
|
||||
StartService::run($resource);
|
||||
@@ -510,7 +534,7 @@ class DeployController extends Controller
|
||||
// Database resource - check authorization
|
||||
try {
|
||||
$this->authorize('manage', $resource);
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
return ['message' => 'Unauthorized to start this database.', 'deployment_uuid' => null];
|
||||
}
|
||||
StartDatabase::dispatch($resource);
|
||||
@@ -525,6 +549,34 @@ class DeployController extends Controller
|
||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
|
||||
private function upsertDockerImagePreview(Application $application, int $pullRequestId, ?string $dockerTag): ?ApplicationPreview
|
||||
{
|
||||
$preview = $application->previews()->where('pull_request_id', $pullRequestId)->first();
|
||||
|
||||
if (! $preview && $dockerTag === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $preview) {
|
||||
$preview = ApplicationPreview::create([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pullRequestId,
|
||||
'pull_request_html_url' => '',
|
||||
'docker_registry_image_tag' => $dockerTag,
|
||||
]);
|
||||
$preview->generate_preview_fqdn();
|
||||
|
||||
return $preview;
|
||||
}
|
||||
|
||||
if ($dockerTag !== null && $preview->docker_registry_image_tag !== $dockerTag) {
|
||||
$preview->docker_registry_image_tag = $dockerTag;
|
||||
$preview->save();
|
||||
}
|
||||
|
||||
return $preview;
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List application deployments',
|
||||
description: 'List application deployments by using the app uuid',
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use OpenApi\Attributes as OA;
|
||||
@@ -234,7 +235,7 @@ class ProjectController extends Controller
|
||||
}
|
||||
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = Validator::make($request->all(), [
|
||||
@@ -257,7 +258,7 @@ class ProjectController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
$project = Project::create([
|
||||
$project = Project::forceCreate([
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'team_id' => $teamId,
|
||||
@@ -347,7 +348,7 @@ class ProjectController extends Controller
|
||||
}
|
||||
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = Validator::make($request->all(), [
|
||||
@@ -600,7 +601,7 @@ class ProjectController extends Controller
|
||||
}
|
||||
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = Validator::make($request->all(), [
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PrivateKey;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
@@ -176,7 +177,7 @@ class SecurityController extends Controller
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
@@ -300,7 +301,7 @@ class SecurityController extends Controller
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
if ($return instanceof JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -330,7 +331,7 @@ class SecurityController extends Controller
|
||||
'message' => 'Private Key not found.',
|
||||
], 404);
|
||||
}
|
||||
$foundKey->update($request->all());
|
||||
$foundKey->update($request->only($allowedFields));
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => $foundKey->uuid,
|
||||
|
||||
@@ -598,6 +598,11 @@ class ServersController extends Controller
|
||||
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||
'concurrent_builds' => ['type' => 'integer', 'description' => 'Number of concurrent builds.'],
|
||||
'dynamic_timeout' => ['type' => 'integer', 'description' => 'Deployment timeout in seconds.'],
|
||||
'deployment_queue_limit' => ['type' => 'integer', 'description' => 'Maximum number of queued deployments.'],
|
||||
'server_disk_usage_notification_threshold' => ['type' => 'integer', 'description' => 'Server disk usage notification threshold (%).'],
|
||||
'server_disk_usage_check_frequency' => ['type' => 'string', 'description' => 'Cron expression for disk usage check frequency.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -634,7 +639,7 @@ class ServersController extends Controller
|
||||
)]
|
||||
public function update_server(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type', 'concurrent_builds', 'dynamic_timeout', 'deployment_queue_limit', 'server_disk_usage_notification_threshold', 'server_disk_usage_check_frequency'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
@@ -655,6 +660,11 @@ class ServersController extends Controller
|
||||
'is_build_server' => 'boolean|nullable',
|
||||
'instant_validate' => 'boolean|nullable',
|
||||
'proxy_type' => 'string|nullable',
|
||||
'concurrent_builds' => 'integer|min:1',
|
||||
'dynamic_timeout' => 'integer|min:1',
|
||||
'deployment_queue_limit' => 'integer|min:1',
|
||||
'server_disk_usage_notification_threshold' => 'integer|min:1|max:100',
|
||||
'server_disk_usage_check_frequency' => 'string',
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -691,6 +701,19 @@ class ServersController extends Controller
|
||||
'is_build_server' => $request->is_build_server,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->has('server_disk_usage_check_frequency') && ! validate_cron_expression($request->server_disk_usage_check_frequency)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['server_disk_usage_check_frequency' => ['Invalid Cron / Human expression for Disk Usage Check Frequency.']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$advancedSettings = $request->only(['concurrent_builds', 'dynamic_timeout', 'deployment_queue_limit', 'server_disk_usage_notification_threshold', 'server_disk_usage_check_frequency']);
|
||||
if (! empty($advancedSettings)) {
|
||||
$server->settings()->update(array_filter($advancedSettings, fn ($value) => ! is_null($value)));
|
||||
}
|
||||
|
||||
if ($request->instant_validate) {
|
||||
ValidateServer::dispatch($server);
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ class ServicesController extends Controller
|
||||
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
|
||||
data_set($servicePayload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($servicePayload);
|
||||
$service = Service::forceCreate($servicePayload);
|
||||
$service->name = $request->name ?? "$oneClickServiceName-".$service->uuid;
|
||||
$service->description = $request->description;
|
||||
if ($request->has('is_container_label_escape_enabled')) {
|
||||
|
||||
@@ -14,14 +14,6 @@ class TeamController extends Controller
|
||||
'custom_server_limit',
|
||||
'pivot',
|
||||
]);
|
||||
if (request()->attributes->get('can_read_sensitive', false) === false) {
|
||||
$team->makeHidden([
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'resend_api_key',
|
||||
'telegram_token',
|
||||
]);
|
||||
}
|
||||
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class Bitbucket extends Controller
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'bitbucket',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
@@ -128,7 +128,7 @@ class Bitbucket extends Controller
|
||||
]);
|
||||
$pr_app->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'bitbucket',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
|
||||
@@ -144,7 +144,7 @@ class Gitea extends Controller
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'gitea',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
@@ -153,7 +153,7 @@ class Gitea extends Controller
|
||||
]);
|
||||
$pr_app->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'gitea',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
|
||||
@@ -177,7 +177,7 @@ class Gitlab extends Controller
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'gitlab',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
@@ -186,7 +186,7 @@ class Gitlab extends Controller
|
||||
]);
|
||||
$pr_app->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$pr_app = ApplicationPreview::create([
|
||||
$pr_app = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'gitlab',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
|
||||
@@ -76,6 +76,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private ?string $dockerImageTag = null;
|
||||
|
||||
private ?string $dockerImagePreviewTag = null;
|
||||
|
||||
private GithubApp|GitlabApp|string $source = 'other';
|
||||
|
||||
private StandaloneDocker|SwarmDocker $destination;
|
||||
@@ -208,6 +210,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
$this->dockerImagePreviewTag = $this->application_deployment_queue->docker_registry_image_tag;
|
||||
|
||||
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||
|
||||
@@ -246,6 +249,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
// Set preview fqdn
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->build_pack === 'dockerimage' && str($this->dockerImagePreviewTag)->isEmpty()) {
|
||||
$this->dockerImagePreviewTag = $this->preview?->docker_registry_image_tag;
|
||||
}
|
||||
if ($this->preview) {
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->preview->generate_preview_fqdn_compose();
|
||||
@@ -288,7 +294,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
// Make sure the private key is stored in the filesystem
|
||||
$this->server->privateKey->storeInFileSystem();
|
||||
// Generate custom host<->ip mapping
|
||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||
$safeNetwork = escapeshellarg($this->destination->network);
|
||||
$allContainers = instant_remote_process(["docker network inspect {$safeNetwork} -f '{{json .Containers}}' "], $this->server);
|
||||
|
||||
if (! is_null($allContainers)) {
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
@@ -465,14 +472,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->just_restart();
|
||||
|
||||
return;
|
||||
} elseif ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
} elseif ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} elseif ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} elseif ($this->application->build_pack === 'dockercompose') {
|
||||
$this->deploy_docker_compose_buildpack();
|
||||
} elseif ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
} elseif ($this->application->build_pack === 'dockerfile') {
|
||||
$this->deploy_dockerfile_buildpack();
|
||||
} elseif ($this->application->build_pack === 'static') {
|
||||
@@ -553,11 +560,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
private function deploy_dockerimage_buildpack()
|
||||
{
|
||||
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||
if (str($this->application->docker_registry_image_tag)->isEmpty()) {
|
||||
$this->dockerImageTag = 'latest';
|
||||
} else {
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
}
|
||||
$this->dockerImageTag = $this->resolveDockerImageTag();
|
||||
|
||||
// Check if this is an image hash deployment
|
||||
$isImageHash = str($this->dockerImageTag)->startsWith('sha256-');
|
||||
@@ -574,6 +577,19 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function resolveDockerImageTag(): string
|
||||
{
|
||||
if ($this->pull_request_id !== 0 && str($this->dockerImagePreviewTag)->isNotEmpty()) {
|
||||
return $this->dockerImagePreviewTag;
|
||||
}
|
||||
|
||||
if (str($this->application->docker_registry_image_tag)->isNotEmpty()) {
|
||||
return $this->application->docker_registry_image_tag;
|
||||
}
|
||||
|
||||
return 'latest';
|
||||
}
|
||||
|
||||
private function deploy_docker_compose_buildpack()
|
||||
{
|
||||
if (data_get($this->application, 'docker_compose_location')) {
|
||||
@@ -1933,6 +1949,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function deploy_pull_request()
|
||||
{
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->deploy_docker_compose_buildpack();
|
||||
|
||||
@@ -2015,9 +2036,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$runCommand = "docker run -d --name {$this->deployment_uuid} {$env_flags} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
} else {
|
||||
if ($this->dockerConfigFileExists === 'OK') {
|
||||
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} {$env_flags} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
$safeNetwork = escapeshellarg($this->destination->network);
|
||||
$runCommand = "docker run -d --network {$safeNetwork} --name {$this->deployment_uuid} {$env_flags} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
} else {
|
||||
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} {$env_flags} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
$safeNetwork = escapeshellarg($this->destination->network);
|
||||
$runCommand = "docker run -d --network {$safeNetwork} --name {$this->deployment_uuid} {$env_flags} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
}
|
||||
}
|
||||
if ($firstTry) {
|
||||
@@ -3046,28 +3069,29 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
||||
} else {
|
||||
// Dockerfile buildpack
|
||||
$safeNetwork = escapeshellarg($this->destination->network);
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
}
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
// Traditional build with args
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$safeNetwork} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
}
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
||||
@@ -678,6 +678,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
} else {
|
||||
$network = $this->database->destination->network;
|
||||
}
|
||||
$safeNetwork = escapeshellarg($network);
|
||||
|
||||
$fullImageName = $this->getFullImageName();
|
||||
|
||||
@@ -689,13 +690,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if (isDev()) {
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
$commands[] = "docker run -d --network {$safeNetwork} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
} else {
|
||||
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
$commands[] = "docker run -d --network {$safeNetwork} --name backup-of-{$this->backup_log_uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
|
||||
}
|
||||
} else {
|
||||
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup_log_uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||
$commands[] = "docker run -d --network {$safeNetwork} --name backup-of-{$this->backup_log_uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
|
||||
}
|
||||
|
||||
// Escape S3 credentials to prevent command injection
|
||||
|
||||
@@ -118,7 +118,7 @@ class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
if (! $found) {
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $this->pullRequestId,
|
||||
@@ -127,7 +127,7 @@ class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
|
||||
]);
|
||||
$preview->generate_preview_fqdn_compose();
|
||||
} else {
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $this->pullRequestId,
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -19,18 +20,18 @@ class Index extends Component
|
||||
'prerequisitesInstalled' => 'handlePrerequisitesInstalled',
|
||||
];
|
||||
|
||||
#[\Livewire\Attributes\Url(as: 'step', history: true)]
|
||||
#[Url(as: 'step', history: true)]
|
||||
public string $currentState = 'welcome';
|
||||
|
||||
#[\Livewire\Attributes\Url(keep: true)]
|
||||
#[Url(keep: true)]
|
||||
public ?string $selectedServerType = null;
|
||||
|
||||
public ?Collection $privateKeys = null;
|
||||
|
||||
#[\Livewire\Attributes\Url(keep: true)]
|
||||
#[Url(keep: true)]
|
||||
public ?int $selectedExistingPrivateKey = null;
|
||||
|
||||
#[\Livewire\Attributes\Url(keep: true)]
|
||||
#[Url(keep: true)]
|
||||
public ?string $privateKeyType = null;
|
||||
|
||||
public ?string $privateKey = null;
|
||||
@@ -45,7 +46,7 @@ class Index extends Component
|
||||
|
||||
public ?Collection $servers = null;
|
||||
|
||||
#[\Livewire\Attributes\Url(keep: true)]
|
||||
#[Url(keep: true)]
|
||||
public ?int $selectedExistingServer = null;
|
||||
|
||||
public ?string $remoteServerName = null;
|
||||
@@ -66,7 +67,7 @@ class Index extends Component
|
||||
|
||||
public Collection $projects;
|
||||
|
||||
#[\Livewire\Attributes\Url(keep: true)]
|
||||
#[Url(keep: true)]
|
||||
public ?int $selectedProject = null;
|
||||
|
||||
public ?Project $createdProject = null;
|
||||
@@ -121,7 +122,7 @@ class Index extends Component
|
||||
}
|
||||
|
||||
if ($this->selectedExistingServer) {
|
||||
$this->createdServer = Server::find($this->selectedExistingServer);
|
||||
$this->createdServer = Server::ownedByCurrentTeam()->find($this->selectedExistingServer);
|
||||
if ($this->createdServer) {
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
|
||||
$this->updateServerDetails();
|
||||
@@ -145,7 +146,7 @@ class Index extends Component
|
||||
}
|
||||
|
||||
if ($this->selectedProject) {
|
||||
$this->createdProject = Project::find($this->selectedProject);
|
||||
$this->createdProject = Project::ownedByCurrentTeam()->find($this->selectedProject);
|
||||
if (! $this->createdProject) {
|
||||
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
|
||||
}
|
||||
@@ -431,13 +432,16 @@ class Index extends Component
|
||||
|
||||
public function selectExistingProject()
|
||||
{
|
||||
$this->createdProject = Project::find($this->selectedProject);
|
||||
$this->createdProject = Project::ownedByCurrentTeam()->find($this->selectedProject);
|
||||
if (! $this->createdProject) {
|
||||
return $this->dispatch('error', 'Project not found.');
|
||||
}
|
||||
$this->currentState = 'create-resource';
|
||||
}
|
||||
|
||||
public function createNewProject()
|
||||
{
|
||||
$this->createdProject = Project::create([
|
||||
$this->createdProject = Project::forceCreate([
|
||||
'name' => 'My first project',
|
||||
'team_id' => currentTeam()->id,
|
||||
'uuid' => (string) new Cuid2,
|
||||
|
||||
@@ -24,7 +24,7 @@ class Docker extends Component
|
||||
#[Validate(['required', 'string'])]
|
||||
public string $name;
|
||||
|
||||
#[Validate(['required', 'string'])]
|
||||
#[Validate(['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/'])]
|
||||
public string $network;
|
||||
|
||||
#[Validate(['required', 'string'])]
|
||||
@@ -77,7 +77,7 @@ class Docker extends Component
|
||||
if ($found) {
|
||||
throw new \Exception('Network already added to this server.');
|
||||
} else {
|
||||
$docker = SwarmDocker::create([
|
||||
$docker = SwarmDocker::forceCreate([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->selectedServer->id,
|
||||
@@ -88,7 +88,7 @@ class Docker extends Component
|
||||
if ($found) {
|
||||
throw new \Exception('Network already added to this server.');
|
||||
} else {
|
||||
$docker = StandaloneDocker::create([
|
||||
$docker = StandaloneDocker::forceCreate([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->selectedServer->id,
|
||||
|
||||
@@ -20,7 +20,7 @@ class Show extends Component
|
||||
#[Validate(['string', 'required'])]
|
||||
public string $name;
|
||||
|
||||
#[Validate(['string', 'required'])]
|
||||
#[Validate(['string', 'required', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/'])]
|
||||
public string $network;
|
||||
|
||||
#[Validate(['string', 'required'])]
|
||||
@@ -84,8 +84,9 @@ class Show extends Component
|
||||
if ($this->destination->attachedTo()) {
|
||||
return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
|
||||
instant_remote_process(['docker network rm -f '.$this->destination->network], $this->destination->server);
|
||||
$safeNetwork = escapeshellarg($this->destination->network);
|
||||
instant_remote_process(["docker network disconnect {$safeNetwork} coolify-proxy"], $this->destination->server, throwError: false);
|
||||
instant_remote_process(["docker network rm -f {$safeNetwork}"], $this->destination->server);
|
||||
}
|
||||
$this->destination->delete();
|
||||
|
||||
|
||||
@@ -1203,7 +1203,7 @@ class GlobalSearch extends Component
|
||||
public function loadDestinations()
|
||||
{
|
||||
$this->loadingDestinations = true;
|
||||
$server = Server::find($this->selectedServerId);
|
||||
$server = Server::ownedByCurrentTeam()->find($this->selectedServerId);
|
||||
|
||||
if (! $server) {
|
||||
$this->loadingDestinations = false;
|
||||
@@ -1280,7 +1280,7 @@ class GlobalSearch extends Component
|
||||
public function loadEnvironments()
|
||||
{
|
||||
$this->loadingEnvironments = true;
|
||||
$project = Project::where('uuid', $this->selectedProjectUuid)->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->selectedProjectUuid)->first();
|
||||
|
||||
if (! $project) {
|
||||
$this->loadingEnvironments = false;
|
||||
|
||||
@@ -42,7 +42,7 @@ class Email extends Component
|
||||
public ?string $smtpHost = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
public ?string $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string', 'in:starttls,tls,none'])]
|
||||
public ?string $smtpEncryption = null;
|
||||
@@ -54,7 +54,7 @@ class Email extends Component
|
||||
public ?string $smtpPassword = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpTimeout = null;
|
||||
public ?string $smtpTimeout = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $resendEnabled = false;
|
||||
|
||||
@@ -30,7 +30,7 @@ class AddEmpty extends Component
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$project = Project::create([
|
||||
$project = Project::forceCreate([
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'team_id' => currentTeam()->id,
|
||||
|
||||
@@ -146,15 +146,15 @@ class General extends Component
|
||||
'gitRepository' => 'required',
|
||||
'gitBranch' => 'required',
|
||||
'gitCommitSha' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'],
|
||||
'installCommand' => 'nullable',
|
||||
'buildCommand' => 'nullable',
|
||||
'startCommand' => 'nullable',
|
||||
'installCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'buildCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'startCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'buildPack' => 'required',
|
||||
'staticImage' => 'required',
|
||||
'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
|
||||
'publishDirectory' => ValidationPatterns::directoryPathRules(),
|
||||
'portsExposes' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsExposes' => ['required', 'string', 'regex:/^(\d+)(,\d+)*$/'],
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'customNetworkAliases' => 'nullable',
|
||||
'dockerfile' => 'nullable',
|
||||
'dockerRegistryImageName' => 'nullable',
|
||||
@@ -200,6 +200,9 @@ class General extends Component
|
||||
'dockerComposeCustomStartCommand.regex' => 'The Docker Compose start command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'dockerComposeCustomBuildCommand.regex' => 'The Docker Compose build command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'customDockerRunOptions.regex' => 'The custom Docker run options contain invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'installCommand.regex' => 'The install command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'buildCommand.regex' => 'The build command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'startCommand.regex' => 'The start command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'preDeploymentCommandContainer.regex' => 'The pre-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
|
||||
'postDeploymentCommandContainer.regex' => 'The post-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
|
||||
'name.required' => 'The Name field is required.',
|
||||
@@ -209,6 +212,8 @@ class General extends Component
|
||||
'staticImage.required' => 'The Static Image field is required.',
|
||||
'baseDirectory.required' => 'The Base Directory field is required.',
|
||||
'portsExposes.required' => 'The Exposed Ports field is required.',
|
||||
'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
|
||||
...ValidationPatterns::portMappingMessages(),
|
||||
'isStatic.required' => 'The Static setting is required.',
|
||||
'isStatic.boolean' => 'The Static setting must be true or false.',
|
||||
'isSpa.required' => 'The SPA setting is required.',
|
||||
@@ -732,6 +737,7 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
try {
|
||||
$this->application->redirect = $this->redirect;
|
||||
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
||||
if ($has_www === 0 && $this->application->redirect === 'www') {
|
||||
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
|
||||
@@ -752,6 +758,12 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString();
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
|
||||
$oldPortsExposes = $this->application->ports_exposes;
|
||||
|
||||
@@ -35,8 +35,17 @@ class Previews extends Component
|
||||
|
||||
public array $previewFqdns = [];
|
||||
|
||||
public array $previewDockerTags = [];
|
||||
|
||||
public ?int $manualPullRequestId = null;
|
||||
|
||||
public ?string $manualDockerTag = null;
|
||||
|
||||
protected $rules = [
|
||||
'previewFqdns.*' => 'string|nullable',
|
||||
'previewDockerTags.*' => 'string|nullable',
|
||||
'manualPullRequestId' => 'integer|min:1|nullable',
|
||||
'manualDockerTag' => 'string|nullable',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -53,12 +62,17 @@ class Previews extends Component
|
||||
$preview = $this->application->previews->get($key);
|
||||
if ($preview) {
|
||||
$preview->fqdn = $fqdn;
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$preview->docker_registry_image_tag = $this->previewDockerTags[$key] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->previewFqdns = [];
|
||||
$this->previewDockerTags = [];
|
||||
foreach ($this->application->previews as $key => $preview) {
|
||||
$this->previewFqdns[$key] = $preview->fqdn;
|
||||
$this->previewDockerTags[$key] = $preview->docker_registry_image_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +188,7 @@ class Previews extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function add(int $pull_request_id, ?string $pull_request_html_url = null)
|
||||
public function add(int $pull_request_id, ?string $pull_request_html_url = null, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
@@ -182,7 +196,7 @@ class Previews extends Component
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found && ! is_null($pull_request_html_url)) {
|
||||
$found = ApplicationPreview::create([
|
||||
$found = ApplicationPreview::forceCreate([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
@@ -195,13 +209,18 @@ class Previews extends Component
|
||||
} else {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found && ! is_null($pull_request_html_url)) {
|
||||
$found = ApplicationPreview::create([
|
||||
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
|
||||
$found = ApplicationPreview::forceCreate([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'pull_request_html_url' => $pull_request_html_url ?? '',
|
||||
'docker_registry_image_tag' => $docker_registry_image_tag,
|
||||
]);
|
||||
}
|
||||
if ($found && $this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()) {
|
||||
$found->docker_registry_image_tag = $docker_registry_image_tag;
|
||||
$found->save();
|
||||
}
|
||||
$found->generate_preview_fqdn();
|
||||
$this->application->refresh();
|
||||
$this->syncData(false);
|
||||
@@ -217,37 +236,50 @@ class Previews extends Component
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true);
|
||||
$dockerRegistryImageTag = null;
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$dockerRegistryImageTag = $this->application->previews()
|
||||
->where('pull_request_id', $pull_request_id)
|
||||
->value('docker_registry_image_tag');
|
||||
}
|
||||
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true, docker_registry_image_tag: $dockerRegistryImageTag);
|
||||
}
|
||||
|
||||
public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null)
|
||||
public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
$this->add($pull_request_id, $pull_request_html_url);
|
||||
$this->deploy($pull_request_id, $pull_request_html_url);
|
||||
$this->add($pull_request_id, $pull_request_html_url, $docker_registry_image_tag);
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: false, docker_registry_image_tag: $docker_registry_image_tag);
|
||||
}
|
||||
|
||||
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false)
|
||||
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
try {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found && ! is_null($pull_request_html_url)) {
|
||||
ApplicationPreview::create([
|
||||
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
|
||||
$found = ApplicationPreview::forceCreate([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'pull_request_html_url' => $pull_request_html_url ?? '',
|
||||
'docker_registry_image_tag' => $docker_registry_image_tag,
|
||||
]);
|
||||
}
|
||||
if ($found && $this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()) {
|
||||
$found->docker_registry_image_tag = $docker_registry_image_tag;
|
||||
$found->save();
|
||||
}
|
||||
$result = queue_application_deployment(
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
pull_request_id: $pull_request_id,
|
||||
git_type: $found->git_type ?? null,
|
||||
docker_registry_image_tag: $docker_registry_image_tag,
|
||||
);
|
||||
if ($result['status'] === 'queue_full') {
|
||||
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||
@@ -277,6 +309,32 @@ class Previews extends Component
|
||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||
}
|
||||
|
||||
public function addDockerImagePreview()
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
$this->validateOnly('manualPullRequestId');
|
||||
$this->validateOnly('manualDockerTag');
|
||||
|
||||
if ($this->application->build_pack !== 'dockerimage') {
|
||||
$this->dispatch('error', 'Manual Docker Image previews are only available for Docker Image applications.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->manualPullRequestId === null || str($this->manualDockerTag)->isEmpty()) {
|
||||
$this->dispatch('error', 'Both pull request id and docker tag are required.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dockerTag = str($this->manualDockerTag)->trim()->value();
|
||||
|
||||
$this->add_and_deploy($this->manualPullRequestId, null, $dockerTag);
|
||||
|
||||
$this->manualPullRequestId = null;
|
||||
$this->manualDockerTag = null;
|
||||
}
|
||||
|
||||
private function stopContainers(array $containers, $server)
|
||||
{
|
||||
$containersToStop = collect($containers)->pluck('Names')->toArray();
|
||||
|
||||
@@ -54,7 +54,7 @@ class CloneMe extends Component
|
||||
public function mount($project_uuid)
|
||||
{
|
||||
$this->project_uuid = $project_uuid;
|
||||
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
|
||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
||||
$this->environment = $this->project->environments->where('uuid', $this->environment_uuid)->first();
|
||||
$this->project_id = $this->project->id;
|
||||
$this->servers = currentTeam()
|
||||
@@ -100,7 +100,7 @@ class CloneMe extends Component
|
||||
if ($foundProject) {
|
||||
throw new \Exception('Project with the same name already exists.');
|
||||
}
|
||||
$project = Project::create([
|
||||
$project = Project::forceCreate([
|
||||
'name' => $this->newName,
|
||||
'team_id' => currentTeam()->id,
|
||||
'description' => $this->project->description.' (clone)',
|
||||
@@ -139,7 +139,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
@@ -187,7 +187,8 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $newDatabase->id,
|
||||
]);
|
||||
@@ -216,7 +217,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resource_id' => $newDatabase->id,
|
||||
]);
|
||||
$newStorage->save();
|
||||
@@ -229,7 +230,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'database_id' => $newDatabase->id,
|
||||
'database_type' => $newDatabase->getMorphClass(),
|
||||
@@ -247,7 +248,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill($payload);
|
||||
])->forceFill($payload);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
}
|
||||
@@ -258,7 +259,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
@@ -276,7 +277,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => (string) new Cuid2,
|
||||
'service_id' => $newService->id,
|
||||
'team_id' => currentTeam()->id,
|
||||
@@ -290,7 +291,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resourceable_id' => $newService->id,
|
||||
'resourceable_type' => $newService->getMorphClass(),
|
||||
]);
|
||||
@@ -298,9 +299,9 @@ class CloneMe extends Component
|
||||
}
|
||||
|
||||
foreach ($newService->applications() as $application) {
|
||||
$application->update([
|
||||
$application->forceFill([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
])->save();
|
||||
|
||||
$persistentVolumes = $application->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
@@ -315,7 +316,8 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $application->id,
|
||||
]);
|
||||
@@ -344,7 +346,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resource_id' => $application->id,
|
||||
]);
|
||||
$newStorage->save();
|
||||
@@ -352,9 +354,9 @@ class CloneMe extends Component
|
||||
}
|
||||
|
||||
foreach ($newService->databases() as $database) {
|
||||
$database->update([
|
||||
$database->forceFill([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
])->save();
|
||||
|
||||
$persistentVolumes = $database->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
@@ -369,7 +371,8 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $database->id,
|
||||
]);
|
||||
@@ -398,7 +401,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resource_id' => $database->id,
|
||||
]);
|
||||
$newStorage->save();
|
||||
@@ -411,7 +414,7 @@ class CloneMe extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'database_id' => $database->id,
|
||||
'database_type' => $database->getMorphClass(),
|
||||
|
||||
@@ -76,7 +76,7 @@ class BackupEdit extends Component
|
||||
public bool $dumpAll = false;
|
||||
|
||||
#[Validate(['required', 'int', 'min:60', 'max:36000'])]
|
||||
public int $timeout = 3600;
|
||||
public int|string $timeout = 3600;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
|
||||
@@ -34,9 +34,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public ?string $customDockerRunOptions = null;
|
||||
|
||||
@@ -79,9 +79,9 @@ class General extends Component
|
||||
'clickhouseAdminUser' => 'required|string',
|
||||
'clickhouseAdminPassword' => 'required|string',
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => 'nullable|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'customDockerRunOptions' => 'nullable|string',
|
||||
'dbUrl' => 'nullable|string',
|
||||
@@ -94,6 +94,7 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'clickhouseAdminUser.required' => 'The Admin User field is required.',
|
||||
'clickhouseAdminUser.string' => 'The Admin User must be a string.',
|
||||
@@ -102,6 +103,8 @@ class General extends Component
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
]
|
||||
@@ -119,8 +122,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->save();
|
||||
@@ -207,6 +210,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public ?string $customDockerRunOptions = null;
|
||||
|
||||
@@ -90,9 +90,9 @@ class General extends Component
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'dragonflyPassword' => 'required|string',
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => 'nullable|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'customDockerRunOptions' => 'nullable|string',
|
||||
'dbUrl' => 'nullable|string',
|
||||
@@ -106,12 +106,15 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'dragonflyPassword.required' => 'The Dragonfly Password field is required.',
|
||||
'dragonflyPassword.string' => 'The Dragonfly Password must be a string.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
]
|
||||
@@ -128,8 +131,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->enable_ssl = $this->enable_ssl;
|
||||
@@ -217,6 +220,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
@@ -276,8 +282,8 @@ class General extends Component
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
|
||||
@@ -36,9 +36,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public ?string $customDockerRunOptions = null;
|
||||
|
||||
@@ -93,9 +93,9 @@ class General extends Component
|
||||
'keydbConf' => 'nullable|string',
|
||||
'keydbPassword' => 'required|string',
|
||||
'image' => 'required|string',
|
||||
'portsMappings' => 'nullable|string',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'customDockerRunOptions' => 'nullable|string',
|
||||
'dbUrl' => 'nullable|string',
|
||||
@@ -111,12 +111,15 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'keydbPassword.required' => 'The KeyDB Password field is required.',
|
||||
'keydbPassword.string' => 'The KeyDB Password must be a string.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'image.string' => 'The Docker Image must be a string.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
]
|
||||
@@ -134,8 +137,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->enable_ssl = $this->enable_ssl;
|
||||
@@ -224,6 +227,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('manageEnvironment', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
@@ -269,9 +275,20 @@ class General extends Component
|
||||
->where('is_ca_certificate', true)
|
||||
->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
|
||||
@@ -42,9 +42,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -78,9 +78,9 @@ class General extends Component
|
||||
'mariadbDatabase' => 'required',
|
||||
'mariadbConf' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
@@ -92,6 +92,7 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mariadbRootPassword.required' => 'The Root Password field is required.',
|
||||
@@ -100,6 +101,8 @@ class General extends Component
|
||||
'mariadbDatabase.required' => 'The MariaDB Database field is required.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
]
|
||||
@@ -159,8 +162,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->enable_ssl = $this->enableSsl;
|
||||
@@ -213,6 +216,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
@@ -289,6 +295,17 @@ class General extends Component
|
||||
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
|
||||
@@ -40,9 +40,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -77,9 +77,9 @@ class General extends Component
|
||||
'mongoInitdbRootPassword' => 'required',
|
||||
'mongoInitdbDatabase' => 'required',
|
||||
'image' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
@@ -92,6 +92,7 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
|
||||
@@ -99,6 +100,8 @@ class General extends Component
|
||||
'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.',
|
||||
@@ -158,8 +161,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->enable_ssl = $this->enableSsl;
|
||||
@@ -213,6 +216,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
@@ -297,6 +303,17 @@ class General extends Component
|
||||
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
|
||||
@@ -42,9 +42,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -80,9 +80,9 @@ class General extends Component
|
||||
'mysqlDatabase' => 'required',
|
||||
'mysqlConf' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
@@ -95,6 +95,7 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'mysqlRootPassword.required' => 'The Root Password field is required.',
|
||||
@@ -103,6 +104,8 @@ class General extends Component
|
||||
'mysqlDatabase.required' => 'The MySQL Database field is required.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
'sslMode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.',
|
||||
@@ -164,8 +167,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->enable_ssl = $this->enableSsl;
|
||||
@@ -220,6 +223,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
@@ -301,6 +307,17 @@ class General extends Component
|
||||
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
|
||||
@@ -46,9 +46,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -92,9 +92,9 @@ class General extends Component
|
||||
'postgresConf' => 'nullable',
|
||||
'initScripts' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
@@ -107,6 +107,7 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'postgresUser.required' => 'The Postgres User field is required.',
|
||||
@@ -114,6 +115,8 @@ class General extends Component
|
||||
'postgresDb.required' => 'The Postgres Database field is required.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.',
|
||||
@@ -179,8 +182,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->enable_ssl = $this->enableSsl;
|
||||
@@ -264,6 +267,17 @@ class General extends Component
|
||||
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
@@ -456,6 +470,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
if (str($this->publicPort)->isEmpty()) {
|
||||
$this->publicPort = null;
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ class General extends Component
|
||||
|
||||
public ?bool $isPublic = null;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
@@ -73,9 +73,9 @@ class General extends Component
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'redisConf' => 'nullable',
|
||||
'image' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'isPublic' => 'nullable|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isLogDrainEnabled' => 'nullable|boolean',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
@@ -89,10 +89,13 @@ class General extends Component
|
||||
{
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
ValidationPatterns::portMappingMessages(),
|
||||
[
|
||||
'name.required' => 'The Name field is required.',
|
||||
'image.required' => 'The Docker Image field is required.',
|
||||
'publicPort.integer' => 'The Public Port must be an integer.',
|
||||
'publicPort.min' => 'The Public Port must be at least 1.',
|
||||
'publicPort.max' => 'The Public Port must not exceed 65535.',
|
||||
'publicPortTimeout.integer' => 'The Public Port Timeout must be an integer.',
|
||||
'publicPortTimeout.min' => 'The Public Port Timeout must be at least 1.',
|
||||
'redisUsername.required' => 'The Redis Username field is required.',
|
||||
@@ -148,8 +151,8 @@ class General extends Component
|
||||
$this->database->image = $this->image;
|
||||
$this->database->ports_mappings = $this->portsMappings;
|
||||
$this->database->is_public = $this->isPublic;
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->database->public_port = $this->publicPort ?: null;
|
||||
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->enable_ssl = $this->enableSsl;
|
||||
@@ -201,6 +204,9 @@ class General extends Component
|
||||
try {
|
||||
$this->authorize('manageEnvironment', $this->database);
|
||||
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
$this->syncData(true);
|
||||
|
||||
if (version_compare($this->redisVersion, '6.0', '>=')) {
|
||||
@@ -282,9 +288,20 @@ class General extends Component
|
||||
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$this->server->generateCaCertificate();
|
||||
$caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
|
||||
@@ -21,7 +21,7 @@ class DeleteProject extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->projectName = Project::findOrFail($this->project_id)->name;
|
||||
$this->projectName = Project::ownedByCurrentTeam()->findOrFail($this->project_id)->name;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
@@ -29,7 +29,7 @@ class DeleteProject extends Component
|
||||
$this->validate([
|
||||
'project_id' => 'required|int',
|
||||
]);
|
||||
$project = Project::findOrFail($this->project_id);
|
||||
$project = Project::ownedByCurrentTeam()->findOrFail($this->project_id);
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
|
||||
@@ -41,8 +41,8 @@ class DockerCompose extends Component
|
||||
// Validate for command injection BEFORE saving to database
|
||||
validateDockerComposeForInjection($this->dockerComposeRaw);
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
$destination_uuid = $this->query['destination'];
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
@@ -54,7 +54,7 @@ class DockerCompose extends Component
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$service = Service::create([
|
||||
$service = Service::forceCreate([
|
||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
|
||||
@@ -121,8 +121,8 @@ class DockerImage extends Component
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
// Append @sha256 to image name if using digest and not already present
|
||||
$imageName = $parser->getFullImageNameWithoutTag();
|
||||
@@ -133,7 +133,7 @@ class DockerImage extends Component
|
||||
// Determine the image tag based on whether it's a hash or regular tag
|
||||
$imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag();
|
||||
|
||||
$application = Application::create([
|
||||
$application = Application::forceCreate([
|
||||
'name' => 'docker-image-'.new Cuid2,
|
||||
'repository_project_id' => 0,
|
||||
'git_repository' => 'coollabsio/coolify',
|
||||
|
||||
@@ -10,7 +10,7 @@ class EmptyProject extends Component
|
||||
{
|
||||
public function createEmptyProject()
|
||||
{
|
||||
$project = Project::create([
|
||||
$project = Project::forceCreate([
|
||||
'name' => generate_random_name(),
|
||||
'team_id' => currentTeam()->id,
|
||||
'uuid' => (string) new Cuid2,
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Rules\ValidGitBranch;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
@@ -168,7 +169,7 @@ class GithubPrivateRepository extends Component
|
||||
'selected_repository_owner' => 'required|string|regex:/^[a-zA-Z0-9\-_]+$/',
|
||||
'selected_repository_repo' => 'required|string|regex:/^[a-zA-Z0-9\-_\.]+$/',
|
||||
'selected_branch_name' => ['required', 'string', new ValidGitBranch],
|
||||
'docker_compose_location' => \App\Support\ValidationPatterns::filePathRules(),
|
||||
'docker_compose_location' => ValidationPatterns::filePathRules(),
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@@ -185,10 +186,10 @@ class GithubPrivateRepository extends Component
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
$application = Application::create([
|
||||
$application = Application::forceCreate([
|
||||
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
||||
'repository_project_id' => $this->selected_repository_id,
|
||||
'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(),
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Rules\ValidGitBranch;
|
||||
use App\Rules\ValidGitRepositoryUrl;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
@@ -66,7 +67,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'is_static' => 'required|boolean',
|
||||
'publish_directory' => 'nullable|string',
|
||||
'build_pack' => 'required|string',
|
||||
'docker_compose_location' => \App\Support\ValidationPatterns::filePathRules(),
|
||||
'docker_compose_location' => ValidationPatterns::filePathRules(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -144,8 +145,8 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
// Note: git_repository has already been validated and transformed in get_git_source()
|
||||
// It may now be in SSH format (git@host:repo.git) which is valid for deploy keys
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
if ($this->git_source === 'other') {
|
||||
$application_init = [
|
||||
'name' => generate_random_name(),
|
||||
@@ -182,7 +183,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application = Application::forceCreate($application_init);
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use App\Rules\ValidGitBranch;
|
||||
use App\Rules\ValidGitRepositoryUrl;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
@@ -72,7 +73,7 @@ class PublicGitRepository extends Component
|
||||
'publish_directory' => 'nullable|string',
|
||||
'build_pack' => 'required|string',
|
||||
'base_directory' => 'nullable|string',
|
||||
'docker_compose_location' => \App\Support\ValidationPatterns::filePathRules(),
|
||||
'docker_compose_location' => ValidationPatterns::filePathRules(),
|
||||
'git_branch' => ['required', 'string', new ValidGitBranch],
|
||||
];
|
||||
}
|
||||
@@ -233,7 +234,7 @@ class PublicGitRepository extends Component
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->git_source->getMorphClass() === \App\Models\GithubApp::class) {
|
||||
if ($this->git_source->getMorphClass() === GithubApp::class) {
|
||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||
$this->branchFound = true;
|
||||
@@ -278,8 +279,8 @@ class PublicGitRepository extends Component
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$project = Project::where('uuid', $project_uuid)->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $environment_uuid)->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $environment_uuid)->firstOrFail();
|
||||
|
||||
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) {
|
||||
$server = $destination->server;
|
||||
@@ -298,7 +299,7 @@ class PublicGitRepository extends Component
|
||||
$new_service['source_id'] = $this->git_source->id;
|
||||
$new_service['source_type'] = $this->git_source->getMorphClass();
|
||||
}
|
||||
$service = Service::create($new_service);
|
||||
$service = Service::forceCreate($new_service);
|
||||
|
||||
return redirect()->route('project.service.configuration', [
|
||||
'service_uuid' => $service->uuid,
|
||||
@@ -345,7 +346,7 @@ class PublicGitRepository extends Component
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application = Application::forceCreate($application_init);
|
||||
|
||||
$application->settings->is_static = $this->isStatic;
|
||||
$application->settings->save();
|
||||
|
||||
@@ -65,7 +65,7 @@ class Select extends Component
|
||||
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
||||
}
|
||||
$projectUuid = data_get($this->parameters, 'project_uuid');
|
||||
$project = Project::whereUuid($projectUuid)->firstOrFail();
|
||||
$project = Project::ownedByCurrentTeam()->whereUuid($projectUuid)->firstOrFail();
|
||||
$this->environments = $project->environments;
|
||||
$this->selectedEnvironment = $this->environments->where('uuid', data_get($this->parameters, 'environment_uuid'))->firstOrFail()->name;
|
||||
|
||||
@@ -79,7 +79,7 @@ class Select extends Component
|
||||
$this->type = $queryType;
|
||||
$this->server_id = $queryServerId;
|
||||
$this->destination_uuid = $queryDestination;
|
||||
$this->server = Server::find($queryServerId);
|
||||
$this->server = Server::ownedByCurrentTeam()->find($queryServerId);
|
||||
$this->current_step = 'select-postgresql-type';
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -45,14 +45,14 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||
|
||||
$port = get_port_from_dockerfile($this->dockerfile);
|
||||
if (! $port) {
|
||||
$port = 80;
|
||||
}
|
||||
$application = Application::create([
|
||||
$application = Application::forceCreate([
|
||||
'name' => 'dockerfile-'.new Cuid2,
|
||||
'repository_project_id' => 0,
|
||||
'git_repository' => 'coollabsio/coolify',
|
||||
|
||||
@@ -91,7 +91,7 @@ class Create extends Component
|
||||
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
|
||||
data_set($service_payload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($service_payload);
|
||||
$service = Service::forceCreate($service_payload);
|
||||
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
|
||||
@@ -51,9 +51,9 @@ class Index extends Component
|
||||
|
||||
public bool $excludeFromStatus = false;
|
||||
|
||||
public ?int $publicPort = null;
|
||||
public mixed $publicPort = null;
|
||||
|
||||
public ?int $publicPortTimeout = 3600;
|
||||
public mixed $publicPortTimeout = 3600;
|
||||
|
||||
public bool $isPublic = false;
|
||||
|
||||
@@ -91,7 +91,7 @@ class Index extends Component
|
||||
'description' => 'nullable',
|
||||
'image' => 'required',
|
||||
'excludeFromStatus' => 'required|boolean',
|
||||
'publicPort' => 'nullable|integer',
|
||||
'publicPort' => 'nullable|integer|min:1|max:65535',
|
||||
'publicPortTimeout' => 'nullable|integer|min:1',
|
||||
'isPublic' => 'required|boolean',
|
||||
'isLogDrainEnabled' => 'required|boolean',
|
||||
@@ -160,8 +160,8 @@ class Index extends Component
|
||||
$this->serviceDatabase->description = $this->description;
|
||||
$this->serviceDatabase->image = $this->image;
|
||||
$this->serviceDatabase->exclude_from_status = $this->excludeFromStatus;
|
||||
$this->serviceDatabase->public_port = $this->publicPort;
|
||||
$this->serviceDatabase->public_port_timeout = $this->publicPortTimeout;
|
||||
$this->serviceDatabase->public_port = $this->publicPort ?: null;
|
||||
$this->serviceDatabase->public_port_timeout = $this->publicPortTimeout ?: null;
|
||||
$this->serviceDatabase->is_public = $this->isPublic;
|
||||
$this->serviceDatabase->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,9 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class GetLogs extends Component
|
||||
@@ -29,12 +31,16 @@ class GetLogs extends Component
|
||||
|
||||
public string $errors = '';
|
||||
|
||||
#[Locked]
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|null $resource = null;
|
||||
|
||||
#[Locked]
|
||||
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||
|
||||
#[Locked]
|
||||
public Server $server;
|
||||
|
||||
#[Locked]
|
||||
public ?string $container = null;
|
||||
|
||||
public ?string $displayName = null;
|
||||
@@ -54,7 +60,7 @@ class GetLogs extends Component
|
||||
public function mount()
|
||||
{
|
||||
if (! is_null($this->resource)) {
|
||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||
if ($this->resource->getMorphClass() === Application::class) {
|
||||
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
|
||||
} else {
|
||||
if ($this->servicesubtype) {
|
||||
@@ -63,7 +69,7 @@ class GetLogs extends Component
|
||||
$this->showTimeStamps = $this->resource->is_include_timestamps;
|
||||
}
|
||||
}
|
||||
if ($this->resource?->getMorphClass() === \App\Models\Application::class) {
|
||||
if ($this->resource?->getMorphClass() === Application::class) {
|
||||
if (str($this->container)->contains('-pr-')) {
|
||||
$this->pull_request = 'Pull Request: '.str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
|
||||
}
|
||||
@@ -74,11 +80,11 @@ class GetLogs extends Component
|
||||
public function instantSave()
|
||||
{
|
||||
if (! is_null($this->resource)) {
|
||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||
if ($this->resource->getMorphClass() === Application::class) {
|
||||
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
|
||||
$this->resource->settings->save();
|
||||
}
|
||||
if ($this->resource->getMorphClass() === \App\Models\Service::class) {
|
||||
if ($this->resource->getMorphClass() === Service::class) {
|
||||
$serviceName = str($this->container)->beforeLast('-')->value();
|
||||
$subType = $this->resource->applications()->where('name', $serviceName)->first();
|
||||
if ($subType) {
|
||||
@@ -118,10 +124,20 @@ class GetLogs extends Component
|
||||
|
||||
public function getLogs($refresh = false)
|
||||
{
|
||||
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
|
||||
$this->outputs = 'Unauthorized.';
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
if (! $refresh && ! $this->expandByDefault && ($this->resource?->getMorphClass() === \App\Models\Service::class || str($this->container)->contains('-pr-'))) {
|
||||
if ($this->container && ! ValidationPatterns::isValidContainerName($this->container)) {
|
||||
$this->outputs = 'Invalid container name.';
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $refresh && ! $this->expandByDefault && ($this->resource?->getMorphClass() === Service::class || str($this->container)->contains('-pr-'))) {
|
||||
return;
|
||||
}
|
||||
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
|
||||
@@ -194,9 +210,15 @@ class GetLogs extends Component
|
||||
|
||||
public function downloadAllLogs(): string
|
||||
{
|
||||
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
|
||||
return '';
|
||||
}
|
||||
if (! $this->server->isFunctional() || ! $this->container) {
|
||||
return '';
|
||||
}
|
||||
if (! ValidationPatterns::isValidContainerName($this->container)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->showTimeStamps) {
|
||||
if ($this->server->isSwarm()) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Component;
|
||||
|
||||
class ResourceLimits extends Component
|
||||
@@ -16,24 +17,24 @@ class ResourceLimits extends Component
|
||||
|
||||
public ?string $limitsCpuset = null;
|
||||
|
||||
public ?int $limitsCpuShares = null;
|
||||
public mixed $limitsCpuShares = null;
|
||||
|
||||
public string $limitsMemory;
|
||||
|
||||
public string $limitsMemorySwap;
|
||||
|
||||
public int $limitsMemorySwappiness;
|
||||
public mixed $limitsMemorySwappiness = 0;
|
||||
|
||||
public string $limitsMemoryReservation;
|
||||
|
||||
protected $rules = [
|
||||
'limitsMemory' => 'required|string',
|
||||
'limitsMemorySwap' => 'required|string',
|
||||
'limitsMemory' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
|
||||
'limitsMemorySwap' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
|
||||
'limitsMemorySwappiness' => 'required|integer|min:0|max:100',
|
||||
'limitsMemoryReservation' => 'required|string',
|
||||
'limitsCpus' => 'nullable',
|
||||
'limitsCpuset' => 'nullable',
|
||||
'limitsCpuShares' => 'nullable',
|
||||
'limitsMemoryReservation' => ['required', 'string', 'regex:/^(0|\d+[bBkKmMgG])$/'],
|
||||
'limitsCpus' => ['nullable', 'regex:/^\d*\.?\d+$/'],
|
||||
'limitsCpuset' => ['nullable', 'regex:/^\d+([,-]\d+)*$/'],
|
||||
'limitsCpuShares' => 'nullable|integer|min:0',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -46,6 +47,19 @@ class ResourceLimits extends Component
|
||||
'limitsCpuShares' => 'cpu shares',
|
||||
];
|
||||
|
||||
protected $messages = [
|
||||
'limitsMemory.regex' => 'Maximum Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
|
||||
'limitsMemorySwap.regex' => 'Maximum Swap Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
|
||||
'limitsMemoryReservation.regex' => 'Soft Memory Limit must be a number followed by a unit (b, k, m, g). Example: 256m, 1g. Use 0 for unlimited.',
|
||||
'limitsCpus.regex' => 'Number of CPUs must be a number (integer or decimal). Example: 0.5, 2.',
|
||||
'limitsCpuset.regex' => 'CPU sets must be a comma-separated list of CPU numbers or ranges. Example: 0-2 or 0,1,3.',
|
||||
'limitsMemorySwappiness.integer' => 'Swappiness must be a whole number between 0 and 100.',
|
||||
'limitsMemorySwappiness.min' => 'Swappiness must be between 0 and 100.',
|
||||
'limitsMemorySwappiness.max' => 'Swappiness must be between 0 and 100.',
|
||||
'limitsCpuShares.integer' => 'CPU Weight must be a whole number.',
|
||||
'limitsCpuShares.min' => 'CPU Weight must be a positive number.',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sync data between component properties and model
|
||||
*
|
||||
@@ -57,10 +71,10 @@ class ResourceLimits extends Component
|
||||
// Sync TO model (before save)
|
||||
$this->resource->limits_cpus = $this->limitsCpus;
|
||||
$this->resource->limits_cpuset = $this->limitsCpuset;
|
||||
$this->resource->limits_cpu_shares = $this->limitsCpuShares;
|
||||
$this->resource->limits_cpu_shares = (int) $this->limitsCpuShares;
|
||||
$this->resource->limits_memory = $this->limitsMemory;
|
||||
$this->resource->limits_memory_swap = $this->limitsMemorySwap;
|
||||
$this->resource->limits_memory_swappiness = $this->limitsMemorySwappiness;
|
||||
$this->resource->limits_memory_swappiness = (int) $this->limitsMemorySwappiness;
|
||||
$this->resource->limits_memory_reservation = $this->limitsMemoryReservation;
|
||||
} else {
|
||||
// Sync FROM model (on load/refresh)
|
||||
@@ -91,7 +105,7 @@ class ResourceLimits extends Component
|
||||
if (! $this->limitsMemorySwap) {
|
||||
$this->limitsMemorySwap = '0';
|
||||
}
|
||||
if (is_null($this->limitsMemorySwappiness)) {
|
||||
if ($this->limitsMemorySwappiness === '' || is_null($this->limitsMemorySwappiness)) {
|
||||
$this->limitsMemorySwappiness = 60;
|
||||
}
|
||||
if (! $this->limitsMemoryReservation) {
|
||||
@@ -103,7 +117,7 @@ class ResourceLimits extends Component
|
||||
if ($this->limitsCpuset === '') {
|
||||
$this->limitsCpuset = null;
|
||||
}
|
||||
if (is_null($this->limitsCpuShares)) {
|
||||
if ($this->limitsCpuShares === '' || is_null($this->limitsCpuShares)) {
|
||||
$this->limitsCpuShares = 1024;
|
||||
}
|
||||
|
||||
@@ -112,6 +126,12 @@ class ResourceLimits extends Component
|
||||
$this->syncData(true);
|
||||
$this->resource->save();
|
||||
$this->dispatch('success', 'Resource limits updated.');
|
||||
} catch (ValidationException $e) {
|
||||
foreach ($e->validator->errors()->all() as $message) {
|
||||
$this->dispatch('error', $message);
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,18 @@ use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Jobs\VolumeCloneJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
@@ -60,7 +69,7 @@ class ResourceOperations extends Component
|
||||
$uuid = (string) new Cuid2;
|
||||
$server = $new_destination->server;
|
||||
|
||||
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
|
||||
if ($this->resource->getMorphClass() === Application::class) {
|
||||
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
|
||||
|
||||
$route = route('project.application.configuration', [
|
||||
@@ -71,21 +80,21 @@ class ResourceOperations extends Component
|
||||
|
||||
return redirect()->to($route);
|
||||
} elseif (
|
||||
$this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
|
||||
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
|
||||
$this->resource->getMorphClass() === StandalonePostgresql::class ||
|
||||
$this->resource->getMorphClass() === StandaloneMongodb::class ||
|
||||
$this->resource->getMorphClass() === StandaloneMysql::class ||
|
||||
$this->resource->getMorphClass() === StandaloneMariadb::class ||
|
||||
$this->resource->getMorphClass() === StandaloneRedis::class ||
|
||||
$this->resource->getMorphClass() === StandaloneKeydb::class ||
|
||||
$this->resource->getMorphClass() === StandaloneDragonfly::class ||
|
||||
$this->resource->getMorphClass() === StandaloneClickhouse::class
|
||||
) {
|
||||
$uuid = (string) new Cuid2;
|
||||
$new_resource = $this->resource->replicate([
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name.'-clone-'.$uuid,
|
||||
'status' => 'exited',
|
||||
@@ -133,7 +142,8 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $new_resource->id,
|
||||
]);
|
||||
@@ -162,7 +172,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resource_id' => $new_resource->id,
|
||||
]);
|
||||
$newStorage->save();
|
||||
@@ -175,7 +185,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'database_id' => $new_resource->id,
|
||||
'database_type' => $new_resource->getMorphClass(),
|
||||
@@ -194,7 +204,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill($payload);
|
||||
])->forceFill($payload);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
|
||||
@@ -211,7 +221,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name.'-clone-'.$uuid,
|
||||
'destination_id' => $new_destination->id,
|
||||
@@ -232,7 +242,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'uuid' => (string) new Cuid2,
|
||||
'service_id' => $new_resource->id,
|
||||
'team_id' => currentTeam()->id,
|
||||
@@ -246,7 +256,7 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
])->forceFill([
|
||||
'resourceable_id' => $new_resource->id,
|
||||
'resourceable_type' => $new_resource->getMorphClass(),
|
||||
]);
|
||||
@@ -254,9 +264,9 @@ class ResourceOperations extends Component
|
||||
}
|
||||
|
||||
foreach ($new_resource->applications() as $application) {
|
||||
$application->update([
|
||||
$application->forceFill([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
])->save();
|
||||
|
||||
$persistentVolumes = $application->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
@@ -271,7 +281,8 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $application->id,
|
||||
]);
|
||||
@@ -296,9 +307,9 @@ class ResourceOperations extends Component
|
||||
}
|
||||
|
||||
foreach ($new_resource->databases() as $database) {
|
||||
$database->update([
|
||||
$database->forceFill([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
])->save();
|
||||
|
||||
$persistentVolumes = $database->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
@@ -313,7 +324,8 @@ class ResourceOperations extends Component
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
])->fill([
|
||||
'uuid',
|
||||
])->forceFill([
|
||||
'name' => $newName,
|
||||
'resource_id' => $database->id,
|
||||
]);
|
||||
@@ -354,9 +366,9 @@ class ResourceOperations extends Component
|
||||
try {
|
||||
$this->authorize('update', $this->resource);
|
||||
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
|
||||
$this->resource->update([
|
||||
$this->resource->forceFill([
|
||||
'environment_id' => $environment_id,
|
||||
]);
|
||||
])->save();
|
||||
if ($this->resource->type() === 'application') {
|
||||
$route = route('project.application.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
|
||||
@@ -52,9 +52,15 @@ class Show extends Component
|
||||
#[Locked]
|
||||
public string $task_uuid;
|
||||
|
||||
public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null)
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$task_uuid = request()->route('task_uuid');
|
||||
$project_uuid = request()->route('project_uuid');
|
||||
$environment_uuid = request()->route('environment_uuid');
|
||||
$application_uuid = request()->route('application_uuid');
|
||||
$service_uuid = request()->route('service_uuid');
|
||||
|
||||
$this->task_uuid = $task_uuid;
|
||||
if ($application_uuid) {
|
||||
$this->type = 'application';
|
||||
@@ -105,6 +111,19 @@ class Show extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleEnabled()
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->resource);
|
||||
$this->isEnabled = ! $this->isEnabled;
|
||||
$this->task->enabled = $this->isEnabled;
|
||||
$this->task->save();
|
||||
$this->dispatch('success', $this->isEnabled ? 'Scheduled task enabled.' : 'Scheduled task disabled.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -42,7 +42,7 @@ class Show extends Component
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$environment = Environment::create([
|
||||
$environment = Environment::forceCreate([
|
||||
'name' => $this->name,
|
||||
'project_id' => $this->project->id,
|
||||
'uuid' => (string) new Cuid2,
|
||||
|
||||
@@ -15,17 +15,17 @@ class Advanced extends Component
|
||||
#[Validate(['string'])]
|
||||
public string $serverDiskUsageCheckFrequency = '0 23 * * *';
|
||||
|
||||
#[Validate(['integer', 'min:1', 'max:99'])]
|
||||
public int $serverDiskUsageNotificationThreshold = 50;
|
||||
#[Validate(['required', 'integer', 'min:1', 'max:99'])]
|
||||
public int|string $serverDiskUsageNotificationThreshold = 50;
|
||||
|
||||
#[Validate(['integer', 'min:1'])]
|
||||
public int $concurrentBuilds = 1;
|
||||
#[Validate(['required', 'integer', 'min:1'])]
|
||||
public int|string $concurrentBuilds = 1;
|
||||
|
||||
#[Validate(['integer', 'min:1'])]
|
||||
public int $dynamicTimeout = 1;
|
||||
#[Validate(['required', 'integer', 'min:1'])]
|
||||
public int|string $dynamicTimeout = 1;
|
||||
|
||||
#[Validate(['integer', 'min:1'])]
|
||||
public int $deploymentQueueLimit = 25;
|
||||
#[Validate(['required', 'integer', 'min:1'])]
|
||||
public int|string $deploymentQueueLimit = 25;
|
||||
|
||||
public function mount(string $server_uuid)
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ class Destinations extends Component
|
||||
|
||||
return;
|
||||
} else {
|
||||
SwarmDocker::create([
|
||||
SwarmDocker::forceCreate([
|
||||
'name' => $this->server->name.'-'.$name,
|
||||
'network' => $this->name,
|
||||
'server_id' => $this->server->id,
|
||||
@@ -57,7 +57,7 @@ class Destinations extends Component
|
||||
|
||||
return;
|
||||
} else {
|
||||
StandaloneDocker::create([
|
||||
StandaloneDocker::forceCreate([
|
||||
'name' => $this->server->name.'-'.$name,
|
||||
'network' => $name,
|
||||
'server_id' => $this->server->id,
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Actions\Proxy\GetProxyConfiguration;
|
||||
use App\Actions\Proxy\SaveProxyConfiguration;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use App\Rules\SafeExternalUrl;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -41,9 +42,13 @@ class Proxy extends Component
|
||||
];
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
'generateExactLabels' => 'required|boolean',
|
||||
];
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'generateExactLabels' => 'required|boolean',
|
||||
'redirectUrl' => ['nullable', new SafeExternalUrl],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -147,6 +152,7 @@ class Proxy extends Component
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->server);
|
||||
$this->validate();
|
||||
SaveProxyConfiguration::run($this->server, $this->proxySettings);
|
||||
$this->server->proxy->redirect_url = $this->redirectUrl;
|
||||
$this->server->save();
|
||||
|
||||
@@ -25,13 +25,13 @@ class Sentinel extends Component
|
||||
public ?string $sentinelUpdatedAt = null;
|
||||
|
||||
#[Validate(['required', 'integer', 'min:1'])]
|
||||
public int $sentinelMetricsRefreshRateSeconds;
|
||||
public int|string $sentinelMetricsRefreshRateSeconds;
|
||||
|
||||
#[Validate(['required', 'integer', 'min:1'])]
|
||||
public int $sentinelMetricsHistoryDays;
|
||||
public int|string $sentinelMetricsHistoryDays;
|
||||
|
||||
#[Validate(['required', 'integer', 'min:10'])]
|
||||
public int $sentinelPushIntervalSeconds;
|
||||
public int|string $sentinelPushIntervalSeconds;
|
||||
|
||||
#[Validate(['nullable', 'url'])]
|
||||
public ?string $sentinelCustomUrl = null;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Rules\ValidDnsServers;
|
||||
use App\Rules\ValidIpOrCidr;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
@@ -20,7 +21,6 @@ class Advanced extends Component
|
||||
#[Validate('boolean')]
|
||||
public bool $is_dns_validation_enabled;
|
||||
|
||||
#[Validate('nullable|string')]
|
||||
public ?string $custom_dns_servers = null;
|
||||
|
||||
#[Validate('boolean')]
|
||||
@@ -43,7 +43,7 @@ class Advanced extends Component
|
||||
'is_registration_enabled' => 'boolean',
|
||||
'do_not_track' => 'boolean',
|
||||
'is_dns_validation_enabled' => 'boolean',
|
||||
'custom_dns_servers' => 'nullable|string',
|
||||
'custom_dns_servers' => ['nullable', 'string', new ValidDnsServers],
|
||||
'is_api_enabled' => 'boolean',
|
||||
'allowed_ips' => ['nullable', 'string', new ValidIpOrCidr],
|
||||
'is_sponsorship_popup_enabled' => 'boolean',
|
||||
@@ -157,6 +157,19 @@ class Advanced extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleRegistration($password): bool
|
||||
{
|
||||
if (! verifyPasswordConfirmation($password, $this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->settings->is_registration_enabled = $this->is_registration_enabled = true;
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Registration has been enabled.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function toggleTwoStepConfirmation($password): bool
|
||||
{
|
||||
if (! verifyPasswordConfirmation($password, $this)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\InstanceSettings;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
@@ -82,7 +83,7 @@ class SettingsBackup extends Component
|
||||
$postgres_password = $envs['POSTGRES_PASSWORD'];
|
||||
$postgres_user = $envs['POSTGRES_USER'];
|
||||
$postgres_db = $envs['POSTGRES_DB'];
|
||||
$this->database = StandalonePostgresql::create([
|
||||
$this->database = StandalonePostgresql::forceCreate([
|
||||
'id' => 0,
|
||||
'name' => 'coolify-db',
|
||||
'description' => 'Coolify database',
|
||||
@@ -90,7 +91,7 @@ class SettingsBackup extends Component
|
||||
'postgres_password' => $postgres_password,
|
||||
'postgres_db' => $postgres_db,
|
||||
'status' => 'running',
|
||||
'destination_type' => \App\Models\StandaloneDocker::class,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'destination_id' => 0,
|
||||
]);
|
||||
$this->backup = ScheduledDatabaseBackup::create([
|
||||
@@ -99,7 +100,7 @@ class SettingsBackup extends Component
|
||||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
'database_id' => $this->database->id,
|
||||
'database_type' => \App\Models\StandalonePostgresql::class,
|
||||
'database_type' => StandalonePostgresql::class,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->database->refresh();
|
||||
|
||||
@@ -33,7 +33,7 @@ class SettingsEmail extends Component
|
||||
public ?string $smtpHost = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||
public ?int $smtpPort = null;
|
||||
public ?string $smtpPort = null;
|
||||
|
||||
#[Validate(['nullable', 'string', 'in:starttls,tls,none'])]
|
||||
public ?string $smtpEncryption = 'starttls';
|
||||
@@ -45,7 +45,7 @@ class SettingsEmail extends Component
|
||||
public ?string $smtpPassword = null;
|
||||
|
||||
#[Validate(['nullable', 'numeric'])]
|
||||
public ?int $smtpTimeout = null;
|
||||
public ?string $smtpTimeout = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $resendEnabled = false;
|
||||
|
||||
+99
-14
@@ -118,7 +118,92 @@ class Application extends BaseModel
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'fqdn',
|
||||
'git_repository',
|
||||
'git_branch',
|
||||
'git_commit_sha',
|
||||
'git_full_url',
|
||||
'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',
|
||||
'health_check_type',
|
||||
'health_check_command',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'status',
|
||||
'preview_url_template',
|
||||
'dockerfile',
|
||||
'dockerfile_location',
|
||||
'dockerfile_target_build',
|
||||
'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',
|
||||
'docker_compose_location',
|
||||
'docker_compose_pr_location',
|
||||
'docker_compose',
|
||||
'docker_compose_pr',
|
||||
'docker_compose_raw',
|
||||
'docker_compose_pr_raw',
|
||||
'docker_compose_domains',
|
||||
'docker_compose_custom_start_command',
|
||||
'docker_compose_custom_build_command',
|
||||
'swarm_replicas',
|
||||
'swarm_placement_constraints',
|
||||
'watch_paths',
|
||||
'redirect',
|
||||
'compose_parsing_version',
|
||||
'custom_nginx_configuration',
|
||||
'custom_network_aliases',
|
||||
'custom_healthcheck_found',
|
||||
'nixpkgsarchive',
|
||||
'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',
|
||||
'use_build_server',
|
||||
'config_hash',
|
||||
'last_online_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
];
|
||||
|
||||
protected $appends = ['server_status'];
|
||||
|
||||
@@ -214,7 +299,7 @@ class Application extends BaseModel
|
||||
}
|
||||
});
|
||||
static::created(function ($application) {
|
||||
ApplicationSetting::create([
|
||||
ApplicationSetting::forceCreate([
|
||||
'application_id' => $application->id,
|
||||
]);
|
||||
$application->compose_parsing_version = self::$parserVersion;
|
||||
@@ -1051,7 +1136,7 @@ class Application extends BaseModel
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->custom_network_aliases.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->settings->use_build_secrets.$this->settings->inject_build_args_to_dockerfile.$this->settings->include_source_commit_in_build);
|
||||
$newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->custom_network_aliases.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration.$this->settings?->use_build_secrets.$this->settings?->inject_build_args_to_dockerfile.$this->settings?->include_source_commit_in_build);
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get(['value', 'is_multiline', 'is_literal', 'is_buildtime', 'is_runtime'])->sort());
|
||||
} else {
|
||||
@@ -1145,7 +1230,7 @@ class Application extends BaseModel
|
||||
'is_accessible' => true,
|
||||
'error' => null,
|
||||
];
|
||||
} catch (\RuntimeException $ex) {
|
||||
} catch (RuntimeException $ex) {
|
||||
return [
|
||||
'is_accessible' => false,
|
||||
'error' => $ex->getMessage(),
|
||||
@@ -1202,7 +1287,7 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->source->getMorphClass() === \App\Models\GitlabApp::class) {
|
||||
if ($this->source->getMorphClass() === GitlabApp::class) {
|
||||
$gitlabSource = $this->source;
|
||||
$private_key = data_get($gitlabSource, 'privateKey.private_key');
|
||||
|
||||
@@ -1354,7 +1439,7 @@ class Application extends BaseModel
|
||||
$source_html_url_host = $url['host'];
|
||||
$source_html_url_scheme = $url['scheme'];
|
||||
|
||||
if ($this->source->getMorphClass() === \App\Models\GithubApp::class) {
|
||||
if ($this->source->getMorphClass() === GithubApp::class) {
|
||||
if ($this->source->is_public) {
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}");
|
||||
@@ -1409,7 +1494,7 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->source->getMorphClass() === \App\Models\GitlabApp::class) {
|
||||
if ($this->source->getMorphClass() === GitlabApp::class) {
|
||||
$gitlabSource = $this->source;
|
||||
$private_key = data_get($gitlabSource, 'privateKey.private_key');
|
||||
|
||||
@@ -1600,7 +1685,7 @@ class Application extends BaseModel
|
||||
try {
|
||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException($e->getMessage());
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
$services = data_get($yaml, 'services');
|
||||
|
||||
@@ -1682,7 +1767,7 @@ class Application extends BaseModel
|
||||
$fileList = collect([".$workdir$composeFile"]);
|
||||
$gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid);
|
||||
if (! $gitRemoteStatus['is_accessible']) {
|
||||
throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
|
||||
throw new RuntimeException('Failed to read Git source. Please verify repository access and try again.');
|
||||
}
|
||||
$getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false);
|
||||
$gitVersion = str($getGitVersion)->explode(' ')->last();
|
||||
@@ -1732,15 +1817,15 @@ class Application extends BaseModel
|
||||
$this->save();
|
||||
|
||||
if (str($e->getMessage())->contains('No such file')) {
|
||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
throw new RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
}
|
||||
if (str($e->getMessage())->contains('fatal: repository') && str($e->getMessage())->contains('does not exist')) {
|
||||
if ($this->deploymentType() === 'deploy_key') {
|
||||
throw new \RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
|
||||
throw new RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
|
||||
}
|
||||
throw new \RuntimeException('Repository does not exist. Please check your repository URL and try again.');
|
||||
throw new RuntimeException('Repository does not exist. Please check your repository URL and try again.');
|
||||
}
|
||||
throw new \RuntimeException($e->getMessage());
|
||||
throw new RuntimeException('Failed to read the Docker Compose file from the repository.');
|
||||
} finally {
|
||||
// Cleanup only - restoration happens in catch block
|
||||
$commands = collect([
|
||||
@@ -1793,7 +1878,7 @@ class Application extends BaseModel
|
||||
$this->base_directory = $initialBaseDirectory;
|
||||
$this->save();
|
||||
|
||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
throw new RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use OpenApi\Attributes as OA;
|
||||
'application_id' => ['type' => 'string'],
|
||||
'deployment_uuid' => ['type' => 'string'],
|
||||
'pull_request_id' => ['type' => 'integer'],
|
||||
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true],
|
||||
'force_rebuild' => ['type' => 'boolean'],
|
||||
'commit' => ['type' => 'string'],
|
||||
'status' => ['type' => 'string'],
|
||||
@@ -39,9 +40,35 @@ use OpenApi\Attributes as OA;
|
||||
)]
|
||||
class ApplicationDeploymentQueue extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'application_id',
|
||||
'deployment_uuid',
|
||||
'pull_request_id',
|
||||
'force_rebuild',
|
||||
'commit',
|
||||
'status',
|
||||
'is_webhook',
|
||||
'logs',
|
||||
'current_process_id',
|
||||
'restart_only',
|
||||
'git_type',
|
||||
'server_id',
|
||||
'application_name',
|
||||
'server_name',
|
||||
'deployment_url',
|
||||
'destination_id',
|
||||
'only_this_server',
|
||||
'rollback',
|
||||
'commit_message',
|
||||
'is_api',
|
||||
'build_server_id',
|
||||
'horizon_job_id',
|
||||
'horizon_job_worker',
|
||||
'finished_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pull_request_id' => 'integer',
|
||||
'finished_at' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
@@ -10,7 +10,22 @@ class ApplicationPreview extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'application_id',
|
||||
'pull_request_id',
|
||||
'pull_request_html_url',
|
||||
'pull_request_issue_comment_id',
|
||||
'fqdn',
|
||||
'status',
|
||||
'git_type',
|
||||
'docker_compose_domains',
|
||||
'docker_registry_image_tag',
|
||||
'last_online_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pull_request_id' => 'integer',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -69,7 +84,7 @@ class ApplicationPreview extends BaseModel
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(\App\Models\LocalPersistentVolume::class, 'resource');
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function generate_preview_fqdn()
|
||||
|
||||
@@ -28,7 +28,42 @@ class ApplicationSetting extends Model
|
||||
'docker_images_to_keep' => 'integer',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'is_static',
|
||||
'is_git_submodules_enabled',
|
||||
'is_git_lfs_enabled',
|
||||
'is_auto_deploy_enabled',
|
||||
'is_force_https_enabled',
|
||||
'is_debug_enabled',
|
||||
'is_preview_deployments_enabled',
|
||||
'is_log_drain_enabled',
|
||||
'is_gpu_enabled',
|
||||
'gpu_driver',
|
||||
'gpu_count',
|
||||
'gpu_device_ids',
|
||||
'gpu_options',
|
||||
'is_include_timestamps',
|
||||
'is_swarm_only_worker_nodes',
|
||||
'is_raw_compose_deployment_enabled',
|
||||
'is_build_server_enabled',
|
||||
'is_consistent_container_name_enabled',
|
||||
'is_gzip_enabled',
|
||||
'is_stripprefix_enabled',
|
||||
'connect_to_docker_network',
|
||||
'custom_internal_name',
|
||||
'is_container_label_escape_enabled',
|
||||
'is_env_sorting_enabled',
|
||||
'is_container_label_readonly_enabled',
|
||||
'is_preserve_repository_enabled',
|
||||
'disable_build_cache',
|
||||
'is_spa',
|
||||
'is_git_shallow_clone_enabled',
|
||||
'is_pr_deployments_public_enabled',
|
||||
'use_build_secrets',
|
||||
'inject_build_args_to_dockerfile',
|
||||
'include_source_commit_in_build',
|
||||
'docker_images_to_keep',
|
||||
];
|
||||
|
||||
public function isStatic(): Attribute
|
||||
{
|
||||
|
||||
@@ -4,7 +4,11 @@ namespace App\Models;
|
||||
|
||||
class CloudProviderToken extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'provider',
|
||||
'token',
|
||||
'name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'token' => 'encrypted',
|
||||
|
||||
@@ -24,7 +24,8 @@ class DiscordNotificationSettings extends Model
|
||||
'backup_failure_discord_notifications',
|
||||
'scheduled_task_success_discord_notifications',
|
||||
'scheduled_task_failure_discord_notifications',
|
||||
'docker_cleanup_discord_notifications',
|
||||
'docker_cleanup_success_discord_notifications',
|
||||
'docker_cleanup_failure_discord_notifications',
|
||||
'server_disk_usage_discord_notifications',
|
||||
'server_reachable_discord_notifications',
|
||||
'server_unreachable_discord_notifications',
|
||||
|
||||
@@ -6,7 +6,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class DockerCleanupExecution extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'server_id',
|
||||
'status',
|
||||
'message',
|
||||
'cleanup_log',
|
||||
'finished_at',
|
||||
];
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
|
||||
@@ -34,7 +34,11 @@ class EmailNotificationSettings extends Model
|
||||
'backup_failure_email_notifications',
|
||||
'scheduled_task_success_email_notifications',
|
||||
'scheduled_task_failure_email_notifications',
|
||||
'docker_cleanup_success_email_notifications',
|
||||
'docker_cleanup_failure_email_notifications',
|
||||
'server_disk_usage_email_notifications',
|
||||
'server_reachable_email_notifications',
|
||||
'server_unreachable_email_notifications',
|
||||
'server_patch_email_notifications',
|
||||
'traefik_outdated_email_notifications',
|
||||
];
|
||||
|
||||
@@ -25,7 +25,10 @@ class Environment extends BaseModel
|
||||
use HasFactory;
|
||||
use HasSafeStringAttribute;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,25 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
||||
class GithubApp extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'organization',
|
||||
'api_url',
|
||||
'html_url',
|
||||
'custom_user',
|
||||
'custom_port',
|
||||
'app_id',
|
||||
'installation_id',
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'webhook_secret',
|
||||
'is_system_wide',
|
||||
'is_public',
|
||||
'contents',
|
||||
'metadata',
|
||||
'pull_requests',
|
||||
'administration',
|
||||
];
|
||||
|
||||
protected $appends = ['type'];
|
||||
|
||||
@@ -92,7 +110,7 @@ class GithubApp extends BaseModel
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if ($this->getMorphClass() === \App\Models\GithubApp::class) {
|
||||
if ($this->getMorphClass() === GithubApp::class) {
|
||||
return 'github';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,6 +4,24 @@ namespace App\Models;
|
||||
|
||||
class GitlabApp extends BaseModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'organization',
|
||||
'api_url',
|
||||
'html_url',
|
||||
'custom_port',
|
||||
'custom_user',
|
||||
'is_system_wide',
|
||||
'is_public',
|
||||
'app_id',
|
||||
'app_secret',
|
||||
'oauth_id',
|
||||
'group_name',
|
||||
'public_key',
|
||||
'webhook_token',
|
||||
'deploy_key_id',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'webhook_token',
|
||||
'app_secret',
|
||||
|
||||
@@ -9,7 +9,43 @@ use Spatie\Url\Url;
|
||||
|
||||
class InstanceSettings extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'public_ipv4',
|
||||
'public_ipv6',
|
||||
'fqdn',
|
||||
'public_port_min',
|
||||
'public_port_max',
|
||||
'do_not_track',
|
||||
'is_auto_update_enabled',
|
||||
'is_registration_enabled',
|
||||
'next_channel',
|
||||
'smtp_enabled',
|
||||
'smtp_from_address',
|
||||
'smtp_from_name',
|
||||
'smtp_recipients',
|
||||
'smtp_host',
|
||||
'smtp_port',
|
||||
'smtp_encryption',
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'smtp_timeout',
|
||||
'resend_enabled',
|
||||
'resend_api_key',
|
||||
'is_dns_validation_enabled',
|
||||
'custom_dns_servers',
|
||||
'instance_name',
|
||||
'is_api_enabled',
|
||||
'allowed_ips',
|
||||
'auto_update_frequency',
|
||||
'update_check_frequency',
|
||||
'new_version_available',
|
||||
'instance_timezone',
|
||||
'helper_version',
|
||||
'disable_two_step_confirmation',
|
||||
'is_sponsorship_popup_enabled',
|
||||
'dev_helper_version',
|
||||
'is_wire_navigate_enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'smtp_enabled' => 'boolean',
|
||||
|
||||
@@ -20,7 +20,18 @@ class LocalFileVolume extends BaseModel
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'fs_path',
|
||||
'mount_path',
|
||||
'content',
|
||||
'resource_type',
|
||||
'resource_id',
|
||||
'is_directory',
|
||||
'chown',
|
||||
'chmod',
|
||||
'is_based_on_git',
|
||||
'is_preview_suffix_enabled',
|
||||
];
|
||||
|
||||
public $appends = ['is_binary'];
|
||||
|
||||
|
||||
@@ -7,7 +7,15 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class LocalPersistentVolume extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'mount_path',
|
||||
'host_path',
|
||||
'container_id',
|
||||
'resource_type',
|
||||
'resource_id',
|
||||
'is_preview_suffix_enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_preview_suffix_enabled' => 'boolean',
|
||||
|
||||
@@ -24,7 +24,10 @@ class Project extends BaseModel
|
||||
use HasFactory;
|
||||
use HasSafeStringAttribute;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get query builder for projects owned by current team.
|
||||
@@ -48,10 +51,10 @@ class Project extends BaseModel
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($project) {
|
||||
ProjectSetting::create([
|
||||
ProjectSetting::forceCreate([
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
Environment::create([
|
||||
Environment::forceCreate([
|
||||
'name' => 'production',
|
||||
'project_id' => $project->id,
|
||||
'uuid' => (string) new Cuid2,
|
||||
|
||||
@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectSetting extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [];
|
||||
|
||||
public function project()
|
||||
{
|
||||
|
||||
@@ -25,7 +25,8 @@ class PushoverNotificationSettings extends Model
|
||||
'backup_failure_pushover_notifications',
|
||||
'scheduled_task_success_pushover_notifications',
|
||||
'scheduled_task_failure_pushover_notifications',
|
||||
'docker_cleanup_pushover_notifications',
|
||||
'docker_cleanup_success_pushover_notifications',
|
||||
'docker_cleanup_failure_pushover_notifications',
|
||||
'server_disk_usage_pushover_notifications',
|
||||
'server_reachable_pushover_notifications',
|
||||
'server_unreachable_pushover_notifications',
|
||||
|
||||
@@ -12,7 +12,17 @@ class S3Storage extends BaseModel
|
||||
{
|
||||
use HasFactory, HasSafeStringAttribute;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'region',
|
||||
'key',
|
||||
'secret',
|
||||
'bucket',
|
||||
'endpoint',
|
||||
'is_usable',
|
||||
'unusable_email_sent',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_usable' => 'boolean',
|
||||
|
||||
@@ -8,7 +8,25 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class ScheduledDatabaseBackup extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'description',
|
||||
'enabled',
|
||||
'save_s3',
|
||||
'frequency',
|
||||
'database_backup_retention_amount_locally',
|
||||
'database_type',
|
||||
'database_id',
|
||||
's3_storage_id',
|
||||
'databases_to_backup',
|
||||
'dump_all',
|
||||
'database_backup_retention_days_locally',
|
||||
'database_backup_retention_max_storage_locally',
|
||||
'database_backup_retention_amount_s3',
|
||||
'database_backup_retention_days_s3',
|
||||
'database_backup_retention_max_storage_s3',
|
||||
'timeout',
|
||||
'disable_local_backup',
|
||||
];
|
||||
|
||||
public static function ownedByCurrentTeam()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,17 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ScheduledDatabaseBackupExecution extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'status',
|
||||
'message',
|
||||
'size',
|
||||
'filename',
|
||||
'database_name',
|
||||
'finished_at',
|
||||
'local_storage_deleted',
|
||||
's3_storage_deleted',
|
||||
's3_uploaded',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
|
||||
@@ -29,7 +29,14 @@ class ScheduledTask extends BaseModel
|
||||
use HasFactory;
|
||||
use HasSafeStringAttribute;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'enabled',
|
||||
'name',
|
||||
'command',
|
||||
'frequency',
|
||||
'container',
|
||||
'timeout',
|
||||
];
|
||||
|
||||
public static function ownedByCurrentTeamAPI(int $teamId)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,15 @@ use OpenApi\Attributes as OA;
|
||||
)]
|
||||
class ScheduledTaskExecution extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'status',
|
||||
'message',
|
||||
'finished_at',
|
||||
'started_at',
|
||||
'retry_count',
|
||||
'duration',
|
||||
'error_details',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ use OpenApi\Attributes as OA;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Spatie\Url\Url;
|
||||
use Stevebauman\Purify\Facades\Purify;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -142,19 +143,19 @@ class Server extends BaseModel
|
||||
}
|
||||
});
|
||||
static::created(function ($server) {
|
||||
ServerSetting::create([
|
||||
ServerSetting::forceCreate([
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
if ($server->id === 0) {
|
||||
if ($server->isSwarm()) {
|
||||
SwarmDocker::create([
|
||||
SwarmDocker::forceCreate([
|
||||
'id' => 0,
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify-overlay',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
} else {
|
||||
StandaloneDocker::create([
|
||||
StandaloneDocker::forceCreate([
|
||||
'id' => 0,
|
||||
'name' => 'coolify',
|
||||
'network' => 'coolify',
|
||||
@@ -163,13 +164,14 @@ class Server extends BaseModel
|
||||
}
|
||||
} else {
|
||||
if ($server->isSwarm()) {
|
||||
SwarmDocker::create([
|
||||
SwarmDocker::forceCreate([
|
||||
'name' => 'coolify-overlay',
|
||||
'network' => 'coolify-overlay',
|
||||
'server_id' => $server->id,
|
||||
]);
|
||||
} else {
|
||||
$standaloneDocker = new StandaloneDocker([
|
||||
$standaloneDocker = new StandaloneDocker;
|
||||
$standaloneDocker->forceFill([
|
||||
'name' => 'coolify',
|
||||
'uuid' => (string) new Cuid2,
|
||||
'network' => 'coolify',
|
||||
@@ -265,14 +267,12 @@ class Server extends BaseModel
|
||||
'server_metadata',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
use HasSafeStringAttribute;
|
||||
|
||||
public function setValidationLogsAttribute($value): void
|
||||
{
|
||||
$this->attributes['validation_logs'] = $value !== null
|
||||
? \Stevebauman\Purify\Facades\Purify::config('validation_logs')->clean($value)
|
||||
? Purify::config('validation_logs')->clean($value)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,53 @@ use OpenApi\Attributes as OA;
|
||||
)]
|
||||
class ServerSetting extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'is_swarm_manager',
|
||||
'is_jump_server',
|
||||
'is_build_server',
|
||||
'is_reachable',
|
||||
'is_usable',
|
||||
'wildcard_domain',
|
||||
'is_cloudflare_tunnel',
|
||||
'is_logdrain_newrelic_enabled',
|
||||
'logdrain_newrelic_license_key',
|
||||
'logdrain_newrelic_base_uri',
|
||||
'is_logdrain_highlight_enabled',
|
||||
'logdrain_highlight_project_id',
|
||||
'is_logdrain_axiom_enabled',
|
||||
'logdrain_axiom_dataset_name',
|
||||
'logdrain_axiom_api_key',
|
||||
'is_swarm_worker',
|
||||
'is_logdrain_custom_enabled',
|
||||
'logdrain_custom_config',
|
||||
'logdrain_custom_config_parser',
|
||||
'concurrent_builds',
|
||||
'dynamic_timeout',
|
||||
'force_disabled',
|
||||
'is_metrics_enabled',
|
||||
'generate_exact_labels',
|
||||
'force_docker_cleanup',
|
||||
'docker_cleanup_frequency',
|
||||
'docker_cleanup_threshold',
|
||||
'server_timezone',
|
||||
'delete_unused_volumes',
|
||||
'delete_unused_networks',
|
||||
'is_sentinel_enabled',
|
||||
'sentinel_token',
|
||||
'sentinel_metrics_refresh_rate_seconds',
|
||||
'sentinel_metrics_history_days',
|
||||
'sentinel_push_interval_seconds',
|
||||
'sentinel_custom_url',
|
||||
'server_disk_usage_notification_threshold',
|
||||
'is_sentinel_debug_enabled',
|
||||
'server_disk_usage_check_frequency',
|
||||
'is_terminal_enabled',
|
||||
'deployment_queue_limit',
|
||||
'disable_application_image_retention',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'force_disabled' => 'boolean',
|
||||
'force_docker_cleanup' => 'boolean',
|
||||
'docker_cleanup_threshold' => 'integer',
|
||||
'sentinel_token' => 'encrypted',
|
||||
|
||||
+13
-2
@@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Storage;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
#[OA\Schema(
|
||||
@@ -47,7 +48,17 @@ class Service extends BaseModel
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'docker_compose_raw',
|
||||
'docker_compose',
|
||||
'connect_to_docker_network',
|
||||
'service_type',
|
||||
'config_hash',
|
||||
'compose_parsing_version',
|
||||
'is_container_label_escape_enabled',
|
||||
];
|
||||
|
||||
protected $appends = ['server_status', 'status'];
|
||||
|
||||
@@ -1552,7 +1563,7 @@ class Service extends BaseModel
|
||||
// Generate SERVICE_NAME_* environment variables from docker-compose services
|
||||
if ($this->docker_compose) {
|
||||
try {
|
||||
$dockerCompose = \Symfony\Component\Yaml\Yaml::parse($this->docker_compose);
|
||||
$dockerCompose = Yaml::parse($this->docker_compose);
|
||||
$services = data_get($dockerCompose, 'services', []);
|
||||
foreach ($services as $serviceName => $_) {
|
||||
$envs->push('SERVICE_NAME_'.str($serviceName)->replace('-', '_')->replace('.', '_')->upper().'='.$serviceName);
|
||||
|
||||
@@ -5,12 +5,30 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class ServiceApplication extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'human_name',
|
||||
'description',
|
||||
'fqdn',
|
||||
'ports',
|
||||
'exposes',
|
||||
'status',
|
||||
'exclude_from_status',
|
||||
'required_fqdn',
|
||||
'image',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'is_gzip_enabled',
|
||||
'is_stripprefix_enabled',
|
||||
'last_online_at',
|
||||
'is_migrated',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -211,7 +229,7 @@ class ServiceApplication extends BaseModel
|
||||
return $this->service->getRequiredPort();
|
||||
}
|
||||
|
||||
$dockerCompose = \Symfony\Component\Yaml\Yaml::parse($dockerComposeRaw);
|
||||
$dockerCompose = Yaml::parse($dockerComposeRaw);
|
||||
$serviceConfig = data_get($dockerCompose, "services.{$this->name}");
|
||||
if (! $serviceConfig) {
|
||||
return $this->service->getRequiredPort();
|
||||
|
||||
@@ -9,7 +9,27 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'human_name',
|
||||
'description',
|
||||
'fqdn',
|
||||
'ports',
|
||||
'exposes',
|
||||
'status',
|
||||
'exclude_from_status',
|
||||
'image',
|
||||
'public_port',
|
||||
'is_public',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'is_gzip_enabled',
|
||||
'is_stripprefix_enabled',
|
||||
'last_online_at',
|
||||
'is_migrated',
|
||||
'custom_type',
|
||||
'public_port_timeout',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'public_port_timeout' => 'integer',
|
||||
|
||||
@@ -22,6 +22,9 @@ class SharedEnvironmentVariable extends Model
|
||||
'is_multiline',
|
||||
'is_literal',
|
||||
'is_shown_once',
|
||||
|
||||
// Metadata
|
||||
'version',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
||||
@@ -24,7 +24,8 @@ class SlackNotificationSettings extends Model
|
||||
'backup_failure_slack_notifications',
|
||||
'scheduled_task_success_slack_notifications',
|
||||
'scheduled_task_failure_slack_notifications',
|
||||
'docker_cleanup_slack_notifications',
|
||||
'docker_cleanup_success_slack_notifications',
|
||||
'docker_cleanup_failure_slack_notifications',
|
||||
'server_disk_usage_slack_notifications',
|
||||
'server_reachable_slack_notifications',
|
||||
'server_unreachable_slack_notifications',
|
||||
|
||||
@@ -13,12 +13,39 @@ class StandaloneClickhouse extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'clickhouse_admin_user',
|
||||
'clickhouse_admin_password',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'custom_docker_run_options',
|
||||
'clickhouse_db',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'clickhouse_password' => 'encrypted',
|
||||
'clickhouse_admin_password' => 'encrypted',
|
||||
'public_port_timeout' => 'integer',
|
||||
'restart_count' => 'integer',
|
||||
'last_restart_at' => 'datetime',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Jobs\ConnectProxyToNetworksJob;
|
||||
use App\Support\ValidationPatterns;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
@@ -11,20 +12,33 @@ class StandaloneDocker extends BaseModel
|
||||
use HasFactory;
|
||||
use HasSafeStringAttribute;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'network',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
static::created(function ($newStandaloneDocker) {
|
||||
$server = $newStandaloneDocker->server;
|
||||
$safeNetwork = escapeshellarg($newStandaloneDocker->network);
|
||||
instant_remote_process([
|
||||
"docker network inspect $newStandaloneDocker->network >/dev/null 2>&1 || docker network create --driver overlay --attachable $newStandaloneDocker->network >/dev/null",
|
||||
"docker network inspect {$safeNetwork} >/dev/null 2>&1 || docker network create --driver overlay --attachable {$safeNetwork} >/dev/null",
|
||||
], $server, false);
|
||||
ConnectProxyToNetworksJob::dispatchSync($server);
|
||||
});
|
||||
}
|
||||
|
||||
public function setNetworkAttribute(string $value): void
|
||||
{
|
||||
if (! ValidationPatterns::isValidDockerNetwork($value)) {
|
||||
throw new \InvalidArgumentException('Invalid Docker network name. Must start with alphanumeric and contain only alphanumeric characters, dots, hyphens, and underscores.');
|
||||
}
|
||||
|
||||
$this->attributes['network'] = $value;
|
||||
}
|
||||
|
||||
public function applications()
|
||||
{
|
||||
return $this->morphMany(Application::class, 'destination');
|
||||
|
||||
@@ -13,7 +13,33 @@ class StandaloneDragonfly extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'dragonfly_password',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -13,7 +13,34 @@ class StandaloneKeydb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'keydb_password',
|
||||
'keydb_conf',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
|
||||
|
||||
|
||||
@@ -14,7 +14,36 @@ class StandaloneMariadb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'mariadb_root_password',
|
||||
'mariadb_user',
|
||||
'mariadb_password',
|
||||
'mariadb_database',
|
||||
'mariadb_conf',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'is_log_drain_enabled',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -13,7 +13,37 @@ class StandaloneMongodb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'mongo_conf',
|
||||
'mongo_initdb_root_username',
|
||||
'mongo_initdb_root_password',
|
||||
'mongo_initdb_database',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'ssl_mode',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -13,7 +13,38 @@ class StandaloneMysql extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'mysql_root_password',
|
||||
'mysql_user',
|
||||
'mysql_password',
|
||||
'mysql_database',
|
||||
'mysql_conf',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'ssl_mode',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -13,7 +13,40 @@ class StandalonePostgresql extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'postgres_user',
|
||||
'postgres_password',
|
||||
'postgres_db',
|
||||
'postgres_initdb_args',
|
||||
'postgres_host_auth_method',
|
||||
'postgres_conf',
|
||||
'init_scripts',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'ssl_mode',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -13,7 +13,33 @@ class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'redis_conf',
|
||||
'status',
|
||||
'image',
|
||||
'is_public',
|
||||
'public_port',
|
||||
'ports_mappings',
|
||||
'limits_memory',
|
||||
'limits_memory_swap',
|
||||
'limits_memory_swappiness',
|
||||
'limits_memory_reservation',
|
||||
'limits_cpus',
|
||||
'limits_cpuset',
|
||||
'limits_cpu_shares',
|
||||
'started_at',
|
||||
'restart_count',
|
||||
'last_restart_at',
|
||||
'last_restart_type',
|
||||
'last_online_at',
|
||||
'public_port_timeout',
|
||||
'enable_ssl',
|
||||
'is_log_drain_enabled',
|
||||
'is_include_timestamps',
|
||||
'custom_docker_run_options',
|
||||
];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
|
||||
@@ -6,7 +6,18 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Subscription extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'stripe_invoice_paid',
|
||||
'stripe_subscription_id',
|
||||
'stripe_customer_id',
|
||||
'stripe_cancel_at_period_end',
|
||||
'stripe_plan_id',
|
||||
'stripe_feedback',
|
||||
'stripe_comment',
|
||||
'stripe_trial_already_ended',
|
||||
'stripe_past_due',
|
||||
'stripe_refunded_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
|
||||
@@ -2,9 +2,23 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Support\ValidationPatterns;
|
||||
|
||||
class SwarmDocker extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'network',
|
||||
];
|
||||
|
||||
public function setNetworkAttribute(string $value): void
|
||||
{
|
||||
if (! ValidationPatterns::isValidDockerNetwork($value)) {
|
||||
throw new \InvalidArgumentException('Invalid Docker network name. Must start with alphanumeric and contain only alphanumeric characters, dots, hyphens, and underscores.');
|
||||
}
|
||||
|
||||
$this->attributes['network'] = $value;
|
||||
}
|
||||
|
||||
public function applications()
|
||||
{
|
||||
|
||||
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user