From d99fc7ccd5c69413b0df174e66c087fde2e51398 Mon Sep 17 00:00:00 2001 From: TheOriginalCER06 <69149072+TheOriginalCER06@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:18:19 +0200 Subject: [PATCH 1/4] Norwegian added (: --- user-creatable-servers/lang/no-nb/strings.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 user-creatable-servers/lang/no-nb/strings.php diff --git a/user-creatable-servers/lang/no-nb/strings.php b/user-creatable-servers/lang/no-nb/strings.php new file mode 100644 index 00000000..04beb5eb --- /dev/null +++ b/user-creatable-servers/lang/no-nb/strings.php @@ -0,0 +1,40 @@ + 'Bruker Ressursgrense|Bruker Ressursgrenser', + 'user' => 'Bruker|Brukere', + 'cpu' => 'CPU', + 'memory' => 'Minne', + 'disk' => 'Diskplass', + 'server_limit' => 'Servergrense', + 'no_limit' => 'Ingen grense', + 'unlimited' => 'Ubegrenset', + 'hint_unlimited' => '0 betyr ubegrenset', + 'name' => 'Servernavn', + 'egg' => 'Egg', + 'left' => 'igjen', + 'variables' => 'Oppstartsvariabler', + + 'create_server' => 'Oprett server', + + 'modals' => [ + 'delete_server_confirm' => 'Er du sikker på at du vil slette denne serveren?', + 'delete_server_warning' => 'Denne handlingen kan ikke angres og all data vil gå tapt permanent.', + 'delete_server' => 'Slett server', + ], + + 'notifications' => [ + 'server_resources_updated' => 'Serverressursgrenser oppdatert', + 'might_need_restart' => 'For å bruke de nye ressursgrensene kan det være nødvendig å starte serveren på nytt.', + 'manual_restart_needed' => 'Vennligst start serveren på nytt, manuelt, for å bruke de nye ressursgrensene.', + + 'server_deleted' => 'Server slettet', + 'server_deleted_success' => 'Serveren ble slettet.', + 'server_delete_error' => 'Kunne ikke slette server', + + 'server_creation_failed' => 'Kunne ikke opprette server', + 'no_viable_node_found' => 'Ingen tilgjengelig node ble funnet. Vennligst kontakt paneladministratoren.', + 'no_viable_allocation_found' => 'Ingen tilgjengelig allokering ble funnet. Vennligst kontakt paneladministratoren.', + 'unknown_server_creation_error' => 'Ukjent feil. Vennligst kontakt paneladministratoren.', + ], +]; From bae227ce10d4e7ba944021e93e9d161511f7ac05 Mon Sep 17 00:00:00 2001 From: TheOriginalCER06 <69149072+TheOriginalCER06@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:46:34 +0200 Subject: [PATCH 2/4] Changes to give al users a resource limit All users now recieve a resource limit on creation, with editable defaults for Plugin admins. --- user-creatable-servers/README.md | 4 +- .../config/user-creatable-servers.php | 8 +++ user-creatable-servers/lang/de/strings.php | 1 + user-creatable-servers/lang/en/strings.php | 1 + user-creatable-servers/lang/no-nb/strings.php | 1 + user-creatable-servers/plugin.json | 4 +- .../UserResourceLimitsResource.php | 6 +- .../UserResourceLimitRelationManager.php | 6 +- .../Server/Pages/ServerResourcePage.php | 13 ++++ .../src/Models/UserResourceLimits.php | 65 +++++++++++++++++-- .../UserCreatableServersPluginProvider.php | 11 ++++ .../src/UserCreatableServersPlugin.php | 58 +++++++++++++++++ 12 files changed, 163 insertions(+), 15 deletions(-) diff --git a/user-creatable-servers/README.md b/user-creatable-servers/README.md index becda7df..72e6b1d7 100644 --- a/user-creatable-servers/README.md +++ b/user-creatable-servers/README.md @@ -1,4 +1,4 @@ -# User Creatable Servers (by Boy132) +# User Creatable Servers (by Boy132 & contributions from CER06) Allow users to create their own servers within defined resource limits set by administrators. @@ -10,6 +10,8 @@ Add the deployment tag (`user_creatable_servers` by default) to the nodes that s - Users can create servers without admin intervention - Configurable resource limits per user (CPU, RAM, disk, etc.) +- Configurable default CPU, memory, and disk allocations automatically assigned to newly created users +- Optional aggregate CPU, memory, and disk caps across all UCS users (`0` is unlimited and deployment remains limited by node capacity) - Admin management of user resource allocations - Resource usage overview widget for users - Integration with existing server management diff --git a/user-creatable-servers/config/user-creatable-servers.php b/user-creatable-servers/config/user-creatable-servers.php index bbe0129c..bf6dff8d 100644 --- a/user-creatable-servers/config/user-creatable-servers.php +++ b/user-creatable-servers/config/user-creatable-servers.php @@ -5,6 +5,14 @@ 'allocation_limit' => (int) env('UCS_DEFAULT_ALLOCATION_LIMIT', 0), 'backup_limit' => (int) env('UCS_DEFAULT_BACKUP_LIMIT', 0), + 'default_user_cpu' => (int) env('UCS_DEFAULT_USER_CPU', 0), + 'default_user_memory' => (int) env('UCS_DEFAULT_USER_MEMORY', 0), + 'default_user_disk' => (int) env('UCS_DEFAULT_USER_DISK', 0), + + 'max_cpu' => (int) env('UCS_MAX_CPU', 0), + 'max_memory' => (int) env('UCS_MAX_MEMORY', 0), + 'max_disk' => (int) env('UCS_MAX_DISK', 0), + 'can_users_update_servers' => (bool) env('UCS_CAN_USERS_UPDATE_SERVERS', true), 'can_users_delete_servers' => (bool) env('UCS_CAN_USERS_DELETE_SERVERS', false), diff --git a/user-creatable-servers/lang/de/strings.php b/user-creatable-servers/lang/de/strings.php index 3a9e9611..d2c61fac 100644 --- a/user-creatable-servers/lang/de/strings.php +++ b/user-creatable-servers/lang/de/strings.php @@ -26,6 +26,7 @@ 'notifications' => [ 'server_resources_updated' => 'Server Ressourcen Limits aktualisiert', 'might_need_restart' => 'Um die neuen Ressourcen Limits vollständig zu nutzen, ist möglicherweise ein Neustart des Servers erforderlich.', + 'resource_limit_reached' => 'Die angeforderten Ressourcen überschreiten die verfügbaren Benutzer- oder UCS-Zuweisungen.', 'manual_restart_needed' => 'Bitte starte deinen Server manuell neu, um die neuen Ressourcen Limits zu übernehmen.', 'server_deleted' => 'Server gelöscht', diff --git a/user-creatable-servers/lang/en/strings.php b/user-creatable-servers/lang/en/strings.php index 4df4865c..fd00285e 100644 --- a/user-creatable-servers/lang/en/strings.php +++ b/user-creatable-servers/lang/en/strings.php @@ -25,6 +25,7 @@ 'notifications' => [ 'server_resources_updated' => 'Server Resource Limits updated', + 'resource_limit_reached' => 'The requested resources exceed the available user or UCS allocation.', 'might_need_restart' => 'To fully use the new resource limits a server restart might be required.', 'manual_restart_needed' => 'Please manually restart your server to apply the new resource limits.', diff --git a/user-creatable-servers/lang/no-nb/strings.php b/user-creatable-servers/lang/no-nb/strings.php index 04beb5eb..7e9dfbf0 100644 --- a/user-creatable-servers/lang/no-nb/strings.php +++ b/user-creatable-servers/lang/no-nb/strings.php @@ -25,6 +25,7 @@ 'notifications' => [ 'server_resources_updated' => 'Serverressursgrenser oppdatert', + 'resource_limit_reached' => 'De forespurte ressursene overstiger tilgjengelig bruker- eller UCS-allokering.', 'might_need_restart' => 'For å bruke de nye ressursgrensene kan det være nødvendig å starte serveren på nytt.', 'manual_restart_needed' => 'Vennligst start serveren på nytt, manuelt, for å bruke de nye ressursgrensene.', diff --git a/user-creatable-servers/plugin.json b/user-creatable-servers/plugin.json index e6ecf288..5a8cd7bb 100644 --- a/user-creatable-servers/plugin.json +++ b/user-creatable-servers/plugin.json @@ -1,8 +1,8 @@ { "id": "user-creatable-servers", "name": "User Creatable Servers", - "author": "Boy132", - "version": "1.1.1", + "author": "Boy132 & CER06", + "version": "1.1.2", "description": "Allow users to create their own servers", "category": "plugin", "url": "https://hub.pelican.dev/plugins/user-creatable-servers", diff --git a/user-creatable-servers/src/Filament/Admin/Resources/UserResourceLimits/UserResourceLimitsResource.php b/user-creatable-servers/src/Filament/Admin/Resources/UserResourceLimits/UserResourceLimitsResource.php index 6799ea0b..280dc214 100644 --- a/user-creatable-servers/src/Filament/Admin/Resources/UserResourceLimits/UserResourceLimitsResource.php +++ b/user-creatable-servers/src/Filament/Admin/Resources/UserResourceLimits/UserResourceLimitsResource.php @@ -111,7 +111,7 @@ public static function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_cpu')) ->suffix('%') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('memory') @@ -119,7 +119,7 @@ public static function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_memory')) ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('disk') @@ -127,7 +127,7 @@ public static function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_disk')) ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('server_limit') diff --git a/user-creatable-servers/src/Filament/Admin/Resources/Users/RelationManagers/UserResourceLimitRelationManager.php b/user-creatable-servers/src/Filament/Admin/Resources/Users/RelationManagers/UserResourceLimitRelationManager.php index 5d47a38b..efe16ae6 100644 --- a/user-creatable-servers/src/Filament/Admin/Resources/Users/RelationManagers/UserResourceLimitRelationManager.php +++ b/user-creatable-servers/src/Filament/Admin/Resources/Users/RelationManagers/UserResourceLimitRelationManager.php @@ -65,7 +65,7 @@ public function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_cpu')) ->suffix('%') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('memory') @@ -73,7 +73,7 @@ public function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_memory')) ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('disk') @@ -81,7 +81,7 @@ public function form(Schema $schema): Schema ->required() ->numeric() ->minValue(0) - ->default(0) + ->default(fn () => config('user-creatable-servers.default_user_disk')) ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') ->hint(trans('user-creatable-servers::strings.hint_unlimited')), TextInput::make('server_limit') diff --git a/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php b/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php index f188de23..cf4dfb04 100644 --- a/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php +++ b/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php @@ -155,6 +155,19 @@ public function save(): void /** @var Server $server */ $server = Filament::getTenant(); + /** @var UserResourceLimits $userResourceLimits */ + $userResourceLimits = UserResourceLimits::where('user_id', $server->owner_id)->firstOrFail(); + + if (!$userResourceLimits->canUpdateServerResources($server, $data['cpu'], $data['memory'], $data['disk'])) { + Notification::make() + ->title(trans('user-creatable-servers::strings.notifications.server_resources_updated')) + ->body(trans('user-creatable-servers::strings.notifications.resource_limit_reached')) + ->danger() + ->send(); + + return; + } + $server->update([ 'cpu' => $data['cpu'], 'memory' => $data['memory'], diff --git a/user-creatable-servers/src/Models/UserResourceLimits.php b/user-creatable-servers/src/Models/UserResourceLimits.php index 4a53e300..4ba81f6d 100644 --- a/user-creatable-servers/src/Models/UserResourceLimits.php +++ b/user-creatable-servers/src/Models/UserResourceLimits.php @@ -39,35 +39,46 @@ public function user(): BelongsTo public function getCpuLeft(): ?int { + $userCpuLeft = null; + if ($this->cpu > 0) { $sum_cpu = $this->user->servers->sum('cpu'); - return (int) max(0, $this->cpu - $sum_cpu); + $userCpuLeft = (int) max(0, $this->cpu - $sum_cpu); } - return null; + return $this->getLowestLimit($userCpuLeft, $this->getUcsResourceLeft('cpu')); } public function getMemoryLeft(): ?int { + $userMemoryLeft = null; + if ($this->memory > 0) { $sum_memory = $this->user->servers->sum('memory'); - return (int) max(0, $this->memory - $sum_memory); + $userMemoryLeft = (int) max(0, $this->memory - $sum_memory); } - return null; + return $this->getLowestLimit($userMemoryLeft, $this->getUcsResourceLeft('memory')); } public function getDiskLeft(): ?int { + $userDiskLeft = null; + if ($this->disk > 0) { $sum_disk = $this->user->servers->sum('disk'); - return (int) max(0, $this->disk - $sum_disk); + $userDiskLeft = (int) max(0, $this->disk - $sum_disk); } - return null; + return $this->getLowestLimit($userDiskLeft, $this->getUcsResourceLeft('disk')); + } + + public function canUpdateServerResources(Server $server, int $cpu, int $memory, int $disk): bool + { + return $this->canAllocateResources($cpu, $memory, $disk, $server); } public function canCreateServer(int $cpu, int $memory, int $disk): bool @@ -109,9 +120,51 @@ public function canCreateServer(int $cpu, int $memory, int $disk): bool } } + return $this->canAllocateResources($cpu, $memory, $disk); + } + + private function canAllocateResources(int $cpu, int $memory, int $disk, ?Server $excludedServer = null): bool + { + foreach (['cpu' => $cpu, 'memory' => $memory, 'disk' => $disk] as $resource => $requested) { + $limit = (int) config("user-creatable-servers.max_{$resource}"); + + if ($limit > 0 && $this->getUcsAllocatedResource($resource, $excludedServer) + $requested > $limit) { + return false; + } + } + return true; } + private function getUcsResourceLeft(string $resource): ?int + { + $limit = (int) config("user-creatable-servers.max_{$resource}"); + + if ($limit <= 0) { + return null; + } + + return (int) max(0, $limit - $this->getUcsAllocatedResource($resource)); + } + + private function getUcsAllocatedResource(string $resource, ?Server $excludedServer = null): int + { + $servers = Server::query()->whereIn('owner_id', self::query()->select('user_id')); + + if ($excludedServer) { + $servers->whereKeyNot($excludedServer->getKey()); + } + + return (int) $servers->sum($resource); + } + + private function getLowestLimit(?int ...$limits): ?int + { + $limits = array_filter($limits, fn (?int $limit) => $limit !== null); + + return empty($limits) ? null : min($limits); + } + /** @param array $variables */ public function createServer(string $name, int|Egg $egg, int $cpu, int $memory, int $disk, array $variables = []): Server|false { diff --git a/user-creatable-servers/src/Providers/UserCreatableServersPluginProvider.php b/user-creatable-servers/src/Providers/UserCreatableServersPluginProvider.php index 511b32b0..4aca7393 100644 --- a/user-creatable-servers/src/Providers/UserCreatableServersPluginProvider.php +++ b/user-creatable-servers/src/Providers/UserCreatableServersPluginProvider.php @@ -31,5 +31,16 @@ public function register(): void public function boot(): void { User::resolveRelationUsing('userResourceLimits', fn (User $user) => $user->belongsTo(UserResourceLimits::class, 'id', 'user_id')); + + User::created(function (User $user) { + UserResourceLimits::firstOrCreate( + ['user_id' => $user->id], + [ + 'cpu' => config('user-creatable-servers.default_user_cpu'), + 'memory' => config('user-creatable-servers.default_user_memory'), + 'disk' => config('user-creatable-servers.default_user_disk'), + ], + ); + }); } } diff --git a/user-creatable-servers/src/UserCreatableServersPlugin.php b/user-creatable-servers/src/UserCreatableServersPlugin.php index f10f246b..70cd4ae4 100644 --- a/user-creatable-servers/src/UserCreatableServersPlugin.php +++ b/user-creatable-servers/src/UserCreatableServersPlugin.php @@ -70,6 +70,58 @@ public function getSettingsForm(): array ->minValue(0) ->default(fn () => config('user-creatable-servers.backup_limit')), ]), + Section::make('Default per-user resource allocation') + ->description('Applied when assigning UCS resource limits to a user. 0 means unlimited.') + ->columns(3) + ->schema([ + TextInput::make('default_user_cpu') + ->label('CPU') + ->required() + ->numeric() + ->minValue(0) + ->suffix('%') + ->default(fn () => config('user-creatable-servers.default_user_cpu')), + TextInput::make('default_user_memory') + ->label('Memory') + ->required() + ->numeric() + ->minValue(0) + ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') + ->default(fn () => config('user-creatable-servers.default_user_memory')), + TextInput::make('default_user_disk') + ->label('Disk space') + ->required() + ->numeric() + ->minValue(0) + ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') + ->default(fn () => config('user-creatable-servers.default_user_disk')), + ]), + Section::make('Maximum UCS resource allocation') + ->description('Caps the combined resources allocated to UCS users. 0 means unlimited and uses the available capacity of a deployment node.') + ->columns(3) + ->schema([ + TextInput::make('max_cpu') + ->label('CPU') + ->required() + ->numeric() + ->minValue(0) + ->suffix('%') + ->default(fn () => config('user-creatable-servers.max_cpu')), + TextInput::make('max_memory') + ->label('Memory') + ->required() + ->numeric() + ->minValue(0) + ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') + ->default(fn () => config('user-creatable-servers.max_memory')), + TextInput::make('max_disk') + ->label('Disk space') + ->required() + ->numeric() + ->minValue(0) + ->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB') + ->default(fn () => config('user-creatable-servers.max_disk')), + ]), Section::make('User Settings') ->columns() ->schema([ @@ -119,6 +171,12 @@ public function saveSettings(array $data): void 'UCS_DEFAULT_DATABASE_LIMIT' => $data['database_limit'], 'UCS_DEFAULT_ALLOCATION_LIMIT' => $data['allocation_limit'], 'UCS_DEFAULT_BACKUP_LIMIT' => $data['backup_limit'], + 'UCS_DEFAULT_USER_CPU' => $data['default_user_cpu'], + 'UCS_DEFAULT_USER_MEMORY' => $data['default_user_memory'], + 'UCS_DEFAULT_USER_DISK' => $data['default_user_disk'], + 'UCS_MAX_CPU' => $data['max_cpu'], + 'UCS_MAX_MEMORY' => $data['max_memory'], + 'UCS_MAX_DISK' => $data['max_disk'], 'UCS_CAN_USERS_UPDATE_SERVERS' => $data['can_users_update_servers'] ? 'true' : 'false', 'UCS_CAN_USERS_DELETE_SERVERS' => $data['can_users_delete_servers'] ? 'true' : 'false', 'UCS_DEPLOYMENT_TAGS' => implode(',', $data['deployment_tags']), From 217386a5e5c961396c133b80b5640c8fb6aaf6e9 Mon Sep 17 00:00:00 2001 From: TheOriginalCER06 <69149072+TheOriginalCER06@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:56:00 +0200 Subject: [PATCH 3/4] I need to learn to write hehe --- user-creatable-servers/lang/no-nb/strings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user-creatable-servers/lang/no-nb/strings.php b/user-creatable-servers/lang/no-nb/strings.php index 7e9dfbf0..28090744 100644 --- a/user-creatable-servers/lang/no-nb/strings.php +++ b/user-creatable-servers/lang/no-nb/strings.php @@ -1,7 +1,7 @@ 'Bruker Ressursgrense|Bruker Ressursgrenser', + 'user_resource_limits' => 'Brukerreessursgrense|Brukerressursgrenser', 'user' => 'Bruker|Brukere', 'cpu' => 'CPU', 'memory' => 'Minne', @@ -15,7 +15,7 @@ 'left' => 'igjen', 'variables' => 'Oppstartsvariabler', - 'create_server' => 'Oprett server', + 'create_server' => 'Opprett server', 'modals' => [ 'delete_server_confirm' => 'Er du sikker på at du vil slette denne serveren?', From 757d4ba1dc4a9bc4080b86067a66638ee5cec11f Mon Sep 17 00:00:00 2001 From: TheOriginalCER06 <69149072+TheOriginalCER06@users.noreply.github.com> Date: Sat, 15 Aug 2026 14:02:09 +0200 Subject: [PATCH 4/4] Coderabbit AI Promt fix --- .../Server/Pages/ServerResourcePage.php | 38 ++++----- .../src/Models/UserResourceLimits.php | 84 +++++++++++-------- 2 files changed, 69 insertions(+), 53 deletions(-) diff --git a/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php b/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php index cf4dfb04..0420ee60 100644 --- a/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php +++ b/user-creatable-servers/src/Filament/Server/Pages/ServerResourcePage.php @@ -50,9 +50,13 @@ public function form(Schema $schema): Schema $userResourceLimits = UserResourceLimits::where('user_id', $server->owner_id)->firstOrFail(); - $maxCpu = $server->cpu + $userResourceLimits->getCpuLeft(); - $maxMemory = $server->memory + $userResourceLimits->getMemoryLeft(); - $maxDisk = $server->disk + $userResourceLimits->getDiskLeft(); + $cpuLeft = $userResourceLimits->getCpuLeft(); + $memoryLeft = $userResourceLimits->getMemoryLeft(); + $diskLeft = $userResourceLimits->getDiskLeft(); + + $maxCpu = $cpuLeft === null ? null : $server->cpu + $cpuLeft; + $maxMemory = $memoryLeft === null ? null : $server->memory + $memoryLeft; + $maxDisk = $diskLeft === null ? null : $server->disk + $diskLeft; $suffix = config('panel.use_binary_prefix') ? 'MiB' : 'MB'; @@ -66,31 +70,31 @@ public function form(Schema $schema): Schema ->label(trans('user-creatable-servers::strings.cpu')) ->required() ->live(onBlur: true) - ->hint(fn ($state) => $userResourceLimits->cpu > 0 ? ($maxCpu - $state . '% ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) - ->hintColor(fn ($state) => $userResourceLimits->cpu > 0 && $maxCpu - $state < 0 ? 'danger' : null) + ->hint(fn ($state) => $cpuLeft !== null ? ($maxCpu - $state . '% ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) + ->hintColor(fn ($state) => $cpuLeft !== null && $maxCpu - $state < 0 ? 'danger' : null) ->numeric() ->minValue($userResourceLimits->cpu > 0 ? 1 : 0) - ->maxValue($userResourceLimits->cpu > 0 ? $maxCpu : null) + ->maxValue($maxCpu) ->suffix('%'), TextInput::make('memory') ->label(trans('user-creatable-servers::strings.memory')) ->required() ->live(onBlur: true) - ->hint(fn ($state) => $userResourceLimits->memory > 0 ? ($maxMemory - $state . $suffix . ' ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) - ->hintColor(fn ($state) => $userResourceLimits->memory > 0 && $maxMemory - $state < 0 ? 'danger' : null) + ->hint(fn ($state) => $memoryLeft !== null ? ($maxMemory - $state . $suffix . ' ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) + ->hintColor(fn ($state) => $memoryLeft !== null && $maxMemory - $state < 0 ? 'danger' : null) ->numeric() ->minValue($userResourceLimits->memory > 0 ? 1 : 0) - ->maxValue($userResourceLimits->memory > 0 ? $maxMemory : null) + ->maxValue($maxMemory) ->suffix($suffix), TextInput::make('disk') ->label(trans('user-creatable-servers::strings.disk')) ->required() ->live(onBlur: true) - ->hint(fn ($state) => $userResourceLimits->disk > 0 ? ($maxDisk - $state . $suffix . ' ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) - ->hintColor(fn ($state) => $userResourceLimits->disk > 0 && $maxDisk - $state < 0 ? 'danger' : null) + ->hint(fn ($state) => $diskLeft !== null ? ($maxDisk - $state . $suffix . ' ' . trans('user-creatable-servers::strings.left')) : trans('user-creatable-servers::strings.unlimited')) + ->hintColor(fn ($state) => $diskLeft !== null && $maxDisk - $state < 0 ? 'danger' : null) ->numeric() ->minValue($userResourceLimits->disk > 0 ? 1 : 0) - ->maxValue($userResourceLimits->disk > 0 ? $maxDisk : null) + ->maxValue($maxDisk) ->suffix($suffix), ]); } @@ -158,7 +162,9 @@ public function save(): void /** @var UserResourceLimits $userResourceLimits */ $userResourceLimits = UserResourceLimits::where('user_id', $server->owner_id)->firstOrFail(); - if (!$userResourceLimits->canUpdateServerResources($server, $data['cpu'], $data['memory'], $data['disk'])) { + $server = $userResourceLimits->updateServerResources($server, $data['cpu'], $data['memory'], $data['disk']); + + if (!$server) { Notification::make() ->title(trans('user-creatable-servers::strings.notifications.server_resources_updated')) ->body(trans('user-creatable-servers::strings.notifications.resource_limit_reached')) @@ -168,12 +174,6 @@ public function save(): void return; } - $server->update([ - 'cpu' => $data['cpu'], - 'memory' => $data['memory'], - 'disk' => $data['disk'], - ]); - try { /** @var DaemonServerRepository $repository */ $repository = app(DaemonServerRepository::class); diff --git a/user-creatable-servers/src/Models/UserResourceLimits.php b/user-creatable-servers/src/Models/UserResourceLimits.php index 4ba81f6d..e20c667e 100644 --- a/user-creatable-servers/src/Models/UserResourceLimits.php +++ b/user-creatable-servers/src/Models/UserResourceLimits.php @@ -10,6 +10,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Facades\DB; /** * @property int $id @@ -81,43 +82,30 @@ public function canUpdateServerResources(Server $server, int $cpu, int $memory, return $this->canAllocateResources($cpu, $memory, $disk, $server); } - public function canCreateServer(int $cpu, int $memory, int $disk): bool + public function updateServerResources(Server $server, int $cpu, int $memory, int $disk): Server|false { - if ($this->server_limit && $this->user->servers->count() >= $this->server_limit) { - return false; - } - - if ($this->cpu > 0) { - if ($cpu <= 0) { - return false; - } + return DB::transaction(function () use ($server, $cpu, $memory, $disk) { + $userResourceLimits = $this->lockQuotaRecord(); + $server = Server::query()->lockForUpdate()->findOrFail($server->getKey()); - $sum_cpu = $this->user->servers->sum('cpu'); - if ($sum_cpu + $cpu > $this->cpu) { - return false; - } - } - - if ($this->memory > 0) { - if ($memory <= 0) { + if (!$userResourceLimits->canUpdateServerResources($server, $cpu, $memory, $disk)) { return false; } - $sum_memory = $this->user->servers->sum('memory'); - if ($sum_memory + $memory > $this->memory) { - return false; - } - } + $server->update([ + 'cpu' => $cpu, + 'memory' => $memory, + 'disk' => $disk, + ]); - if ($this->disk > 0) { - if ($disk <= 0) { - return false; - } + return $server; + }, 5); + } - $sum_disk = $this->user->servers->sum('disk'); - if ($sum_disk + $disk > $this->disk) { - return false; - } + public function canCreateServer(int $cpu, int $memory, int $disk): bool + { + if ($this->server_limit && $this->user->servers->count() >= $this->server_limit) { + return false; } return $this->canAllocateResources($cpu, $memory, $disk); @@ -126,6 +114,12 @@ public function canCreateServer(int $cpu, int $memory, int $disk): bool private function canAllocateResources(int $cpu, int $memory, int $disk, ?Server $excludedServer = null): bool { foreach (['cpu' => $cpu, 'memory' => $memory, 'disk' => $disk] as $resource => $requested) { + $userLimit = $this->{$resource}; + + if ($userLimit > 0 && ($requested <= 0 || $this->getUserAllocatedResource($resource, $excludedServer) + $requested > $userLimit)) { + return false; + } + $limit = (int) config("user-creatable-servers.max_{$resource}"); if ($limit > 0 && $this->getUcsAllocatedResource($resource, $excludedServer) + $requested > $limit) { @@ -158,6 +152,24 @@ private function getUcsAllocatedResource(string $resource, ?Server $excludedServ return (int) $servers->sum($resource); } + private function getUserAllocatedResource(string $resource, ?Server $excludedServer = null): int + { + $servers = Server::query()->where('owner_id', $this->user_id); + + if ($excludedServer) { + $servers->whereKeyNot($excludedServer->getKey()); + } + + return (int) $servers->sum($resource); + } + + private function lockQuotaRecord(): self + { + self::query()->orderBy('id')->lockForUpdate()->firstOrFail(); + + return self::query()->findOrFail($this->getKey()); + } + private function getLowestLimit(?int ...$limits): ?int { $limits = array_filter($limits, fn (?int $limit) => $limit !== null); @@ -168,7 +180,13 @@ private function getLowestLimit(?int ...$limits): ?int /** @param array $variables */ public function createServer(string $name, int|Egg $egg, int $cpu, int $memory, int $disk, array $variables = []): Server|false { - if ($this->canCreateServer($cpu, $memory, $disk)) { + return DB::transaction(function () use ($name, $egg, $cpu, $memory, $disk, $variables) { + $userResourceLimits = $this->lockQuotaRecord(); + + if (!$userResourceLimits->canCreateServer($cpu, $memory, $disk)) { + return false; + } + if (!$egg instanceof Egg) { $egg = Egg::findOrFail($egg); } @@ -205,8 +223,6 @@ public function createServer(string $name, int|Egg $egg, int $cpu, int $memory, $service = app(ServerCreationService::class); return $service->handle($data, $object); - } - - return false; + }, 5); } }