mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 22:14:53 +00:00
Merge remote-tracking branch 'origin/next' into feat/search-to-envs
This commit is contained in:
commit
d300ddf902
16 files changed
+536
-57
No files matched your search
@@ -13,6 +13,7 @@ use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use App\Rules\ValidServerIp;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
@@ -487,10 +488,12 @@ class ServersController extends Controller
|
||||
'ip' => ['string', 'required', new ValidServerIp],
|
||||
'port' => 'integer|nullable|between:1,65535',
|
||||
'private_key_uuid' => 'string|required',
|
||||
'user' => ['string', 'nullable', 'regex:/^[a-zA-Z0-9_-]+$/'],
|
||||
'user' => ValidationPatterns::serverUsernameRules(required: false),
|
||||
'is_build_server' => 'boolean|nullable',
|
||||
'instant_validate' => 'boolean|nullable',
|
||||
'proxy_type' => 'string|nullable',
|
||||
], [
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
@@ -666,7 +669,7 @@ class ServersController extends Controller
|
||||
'ip' => ['string', 'nullable', new ValidServerIp],
|
||||
'port' => 'integer|nullable|between:1,65535',
|
||||
'private_key_uuid' => 'string|nullable',
|
||||
'user' => ['string', 'nullable', 'regex:/^[a-zA-Z0-9_-]+$/'],
|
||||
'user' => ValidationPatterns::serverUsernameRules(required: false),
|
||||
'is_build_server' => 'boolean|nullable',
|
||||
'instant_validate' => 'boolean|nullable',
|
||||
'proxy_type' => 'string|nullable',
|
||||
@@ -676,6 +679,8 @@ class ServersController extends Controller
|
||||
'server_disk_usage_notification_threshold' => 'integer|min:1|max:100',
|
||||
'server_disk_usage_check_frequency' => 'string',
|
||||
'connection_timeout' => 'integer|min:1|max:300',
|
||||
], [
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
@@ -212,6 +213,23 @@ class Index extends Component
|
||||
}
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'remoteServerName' => 'required|string',
|
||||
'remoteServerHost' => 'required|string',
|
||||
'remoteServerPort' => 'required|integer|min:1|max:65535',
|
||||
'remoteServerUser' => ValidationPatterns::serverUsernameRules(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function messages(): array
|
||||
{
|
||||
return [
|
||||
...ValidationPatterns::serverUsernameMessages('remoteServerUser', 'SSH User'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getProxyType()
|
||||
{
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||
@@ -274,12 +292,7 @@ class Index extends Component
|
||||
|
||||
public function saveServer()
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required|string',
|
||||
'remoteServerHost' => 'required|string',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required|string',
|
||||
]);
|
||||
$this->validate();
|
||||
|
||||
$this->privateKey = formatPrivateKey($this->privateKey);
|
||||
$foundServer = Server::whereIp($this->remoteServerHost)->first();
|
||||
@@ -465,10 +478,10 @@ class Index extends Component
|
||||
|
||||
public function saveAndValidateServer()
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerPort' => 'required|integer|min:1|max:65535',
|
||||
'remoteServerUser' => 'required|string',
|
||||
]);
|
||||
$this->validate(array_intersect_key($this->rules(), array_flip([
|
||||
'remoteServerPort',
|
||||
'remoteServerUser',
|
||||
])));
|
||||
|
||||
$this->createdServer->update([
|
||||
'port' => $this->remoteServerPort,
|
||||
|
||||
@@ -57,7 +57,7 @@ class ByIp extends Component
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'ip' => ['required', 'string', new ValidServerIp],
|
||||
'user' => ['required', 'string', 'regex:/^[a-zA-Z0-9_-]+$/'],
|
||||
'user' => ValidationPatterns::serverUsernameRules(),
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'is_build_server' => 'required|boolean',
|
||||
];
|
||||
@@ -75,6 +75,7 @@ class ByIp extends Component
|
||||
'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.',
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
'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.',
|
||||
|
||||
@@ -110,7 +110,7 @@ class Show extends Component
|
||||
'name' => ValidationPatterns::nameRules(),
|
||||
'description' => ValidationPatterns::descriptionRules(),
|
||||
'ip' => ['required', new ValidServerIp],
|
||||
'user' => ['required', 'regex:/^[a-zA-Z0-9_-]+$/'],
|
||||
'user' => ValidationPatterns::serverUsernameRules(),
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'connectionTimeout' => 'required|integer|min:1|max:300',
|
||||
'validationLogs' => 'nullable',
|
||||
@@ -140,6 +140,7 @@ class Show extends Component
|
||||
[
|
||||
'ip.required' => 'The IP Address field is required.',
|
||||
'user.required' => 'The User field is required.',
|
||||
...ValidationPatterns::serverUsernameMessages(),
|
||||
'port.required' => 'The Port field is required.',
|
||||
'connectionTimeout.required' => 'The SSH Connection Timeout field is required.',
|
||||
'connectionTimeout.integer' => 'The SSH Connection Timeout must be an integer.',
|
||||
|
||||
@@ -8,6 +8,15 @@ use App\Models\ScheduledDatabaseBackupExecution;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\ScheduledTaskExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
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\Services\SchedulerLogParser;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -125,7 +134,21 @@ class ScheduledJobs extends Component
|
||||
: collect();
|
||||
|
||||
$backups = $backupIds->isNotEmpty()
|
||||
? ScheduledDatabaseBackup::with(['database.environment.project'])->whereIn('id', $backupIds)->get()->keyBy('id')
|
||||
? ScheduledDatabaseBackup::with('database')
|
||||
->whereIn('id', $backupIds)
|
||||
->get()
|
||||
->loadMorph('database', [
|
||||
ServiceDatabase::class => ['service.environment.project'],
|
||||
StandaloneClickhouse::class => ['environment.project'],
|
||||
StandaloneDragonfly::class => ['environment.project'],
|
||||
StandaloneKeydb::class => ['environment.project'],
|
||||
StandaloneMariadb::class => ['environment.project'],
|
||||
StandaloneMongodb::class => ['environment.project'],
|
||||
StandaloneMysql::class => ['environment.project'],
|
||||
StandalonePostgresql::class => ['environment.project'],
|
||||
StandaloneRedis::class => ['environment.project'],
|
||||
])
|
||||
->keyBy('id')
|
||||
: collect();
|
||||
|
||||
$servers = $serverIds->isNotEmpty()
|
||||
@@ -161,14 +184,29 @@ class ScheduledJobs extends Component
|
||||
if ($backup) {
|
||||
$database = $backup->database;
|
||||
$skip['resource_name'] = $database?->name ?? 'Database backup';
|
||||
$environment = $database?->environment;
|
||||
$project = $environment?->project;
|
||||
if ($project && $environment && $database) {
|
||||
$skip['link'] = route('project.database.backup.index', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $environment->uuid,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
|
||||
if ($database instanceof ServiceDatabase) {
|
||||
$service = $database->service;
|
||||
$environment = $service?->environment;
|
||||
$project = $environment?->project;
|
||||
if ($project && $environment && $service) {
|
||||
$skip['link'] = route('project.service.database.backups', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $environment->uuid,
|
||||
'service_uuid' => $service->uuid,
|
||||
'stack_service_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$environment = $database?->environment;
|
||||
$project = $environment?->project;
|
||||
if ($project && $environment && $database) {
|
||||
$skip['link'] = route('project.database.backup.index', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $environment->uuid,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($skip['type'] === 'docker_cleanup') {
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Livewire\Server\Proxy;
|
||||
use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use App\Support\ValidationPatterns;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
@@ -945,10 +946,10 @@ $schema://$host {
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function ($value) {
|
||||
return preg_replace('/[^A-Za-z0-9\-_]/', '', $value);
|
||||
return preg_replace(ValidationPatterns::INVALID_SERVER_USERNAME_CHARACTERS_PATTERN, '', $value);
|
||||
},
|
||||
set: function ($value) {
|
||||
return preg_replace('/[^A-Za-z0-9\-_]/', '', $value);
|
||||
return preg_replace(ValidationPatterns::INVALID_SERVER_USERNAME_CHARACTERS_PATTERN, '', $value);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,17 @@ class ValidationPatterns
|
||||
*/
|
||||
public const DOCKER_TARGET_PATTERN = '/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/';
|
||||
|
||||
/**
|
||||
* Pattern for SSH usernames.
|
||||
* Allows alphanumeric characters, dots, hyphens, and underscores.
|
||||
*/
|
||||
public const SERVER_USERNAME_PATTERN = '/^[a-zA-Z0-9._-]+$/';
|
||||
|
||||
/**
|
||||
* Pattern for removing characters not allowed in SSH usernames.
|
||||
*/
|
||||
public const INVALID_SERVER_USERNAME_CHARACTERS_PATTERN = '/[^A-Za-z0-9.\-_]/';
|
||||
|
||||
/**
|
||||
* Token-aware pattern for shell-safe command strings (docker compose commands, docker run options).
|
||||
*
|
||||
@@ -283,6 +294,28 @@ class ValidationPatterns
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for SSH username fields.
|
||||
*/
|
||||
public static function serverUsernameRules(bool $required = true): array
|
||||
{
|
||||
return [
|
||||
$required ? 'required' : 'nullable',
|
||||
'string',
|
||||
'regex:'.self::SERVER_USERNAME_PATTERN,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages for SSH username fields.
|
||||
*/
|
||||
public static function serverUsernameMessages(string $field = 'user', string $label = 'User'): array
|
||||
{
|
||||
return [
|
||||
"{$field}.regex" => "The {$label} may only contain letters, numbers, dots, hyphens, and underscores.",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages for database identifier fields.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user