From 5c4a265542df43f0ad4091c5866f3f7e4fbb2851 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:15:31 +0200 Subject: [PATCH] refactor(validation): implement centralized validation patterns across components - Introduced `ValidationPatterns` class to standardize validation rules and messages for various fields across multiple components. - Updated components including `General`, `StackForm`, `Create`, and `Show` to utilize the new validation patterns, ensuring consistent validation logic. - Enhanced error messages for required fields and added regex validation for names and descriptions to improve user feedback. - Adjusted styling in the `create.blade.php` view for better visual hierarchy. --- app/Livewire/Project/Application/General.php | 130 +++++++++++------- .../Project/Database/Clickhouse/General.php | 48 +++++-- .../Project/Database/Dragonfly/General.php | 46 +++++-- .../Project/Database/Keydb/General.php | 50 +++++-- .../Project/Database/Mariadb/General.php | 54 +++++--- .../Project/Database/Mongodb/General.php | 54 +++++--- .../Project/Database/Mysql/General.php | 57 +++++--- .../Project/Database/Postgresql/General.php | 60 +++++--- .../Project/Database/Redis/General.php | 48 +++++-- app/Livewire/Project/Service/StackForm.php | 41 ++++-- app/Livewire/Security/PrivateKey/Create.php | 24 +++- app/Livewire/Security/PrivateKey/Show.php | 30 +++- app/Livewire/Server/New/ByIp.php | 59 ++++++-- app/Livewire/Server/Show.php | 77 +++++++---- app/Livewire/Storage/Create.php | 42 ++++-- app/Livewire/Storage/Form.php | 46 +++++-- app/Livewire/Team/Create.php | 17 ++- app/Livewire/Team/Index.php | 24 +++- .../security/private-key/create.blade.php | 2 +- 19 files changed, 659 insertions(+), 250 deletions(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 6ee9dcaad..e7d464ba4 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\GenerateConfig; use App\Models\Application; +use App\Support\ValidationPatterns; use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; @@ -52,52 +53,89 @@ class General extends Component 'configurationChanged' => '$refresh', ]; - protected $rules = [ - 'application.name' => 'required', - 'application.description' => 'nullable', - 'application.fqdn' => 'nullable', - 'application.git_repository' => 'required', - 'application.git_branch' => 'required', - 'application.git_commit_sha' => 'nullable', - 'application.install_command' => 'nullable', - 'application.build_command' => 'nullable', - 'application.start_command' => 'nullable', - 'application.build_pack' => 'required', - 'application.static_image' => 'required', - 'application.base_directory' => 'required', - 'application.publish_directory' => 'nullable', - 'application.ports_exposes' => 'required', - 'application.ports_mappings' => 'nullable', - 'application.custom_network_aliases' => 'nullable', - 'application.dockerfile' => 'nullable', - 'application.docker_registry_image_name' => 'nullable', - 'application.docker_registry_image_tag' => 'nullable', - 'application.dockerfile_location' => 'nullable', - 'application.docker_compose_location' => 'nullable', - 'application.docker_compose' => 'nullable', - 'application.docker_compose_raw' => 'nullable', - 'application.dockerfile_target_build' => 'nullable', - 'application.docker_compose_custom_start_command' => 'nullable', - 'application.docker_compose_custom_build_command' => 'nullable', - 'application.custom_labels' => 'nullable', - 'application.custom_docker_run_options' => 'nullable', - 'application.pre_deployment_command' => 'nullable', - 'application.pre_deployment_command_container' => 'nullable', - 'application.post_deployment_command' => 'nullable', - 'application.post_deployment_command_container' => 'nullable', - 'application.custom_nginx_configuration' => 'nullable', - 'application.settings.is_static' => 'boolean|required', - 'application.settings.is_spa' => 'boolean|required', - 'application.settings.is_build_server_enabled' => 'boolean|required', - 'application.settings.is_container_label_escape_enabled' => 'boolean|required', - 'application.settings.is_container_label_readonly_enabled' => 'boolean|required', - 'application.settings.is_preserve_repository_enabled' => 'boolean|required', - 'application.is_http_basic_auth_enabled' => 'boolean|required', - 'application.http_basic_auth_username' => 'string|nullable', - 'application.http_basic_auth_password' => 'string|nullable', - 'application.watch_paths' => 'nullable', - 'application.redirect' => 'string|required', - ]; + protected function rules(): array + { + return [ + 'application.name' => ValidationPatterns::nameRules(), + 'application.description' => ValidationPatterns::descriptionRules(), + 'application.fqdn' => 'nullable', + 'application.git_repository' => 'required', + 'application.git_branch' => 'required', + 'application.git_commit_sha' => 'nullable', + 'application.install_command' => 'nullable', + 'application.build_command' => 'nullable', + 'application.start_command' => 'nullable', + 'application.build_pack' => 'required', + 'application.static_image' => 'required', + 'application.base_directory' => 'required', + 'application.publish_directory' => 'nullable', + 'application.ports_exposes' => 'required', + 'application.ports_mappings' => 'nullable', + 'application.custom_network_aliases' => 'nullable', + 'application.dockerfile' => 'nullable', + 'application.docker_registry_image_name' => 'nullable', + 'application.docker_registry_image_tag' => 'nullable', + 'application.dockerfile_location' => 'nullable', + 'application.docker_compose_location' => 'nullable', + 'application.docker_compose' => 'nullable', + 'application.docker_compose_raw' => 'nullable', + 'application.dockerfile_target_build' => 'nullable', + 'application.docker_compose_custom_start_command' => 'nullable', + 'application.docker_compose_custom_build_command' => 'nullable', + 'application.custom_labels' => 'nullable', + 'application.custom_docker_run_options' => 'nullable', + 'application.pre_deployment_command' => 'nullable', + 'application.pre_deployment_command_container' => 'nullable', + 'application.post_deployment_command' => 'nullable', + 'application.post_deployment_command_container' => 'nullable', + 'application.custom_nginx_configuration' => 'nullable', + 'application.settings.is_static' => 'boolean|required', + 'application.settings.is_spa' => 'boolean|required', + 'application.settings.is_build_server_enabled' => 'boolean|required', + 'application.settings.is_container_label_escape_enabled' => 'boolean|required', + 'application.settings.is_container_label_readonly_enabled' => 'boolean|required', + 'application.settings.is_preserve_repository_enabled' => 'boolean|required', + 'application.is_http_basic_auth_enabled' => 'boolean|required', + 'application.http_basic_auth_username' => 'string|nullable', + 'application.http_basic_auth_password' => 'string|nullable', + 'application.watch_paths' => 'nullable', + 'application.redirect' => 'string|required', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'application.name.required' => 'The Name field is required.', + 'application.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'application.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'application.git_repository.required' => 'The Git Repository field is required.', + 'application.git_branch.required' => 'The Git Branch field is required.', + 'application.build_pack.required' => 'The Build Pack field is required.', + 'application.static_image.required' => 'The Static Image field is required.', + 'application.base_directory.required' => 'The Base Directory field is required.', + 'application.ports_exposes.required' => 'The Exposed Ports field is required.', + 'application.settings.is_static.required' => 'The Static setting is required.', + 'application.settings.is_static.boolean' => 'The Static setting must be true or false.', + 'application.settings.is_spa.required' => 'The SPA setting is required.', + 'application.settings.is_spa.boolean' => 'The SPA setting must be true or false.', + 'application.settings.is_build_server_enabled.required' => 'The Build Server setting is required.', + 'application.settings.is_build_server_enabled.boolean' => 'The Build Server setting must be true or false.', + 'application.settings.is_container_label_escape_enabled.required' => 'The Container Label Escape setting is required.', + 'application.settings.is_container_label_escape_enabled.boolean' => 'The Container Label Escape setting must be true or false.', + 'application.settings.is_container_label_readonly_enabled.required' => 'The Container Label Readonly setting is required.', + 'application.settings.is_container_label_readonly_enabled.boolean' => 'The Container Label Readonly setting must be true or false.', + 'application.settings.is_preserve_repository_enabled.required' => 'The Preserve Repository setting is required.', + 'application.settings.is_preserve_repository_enabled.boolean' => 'The Preserve Repository setting must be true or false.', + 'application.is_http_basic_auth_enabled.required' => 'The HTTP Basic Auth setting is required.', + 'application.is_http_basic_auth_enabled.boolean' => 'The HTTP Basic Auth setting must be true or false.', + 'application.redirect.required' => 'The Redirect setting is required.', + 'application.redirect.string' => 'The Redirect setting must be a string.', + ] + ); + } protected $validationAttributes = [ 'application.name' => 'name', diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index 2d39c5151..f6eb54a50 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -6,9 +6,9 @@ use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; use App\Models\Server; use App\Models\StandaloneClickhouse; +use App\Support\ValidationPatterns; use Exception; use Illuminate\Support\Facades\Auth; -use Livewire\Attributes\Validate; use Livewire\Component; class General extends Component @@ -17,40 +17,28 @@ class General extends Component public StandaloneClickhouse $database; - #[Validate(['required', 'string'])] public string $name; - #[Validate(['nullable', 'string'])] public ?string $description = null; - #[Validate(['required', 'string'])] public string $clickhouseAdminUser; - #[Validate(['required', 'string'])] public string $clickhouseAdminPassword; - #[Validate(['required', 'string'])] public string $image; - #[Validate(['nullable', 'string'])] public ?string $portsMappings = null; - #[Validate(['nullable', 'boolean'])] public ?bool $isPublic = null; - #[Validate(['nullable', 'integer'])] public ?int $publicPort = null; - #[Validate(['nullable', 'string'])] public ?string $customDockerRunOptions = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrl = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrlPublic = null; - #[Validate(['nullable', 'boolean'])] public bool $isLogDrainEnabled = false; public function getListeners() @@ -72,6 +60,40 @@ class General extends Component } } + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'clickhouseAdminUser' => 'required|string', + 'clickhouseAdminPassword' => 'required|string', + 'image' => 'required|string', + 'portsMappings' => 'nullable|string', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'customDockerRunOptions' => 'nullable|string', + 'dbUrl' => 'nullable|string', + 'dbUrlPublic' => 'nullable|string', + 'isLogDrainEnabled' => 'nullable|boolean', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'clickhouseAdminUser.required' => 'The Admin User field is required.', + 'clickhouseAdminUser.string' => 'The Admin User must be a string.', + 'clickhouseAdminPassword.required' => 'The Admin Password field is required.', + 'clickhouseAdminPassword.string' => 'The Admin 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.', + ] + ); + } + public function syncData(bool $toModel = false) { if ($toModel) { diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index 0fffbef31..2a6fc99a9 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -8,10 +8,10 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneDragonfly; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; -use Livewire\Attributes\Validate; use Livewire\Component; class General extends Component @@ -20,42 +20,30 @@ class General extends Component public StandaloneDragonfly $database; - #[Validate(['required', 'string'])] public string $name; - #[Validate(['nullable', 'string'])] public ?string $description = null; - #[Validate(['required', 'string'])] public string $dragonflyPassword; - #[Validate(['required', 'string'])] public string $image; - #[Validate(['nullable', 'string'])] public ?string $portsMappings = null; - #[Validate(['nullable', 'boolean'])] public ?bool $isPublic = null; - #[Validate(['nullable', 'integer'])] public ?int $publicPort = null; - #[Validate(['nullable', 'string'])] public ?string $customDockerRunOptions = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrl = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrlPublic = null; - #[Validate(['nullable', 'boolean'])] public bool $isLogDrainEnabled = false; public ?Carbon $certificateValidUntil = null; - #[Validate(['nullable', 'boolean'])] public bool $enable_ssl = false; public function getListeners() @@ -85,6 +73,38 @@ class General extends Component } } + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'dragonflyPassword' => 'required|string', + 'image' => 'required|string', + 'portsMappings' => 'nullable|string', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'customDockerRunOptions' => 'nullable|string', + 'dbUrl' => 'nullable|string', + 'dbUrlPublic' => 'nullable|string', + 'isLogDrainEnabled' => 'nullable|boolean', + 'enable_ssl' => 'nullable|boolean', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + '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.', + ] + ); + } + public function syncData(bool $toModel = false) { if ($toModel) { diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index cfc22aedc..6ddffc05a 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -8,10 +8,10 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneKeydb; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; -use Livewire\Attributes\Validate; use Livewire\Component; class General extends Component @@ -20,45 +20,32 @@ class General extends Component public StandaloneKeydb $database; - #[Validate(['required', 'string'])] public string $name; - #[Validate(['nullable', 'string'])] public ?string $description = null; - #[Validate(['nullable', 'string'])] public ?string $keydbConf = null; - #[Validate(['required', 'string'])] public string $keydbPassword; - #[Validate(['required', 'string'])] public string $image; - #[Validate(['nullable', 'string'])] public ?string $portsMappings = null; - #[Validate(['nullable', 'boolean'])] public ?bool $isPublic = null; - #[Validate(['nullable', 'integer'])] public ?int $publicPort = null; - #[Validate(['nullable', 'string'])] public ?string $customDockerRunOptions = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrl = null; - #[Validate(['nullable', 'string'])] public ?string $dbUrlPublic = null; - #[Validate(['nullable', 'boolean'])] public bool $isLogDrainEnabled = false; public ?Carbon $certificateValidUntil = null; - #[Validate(['boolean'])] public bool $enable_ssl = false; public function getListeners() @@ -89,6 +76,41 @@ class General extends Component } } + protected function rules(): array + { + $baseRules = [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'keydbConf' => 'nullable|string', + 'keydbPassword' => 'required|string', + 'image' => 'required|string', + 'portsMappings' => 'nullable|string', + 'isPublic' => 'nullable|boolean', + 'publicPort' => 'nullable|integer', + 'customDockerRunOptions' => 'nullable|string', + 'dbUrl' => 'nullable|string', + 'dbUrlPublic' => 'nullable|string', + 'isLogDrainEnabled' => 'nullable|boolean', + 'enable_ssl' => 'boolean', + ]; + + return $baseRules; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + '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.', + ] + ); + } + public function syncData(bool $toModel = false) { if ($toModel) { diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index 174f907c8..202056311 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -8,6 +8,7 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneMariadb; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; @@ -37,22 +38,43 @@ class General extends Component ]; } - protected $rules = [ - 'database.name' => 'required', - 'database.description' => 'nullable', - 'database.mariadb_root_password' => 'required', - 'database.mariadb_user' => 'required', - 'database.mariadb_password' => 'required', - 'database.mariadb_database' => 'required', - 'database.mariadb_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - ]; + protected function rules(): array + { + return [ + 'database.name' => ValidationPatterns::nameRules(), + 'database.description' => ValidationPatterns::descriptionRules(), + 'database.mariadb_root_password' => 'required', + 'database.mariadb_user' => 'required', + 'database.mariadb_password' => 'required', + 'database.mariadb_database' => 'required', + 'database.mariadb_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'database.name.required' => 'The Name field is required.', + 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'database.mariadb_root_password.required' => 'The Root Password field is required.', + 'database.mariadb_user.required' => 'The MariaDB User field is required.', + 'database.mariadb_password.required' => 'The MariaDB Password field is required.', + 'database.mariadb_database.required' => 'The MariaDB Database field is required.', + 'database.image.required' => 'The Docker Image field is required.', + 'database.public_port.integer' => 'The Public Port must be an integer.', + ] + ); + } protected $validationAttributes = [ 'database.name' => 'Name', diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index 2ac6e43b7..dc90b6b55 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -8,6 +8,7 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneMongodb; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; @@ -37,22 +38,43 @@ class General extends Component ]; } - protected $rules = [ - 'database.name' => 'required', - 'database.description' => 'nullable', - 'database.mongo_conf' => 'nullable', - 'database.mongo_initdb_root_username' => 'required', - 'database.mongo_initdb_root_password' => 'required', - 'database.mongo_initdb_database' => 'required', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full', - ]; + protected function rules(): array + { + return [ + 'database.name' => ValidationPatterns::nameRules(), + 'database.description' => ValidationPatterns::descriptionRules(), + 'database.mongo_conf' => 'nullable', + 'database.mongo_initdb_root_username' => 'required', + 'database.mongo_initdb_root_password' => 'required', + 'database.mongo_initdb_database' => 'required', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'database.name.required' => 'The Name field is required.', + 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'database.mongo_initdb_root_username.required' => 'The Root Username field is required.', + 'database.mongo_initdb_root_password.required' => 'The Root Password field is required.', + 'database.mongo_initdb_database.required' => 'The MongoDB Database field is required.', + 'database.image.required' => 'The Docker Image field is required.', + 'database.public_port.integer' => 'The Public Port must be an integer.', + 'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.', + ] + ); + } protected $validationAttributes = [ 'database.name' => 'Name', diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index ea0ea4691..4753141ec 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -8,6 +8,7 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneMysql; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; @@ -37,23 +38,45 @@ class General extends Component ]; } - protected $rules = [ - 'database.name' => 'required', - 'database.description' => 'nullable', - 'database.mysql_root_password' => 'required', - 'database.mysql_user' => 'required', - 'database.mysql_password' => 'required', - 'database.mysql_database' => 'required', - 'database.mysql_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY', - ]; + protected function rules(): array + { + return [ + 'database.name' => ValidationPatterns::nameRules(), + 'database.description' => ValidationPatterns::descriptionRules(), + 'database.mysql_root_password' => 'required', + 'database.mysql_user' => 'required', + 'database.mysql_password' => 'required', + 'database.mysql_database' => 'required', + 'database.mysql_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'database.name.required' => 'The Name field is required.', + 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'database.mysql_root_password.required' => 'The Root Password field is required.', + 'database.mysql_user.required' => 'The MySQL User field is required.', + 'database.mysql_password.required' => 'The MySQL Password field is required.', + 'database.mysql_database.required' => 'The MySQL Database field is required.', + 'database.image.required' => 'The Docker Image field is required.', + 'database.public_port.integer' => 'The Public Port must be an integer.', + 'database.ssl_mode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.', + ] + ); + } protected $validationAttributes = [ 'database.name' => 'Name', diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index d512445b7..ae49ae4d2 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -8,6 +8,7 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandalonePostgresql; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; @@ -41,25 +42,46 @@ class General extends Component ]; } - protected $rules = [ - 'database.name' => 'required', - 'database.description' => 'nullable', - 'database.postgres_user' => 'required', - 'database.postgres_password' => 'required', - 'database.postgres_db' => 'required', - 'database.postgres_initdb_args' => 'nullable', - 'database.postgres_host_auth_method' => 'nullable', - 'database.postgres_conf' => 'nullable', - 'database.init_scripts' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'database.enable_ssl' => 'boolean', - 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', - ]; + protected function rules(): array + { + return [ + 'database.name' => ValidationPatterns::nameRules(), + 'database.description' => ValidationPatterns::descriptionRules(), + 'database.postgres_user' => 'required', + 'database.postgres_password' => 'required', + 'database.postgres_db' => 'required', + 'database.postgres_initdb_args' => 'nullable', + 'database.postgres_host_auth_method' => 'nullable', + 'database.postgres_conf' => 'nullable', + 'database.init_scripts' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'database.name.required' => 'The Name field is required.', + 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'database.postgres_user.required' => 'The Postgres User field is required.', + 'database.postgres_password.required' => 'The Postgres Password field is required.', + 'database.postgres_db.required' => 'The Postgres Database field is required.', + 'database.image.required' => 'The Docker Image field is required.', + 'database.public_port.integer' => 'The Public Port must be an integer.', + 'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.', + ] + ); + } protected $validationAttributes = [ 'database.name' => 'Name', diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index f03f1256d..95a72ea6a 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -8,6 +8,7 @@ use App\Helpers\SslHelper; use App\Models\Server; use App\Models\SslCertificate; use App\Models\StandaloneRedis; +use App\Support\ValidationPatterns; use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; @@ -42,20 +43,39 @@ class General extends Component ]; } - protected $rules = [ - 'database.name' => 'required', - 'database.description' => 'nullable', - 'database.redis_conf' => 'nullable', - 'database.image' => 'required', - 'database.ports_mappings' => 'nullable', - 'database.is_public' => 'nullable|boolean', - 'database.public_port' => 'nullable|integer', - 'database.is_log_drain_enabled' => 'nullable|boolean', - 'database.custom_docker_run_options' => 'nullable', - 'redis_username' => 'required', - 'redis_password' => 'required', - 'database.enable_ssl' => 'boolean', - ]; + protected function rules(): array + { + return [ + 'database.name' => ValidationPatterns::nameRules(), + 'database.description' => ValidationPatterns::descriptionRules(), + 'database.redis_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', + 'redis_username' => 'required', + 'redis_password' => 'required', + 'database.enable_ssl' => 'boolean', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'database.name.required' => 'The Name field is required.', + 'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'database.image.required' => 'The Docker Image field is required.', + 'database.public_port.integer' => 'The Public Port must be an integer.', + 'redis_username.required' => 'The Redis Username field is required.', + 'redis_password.required' => 'The Redis Password field is required.', + ] + ); + } protected $validationAttributes = [ 'database.name' => 'Name', diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index a67bd9210..1961a7985 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Service; use App\Models\Service; +use App\Support\ValidationPatterns; use Illuminate\Support\Collection; use Livewire\Component; @@ -14,13 +15,38 @@ class StackForm extends Component protected $listeners = ['saveCompose']; - public $rules = [ - 'service.docker_compose_raw' => 'required', - 'service.docker_compose' => 'required', - 'service.name' => 'required', - 'service.description' => 'nullable', - 'service.connect_to_docker_network' => 'nullable', - ]; + protected function rules(): array + { + $baseRules = [ + 'service.docker_compose_raw' => 'required', + 'service.docker_compose' => 'required', + 'service.name' => ValidationPatterns::nameRules(), + 'service.description' => ValidationPatterns::descriptionRules(), + 'service.connect_to_docker_network' => 'nullable', + ]; + + // Add dynamic field rules + foreach ($this->fields ?? collect() as $key => $field) { + $rules = data_get($field, 'rules', 'nullable'); + $baseRules["fields.$key.value"] = $rules; + } + + return $baseRules; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'service.name.required' => 'The Name field is required.', + 'service.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'service.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'service.docker_compose_raw.required' => 'The Docker Compose Raw field is required.', + 'service.docker_compose.required' => 'The Docker Compose field is required.', + ] + ); + } public $validationAttributes = []; @@ -45,7 +71,6 @@ class StackForm extends Component 'customHelper' => $customHelper, ]); - $this->rules["fields.$key.value"] = $rules; $this->validationAttributes["fields.$key.value"] = $fieldKey; } } diff --git a/app/Livewire/Security/PrivateKey/Create.php b/app/Livewire/Security/PrivateKey/Create.php index 319cec192..ff196bd35 100644 --- a/app/Livewire/Security/PrivateKey/Create.php +++ b/app/Livewire/Security/PrivateKey/Create.php @@ -3,6 +3,7 @@ namespace App\Livewire\Security\PrivateKey; use App\Models\PrivateKey; +use App\Support\ValidationPatterns; use Livewire\Component; class Create extends Component @@ -17,10 +18,25 @@ class Create extends Component public ?string $publicKey = null; - protected $rules = [ - 'name' => 'required|string', - 'value' => 'required|string', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'value' => 'required|string', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'value.required' => 'The Private Key field is required.', + 'value.string' => 'The Private Key must be a valid string.', + ] + ); + } public function generateNewRSAKey() { diff --git a/app/Livewire/Security/PrivateKey/Show.php b/app/Livewire/Security/PrivateKey/Show.php index b9195b543..b78367464 100644 --- a/app/Livewire/Security/PrivateKey/Show.php +++ b/app/Livewire/Security/PrivateKey/Show.php @@ -3,6 +3,7 @@ namespace App\Livewire\Security\PrivateKey; use App\Models\PrivateKey; +use App\Support\ValidationPatterns; use Livewire\Component; class Show extends Component @@ -11,12 +12,29 @@ class Show extends Component public $public_key = 'Loading...'; - protected $rules = [ - 'private_key.name' => 'required|string', - 'private_key.description' => 'nullable|string', - 'private_key.private_key' => 'required|string', - 'private_key.is_git_related' => 'nullable|boolean', - ]; + protected function rules(): array + { + return [ + 'private_key.name' => ValidationPatterns::nameRules(), + 'private_key.description' => ValidationPatterns::descriptionRules(), + 'private_key.private_key' => 'required|string', + 'private_key.is_git_related' => 'nullable|boolean', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'private_key.name.required' => 'The Name field is required.', + 'private_key.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'private_key.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'private_key.private_key.required' => 'The Private Key field is required.', + 'private_key.private_key.string' => 'The Private Key must be a valid string.', + ] + ); + } protected $validationAttributes = [ 'private_key.name' => 'name', diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php index 5f60c5db5..6b4bfc15e 100644 --- a/app/Livewire/Server/New/ByIp.php +++ b/app/Livewire/Server/New/ByIp.php @@ -5,9 +5,9 @@ namespace App\Livewire\Server\New; use App\Enums\ProxyTypes; use App\Models\Server; use App\Models\Team; +use App\Support\ValidationPatterns; use Illuminate\Support\Collection; use Livewire\Attributes\Locked; -use Livewire\Attributes\Validate; use Livewire\Component; class ByIp extends Component @@ -18,43 +18,30 @@ class ByIp extends Component #[Locked] public $limit_reached; - #[Validate('nullable|integer', as: 'Private Key')] public ?int $private_key_id = null; - #[Validate('nullable|string', as: 'Private Key Name')] public $new_private_key_name; - #[Validate('nullable|string', as: 'Private Key Description')] public $new_private_key_description; - #[Validate('nullable|string', as: 'Private Key Value')] public $new_private_key_value; - #[Validate('required|string', as: 'Name')] public string $name; - #[Validate('nullable|string', as: 'Description')] public ?string $description = null; - #[Validate('required|string', as: 'IP Address/Domain')] public string $ip; - #[Validate('required|string', as: 'User')] public string $user = 'root'; - #[Validate('required|integer|between:1,65535', as: 'Port')] public int $port = 22; - #[Validate('required|boolean', as: 'Swarm Manager')] public bool $is_swarm_manager = false; - #[Validate('required|boolean', as: 'Swarm Worker')] public bool $is_swarm_worker = false; - #[Validate('nullable|integer', as: 'Swarm Cluster')] public $selected_swarm_cluster = null; - #[Validate('required|boolean', as: 'Build Server')] public bool $is_build_server = false; #[Locked] @@ -70,6 +57,50 @@ class ByIp extends Component } } + protected function rules(): array + { + return [ + 'private_key_id' => 'nullable|integer', + 'new_private_key_name' => 'nullable|string', + 'new_private_key_description' => 'nullable|string', + 'new_private_key_value' => 'nullable|string', + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'ip' => 'required|string', + 'user' => 'required|string', + 'port' => 'required|integer|between:1,65535', + 'is_swarm_manager' => 'required|boolean', + 'is_swarm_worker' => 'required|boolean', + 'selected_swarm_cluster' => 'nullable|integer', + 'is_build_server' => 'required|boolean', + ]; + } + + protected function messages(): array + { + return array_merge(ValidationPatterns::combinedMessages(), [ + 'private_key_id.integer' => 'The Private Key field must be an integer.', + 'private_key_id.nullable' => 'The Private Key field is optional.', + 'new_private_key_name.string' => 'The Private Key Name must be a string.', + 'new_private_key_description.string' => 'The Private Key Description must be a string.', + 'new_private_key_value.string' => 'The Private Key Value must be a string.', + 'ip.required' => 'The IP Address/Domain is required.', + 'ip.string' => 'The IP Address/Domain must be a string.', + 'user.required' => 'The User field is required.', + 'user.string' => 'The User field must be a string.', + 'port.required' => 'The Port field is required.', + 'port.integer' => 'The Port field must be an integer.', + 'port.between' => 'The Port field must be between 1 and 65535.', + 'is_swarm_manager.required' => 'The Swarm Manager field is required.', + 'is_swarm_manager.boolean' => 'The Swarm Manager field must be true or false.', + 'is_swarm_worker.required' => 'The Swarm Worker field is required.', + 'is_swarm_worker.boolean' => 'The Swarm Worker field must be true or false.', + 'selected_swarm_cluster.integer' => 'The Swarm Cluster field must be an integer.', + 'is_build_server.required' => 'The Build Server field is required.', + 'is_build_server.boolean' => 'The Build Server field must be true or false.', + ]); + } + public function setPrivateKey(string $private_key_id) { $this->private_key_id = $private_key_id; diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index d53f10d74..a6702a39b 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -6,82 +6,60 @@ use App\Actions\Server\StartSentinel; use App\Actions\Server\StopSentinel; use App\Events\ServerReachabilityChanged; use App\Models\Server; +use App\Support\ValidationPatterns; use Livewire\Attributes\Computed; use Livewire\Attributes\Locked; -use Livewire\Attributes\Validate; use Livewire\Component; class Show extends Component { public Server $server; - #[Validate(['required'])] public string $name; - #[Validate(['nullable'])] public ?string $description = null; - #[Validate(['required'])] public string $ip; - #[Validate(['required'])] public string $user; - #[Validate(['required'])] public string $port; - #[Validate(['nullable'])] public ?string $validationLogs = null; - #[Validate(['nullable', 'url'])] public ?string $wildcardDomain = null; - #[Validate(['required'])] public bool $isReachable; - #[Validate(['required'])] public bool $isUsable; - #[Validate(['required'])] public bool $isSwarmManager; - #[Validate(['required'])] public bool $isSwarmWorker; - #[Validate(['required'])] public bool $isBuildServer; #[Locked] public bool $isBuildServerLocked = false; - #[Validate(['required'])] public bool $isMetricsEnabled; - #[Validate(['required'])] public string $sentinelToken; - #[Validate(['nullable'])] public ?string $sentinelUpdatedAt = null; - #[Validate(['required', 'integer', 'min:1'])] public int $sentinelMetricsRefreshRateSeconds; - #[Validate(['required', 'integer', 'min:1'])] public int $sentinelMetricsHistoryDays; - #[Validate(['required', 'integer', 'min:10'])] public int $sentinelPushIntervalSeconds; - #[Validate(['nullable', 'url'])] public ?string $sentinelCustomUrl = null; - #[Validate(['required'])] public bool $isSentinelEnabled; - #[Validate(['required'])] public bool $isSentinelDebugEnabled; - #[Validate(['required'])] public string $serverTimezone; public function getListeners() @@ -91,6 +69,59 @@ class Show extends Component ]; } + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'ip' => 'required', + 'user' => 'required', + 'port' => 'required', + 'validationLogs' => 'nullable', + 'wildcardDomain' => 'nullable|url', + 'isReachable' => 'required', + 'isUsable' => 'required', + 'isSwarmManager' => 'required', + 'isSwarmWorker' => 'required', + 'isBuildServer' => 'required', + 'isMetricsEnabled' => 'required', + 'sentinelToken' => 'required', + 'sentinelUpdatedAt' => 'nullable', + 'sentinelMetricsRefreshRateSeconds' => 'required|integer|min:1', + 'sentinelMetricsHistoryDays' => 'required|integer|min:1', + 'sentinelPushIntervalSeconds' => 'required|integer|min:10', + 'sentinelCustomUrl' => 'nullable|url', + 'isSentinelEnabled' => 'required', + 'isSentinelDebugEnabled' => 'required', + 'serverTimezone' => 'required', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'ip.required' => 'The IP Address field is required.', + 'user.required' => 'The User field is required.', + 'port.required' => 'The Port field is required.', + 'wildcardDomain.url' => 'The Wildcard Domain must be a valid URL.', + 'sentinelToken.required' => 'The Sentinel Token field is required.', + 'sentinelMetricsRefreshRateSeconds.required' => 'The Metrics Refresh Rate field is required.', + 'sentinelMetricsRefreshRateSeconds.integer' => 'The Metrics Refresh Rate must be an integer.', + 'sentinelMetricsRefreshRateSeconds.min' => 'The Metrics Refresh Rate must be at least 1 second.', + 'sentinelMetricsHistoryDays.required' => 'The Metrics History Days field is required.', + 'sentinelMetricsHistoryDays.integer' => 'The Metrics History Days must be an integer.', + 'sentinelMetricsHistoryDays.min' => 'The Metrics History Days must be at least 1 day.', + 'sentinelPushIntervalSeconds.required' => 'The Push Interval field is required.', + 'sentinelPushIntervalSeconds.integer' => 'The Push Interval must be an integer.', + 'sentinelPushIntervalSeconds.min' => 'The Push Interval must be at least 10 seconds.', + 'sentinelCustomUrl.url' => 'The Custom Sentinel URL must be a valid URL.', + 'serverTimezone.required' => 'The Server Timezone field is required.', + ] + ); + } + public function mount(string $server_uuid) { try { diff --git a/app/Livewire/Storage/Create.php b/app/Livewire/Storage/Create.php index 1d60d6ac5..9cbc516da 100644 --- a/app/Livewire/Storage/Create.php +++ b/app/Livewire/Storage/Create.php @@ -3,6 +3,7 @@ namespace App\Livewire\Storage; use App\Models\S3Storage; +use App\Support\ValidationPatterns; use Illuminate\Support\Uri; use Livewire\Component; @@ -24,15 +25,38 @@ class Create extends Component public S3Storage $storage; - protected $rules = [ - 'name' => 'required|min:3|max:255', - 'description' => 'nullable|min:3|max:255', - 'region' => 'required|max:255', - 'key' => 'required|max:255', - 'secret' => 'required|max:255', - 'bucket' => 'required|max:255', - 'endpoint' => 'required|url|max:255', - ]; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + 'region' => 'required|max:255', + 'key' => 'required|max:255', + 'secret' => 'required|max:255', + 'bucket' => 'required|max:255', + 'endpoint' => 'required|url|max:255', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'region.required' => 'The Region field is required.', + 'region.max' => 'The Region may not be greater than 255 characters.', + 'key.required' => 'The Access Key field is required.', + 'key.max' => 'The Access Key may not be greater than 255 characters.', + 'secret.required' => 'The Secret Key field is required.', + 'secret.max' => 'The Secret Key may not be greater than 255 characters.', + 'bucket.required' => 'The Bucket field is required.', + 'bucket.max' => 'The Bucket may not be greater than 255 characters.', + 'endpoint.required' => 'The Endpoint field is required.', + 'endpoint.url' => 'The Endpoint must be a valid URL.', + 'endpoint.max' => 'The Endpoint may not be greater than 255 characters.', + ] + ); + } protected $validationAttributes = [ 'name' => 'Name', diff --git a/app/Livewire/Storage/Form.php b/app/Livewire/Storage/Form.php index ad1627863..fd2222d32 100644 --- a/app/Livewire/Storage/Form.php +++ b/app/Livewire/Storage/Form.php @@ -3,22 +3,48 @@ namespace App\Livewire\Storage; use App\Models\S3Storage; +use App\Support\ValidationPatterns; use Livewire\Component; class Form extends Component { public S3Storage $storage; - protected $rules = [ - 'storage.is_usable' => 'nullable|boolean', - 'storage.name' => 'nullable|min:3|max:255', - 'storage.description' => 'nullable|min:3|max:255', - 'storage.region' => 'required|max:255', - 'storage.key' => 'required|max:255', - 'storage.secret' => 'required|max:255', - 'storage.bucket' => 'required|max:255', - 'storage.endpoint' => 'required|url|max:255', - ]; + protected function rules(): array + { + return [ + 'storage.is_usable' => 'nullable|boolean', + 'storage.name' => ValidationPatterns::nameRules(required: false), + 'storage.description' => ValidationPatterns::descriptionRules(), + 'storage.region' => 'required|max:255', + 'storage.key' => 'required|max:255', + 'storage.secret' => 'required|max:255', + 'storage.bucket' => 'required|max:255', + 'storage.endpoint' => 'required|url|max:255', + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'storage.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'storage.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + 'storage.region.required' => 'The Region field is required.', + 'storage.region.max' => 'The Region may not be greater than 255 characters.', + 'storage.key.required' => 'The Access Key field is required.', + 'storage.key.max' => 'The Access Key may not be greater than 255 characters.', + 'storage.secret.required' => 'The Secret Key field is required.', + 'storage.secret.max' => 'The Secret Key may not be greater than 255 characters.', + 'storage.bucket.required' => 'The Bucket field is required.', + 'storage.bucket.max' => 'The Bucket may not be greater than 255 characters.', + 'storage.endpoint.required' => 'The Endpoint field is required.', + 'storage.endpoint.url' => 'The Endpoint must be a valid URL.', + 'storage.endpoint.max' => 'The Endpoint may not be greater than 255 characters.', + ] + ); + } protected $validationAttributes = [ 'storage.is_usable' => 'Is Usable', diff --git a/app/Livewire/Team/Create.php b/app/Livewire/Team/Create.php index f805d6122..d3d27556c 100644 --- a/app/Livewire/Team/Create.php +++ b/app/Livewire/Team/Create.php @@ -3,17 +3,28 @@ namespace App\Livewire\Team; use App\Models\Team; -use Livewire\Attributes\Validate; +use App\Support\ValidationPatterns; use Livewire\Component; class Create extends Component { - #[Validate(['required', 'min:3', 'max:255'])] public string $name = ''; - #[Validate(['nullable', 'min:3', 'max:255'])] public ?string $description = null; + protected function rules(): array + { + return [ + 'name' => ValidationPatterns::nameRules(), + 'description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return ValidationPatterns::combinedMessages(); + } + public function submit() { try { diff --git a/app/Livewire/Team/Index.php b/app/Livewire/Team/Index.php index 0972e7364..accaab8d9 100644 --- a/app/Livewire/Team/Index.php +++ b/app/Livewire/Team/Index.php @@ -4,6 +4,7 @@ namespace App\Livewire\Team; use App\Models\Team; use App\Models\TeamInvitation; +use App\Support\ValidationPatterns; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Livewire\Component; @@ -14,10 +15,25 @@ class Index extends Component public Team $team; - protected $rules = [ - 'team.name' => 'required|min:3|max:255', - 'team.description' => 'nullable|min:3|max:255', - ]; + protected function rules(): array + { + return [ + 'team.name' => ValidationPatterns::nameRules(), + 'team.description' => ValidationPatterns::descriptionRules(), + ]; + } + + protected function messages(): array + { + return array_merge( + ValidationPatterns::combinedMessages(), + [ + 'team.name.required' => 'The Name field is required.', + 'team.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().', + 'team.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.', + ] + ); + } protected $validationAttributes = [ 'team.name' => 'name', diff --git a/resources/views/livewire/security/private-key/create.blade.php b/resources/views/livewire/security/private-key/create.blade.php index 132c0e9ad..4294823e0 100644 --- a/resources/views/livewire/security/private-key/create.blade.php +++ b/resources/views/livewire/security/private-key/create.blade.php @@ -1,5 +1,5 @@
-
+
Private Keys are used to connect to your servers without passwords.
You should not use passphrase protected keys.