mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-10-31 04:36:51 +01:00 
			
		
		
		
	Server Without Allocations (#1432)
Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									6a088d0c4f
								
							
						
					
					
						commit
						dca37ccc95
					
				| @ -0,0 +1,7 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Exceptions\Service\Deployment; | ||||||
|  | 
 | ||||||
|  | use App\Exceptions\DisplayException; | ||||||
|  | 
 | ||||||
|  | class NoViableNodeException extends DisplayException {} | ||||||
| @ -38,8 +38,9 @@ class ServersRelationManager extends RelationManager | |||||||
|                     ->label(trans('admin/server.docker_image')), |                     ->label(trans('admin/server.docker_image')), | ||||||
|                 SelectColumn::make('allocation.id') |                 SelectColumn::make('allocation.id') | ||||||
|                     ->label(trans('admin/server.primary_allocation')) |                     ->label(trans('admin/server.primary_allocation')) | ||||||
|                     ->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address]) |                     ->disabled() | ||||||
|                     ->selectablePlaceholder(false) |                     ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) | ||||||
|  |                     ->placeholder('None') | ||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|             ]); |             ]); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -58,6 +58,9 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                 TextInputColumn::make('ip_alias') |                 TextInputColumn::make('ip_alias') | ||||||
|                     ->searchable() |                     ->searchable() | ||||||
|                     ->label(trans('admin/node.table.alias')), |                     ->label(trans('admin/node.table.alias')), | ||||||
|  |                 TextInputColumn::make('notes') | ||||||
|  |                     ->label(trans('admin/node.table.allocation_notes')) | ||||||
|  |                     ->placeholder(trans('admin/node.table.no_notes')), | ||||||
|                 SelectColumn::make('ip') |                 SelectColumn::make('ip') | ||||||
|                     ->options(fn (Allocation $allocation) => collect($this->getOwnerRecord()->ipAddresses())->merge([$allocation->ip])->mapWithKeys(fn (string $ip) => [$ip => $ip])) |                     ->options(fn (Allocation $allocation) => collect($this->getOwnerRecord()->ipAddresses())->merge([$allocation->ip])->mapWithKeys(fn (string $ip) => [$ip => $ip])) | ||||||
|                     ->selectablePlaceholder(false) |                     ->selectablePlaceholder(false) | ||||||
| @ -81,8 +84,7 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                             ->label(trans('admin/node.table.alias')) |                             ->label(trans('admin/node.table.alias')) | ||||||
|                             ->inlineLabel() |                             ->inlineLabel() | ||||||
|                             ->default(null) |                             ->default(null) | ||||||
|                             ->helperText(trans('admin/node.alias_help')) |                             ->helperText(trans('admin/node.alias_help')), | ||||||
|                             ->required(false), |  | ||||||
|                         TagsInput::make('allocation_ports') |                         TagsInput::make('allocation_ports') | ||||||
|                             ->placeholder('27015, 27017-27019') |                             ->placeholder('27015, 27017-27019') | ||||||
|                             ->label(trans('admin/node.ports')) |                             ->label(trans('admin/node.ports')) | ||||||
|  | |||||||
| @ -43,8 +43,10 @@ class NodesRelationManager extends RelationManager | |||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|                 SelectColumn::make('allocation.id') |                 SelectColumn::make('allocation.id') | ||||||
|                     ->label(trans('admin/node.primary_allocation')) |                     ->label(trans('admin/node.primary_allocation')) | ||||||
|                     ->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address]) |                     ->disabled(fn (Server $server) => $server->allocations->count() <= 1) | ||||||
|                     ->selectablePlaceholder(false) |                     ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) | ||||||
|  |                     ->selectablePlaceholder(fn (SelectColumn $select) => !$select->isDisabled()) | ||||||
|  |                     ->placeholder('None') | ||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|                 TextColumn::make('memory')->label(trans('admin/node.memory'))->icon('tabler-device-desktop-analytics'), |                 TextColumn::make('memory')->label(trans('admin/node.memory'))->icon('tabler-device-desktop-analytics'), | ||||||
|                 TextColumn::make('cpu')->label(trans('admin/node.cpu'))->icon('tabler-cpu'), |                 TextColumn::make('cpu')->label(trans('admin/node.cpu'))->icon('tabler-cpu'), | ||||||
|  | |||||||
| @ -128,12 +128,12 @@ class CreateServer extends CreateRecord | |||||||
|                                 ->live() |                                 ->live() | ||||||
|                                 ->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id'))) |                                 ->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id'))) | ||||||
|                                 ->searchable() |                                 ->searchable() | ||||||
|  |                                 ->required() | ||||||
|                                 ->preload() |                                 ->preload() | ||||||
|                                 ->afterStateUpdated(function (Set $set, $state) { |                                 ->afterStateUpdated(function (Set $set, $state) { | ||||||
|                                     $set('allocation_id', null); |                                     $set('allocation_id', null); | ||||||
|                                     $this->node = Node::find($state); |                                     $this->node = Node::find($state); | ||||||
|                                 }) |                                 }), | ||||||
|                                 ->required(), |  | ||||||
| 
 | 
 | ||||||
|                             Select::make('owner_id') |                             Select::make('owner_id') | ||||||
|                                 ->preload() |                                 ->preload() | ||||||
| @ -194,7 +194,7 @@ class CreateServer extends CreateRecord | |||||||
|                                     $set('allocation_additional', null); |                                     $set('allocation_additional', null); | ||||||
|                                     $set('allocation_additional.needstobeastringhere.extra_allocations', null); |                                     $set('allocation_additional.needstobeastringhere.extra_allocations', null); | ||||||
|                                 }) |                                 }) | ||||||
|                                 ->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address) |                                 ->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address ?? '') | ||||||
|                                 ->placeholder(function (Get $get) { |                                 ->placeholder(function (Get $get) { | ||||||
|                                     $node = Node::find($get('node_id')); |                                     $node = Node::find($get('node_id')); | ||||||
| 
 | 
 | ||||||
| @ -248,9 +248,7 @@ class CreateServer extends CreateRecord | |||||||
|                                     return collect( |                                     return collect( | ||||||
|                                         $assignmentService->handle(Node::find($get('node_id')), $data) |                                         $assignmentService->handle(Node::find($get('node_id')), $data) | ||||||
|                                     )->first(); |                                     )->first(); | ||||||
|                                 }) |                                 }), | ||||||
|                                 ->required(), |  | ||||||
| 
 |  | ||||||
|                             Repeater::make('allocation_additional') |                             Repeater::make('allocation_additional') | ||||||
|                                 ->label(trans('admin/server.additional_allocations')) |                                 ->label(trans('admin/server.additional_allocations')) | ||||||
|                                 ->columnSpan([ |                                 ->columnSpan([ | ||||||
| @ -270,7 +268,7 @@ class CreateServer extends CreateRecord | |||||||
|                                         ->prefixIcon('tabler-network') |                                         ->prefixIcon('tabler-network') | ||||||
|                                         ->label('Additional Allocations') |                                         ->label('Additional Allocations') | ||||||
|                                         ->columnSpan(2) |                                         ->columnSpan(2) | ||||||
|                                         ->disabled(fn (Get $get) => $get('../../node_id') === null) |                                         ->disabled(fn (Get $get) => $get('../../allocation_id') === null || $get('../../node_id') === null) | ||||||
|                                         ->searchable(['ip', 'port', 'ip_alias']) |                                         ->searchable(['ip', 'port', 'ip_alias']) | ||||||
|                                         ->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address) |                                         ->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address) | ||||||
|                                         ->placeholder(trans('admin/server.select_additional')) |                                         ->placeholder(trans('admin/server.select_additional')) | ||||||
| @ -833,7 +831,9 @@ class CreateServer extends CreateRecord | |||||||
| 
 | 
 | ||||||
|     protected function handleRecordCreation(array $data): Model |     protected function handleRecordCreation(array $data): Model | ||||||
|     { |     { | ||||||
|         $data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all(); |         if ($allocation_additional = array_get($data, 'allocation_additional')) { | ||||||
|  |             $data['allocation_additional'] = collect($allocation_additional)->filter()->all(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             return $this->serverCreationService->handle($data); |             return $this->serverCreationService->handle($data); | ||||||
|  | |||||||
| @ -1020,17 +1020,20 @@ class EditServer extends EditRecord | |||||||
|                 ->options(fn (Server $server) => Node::whereNot('id', $server->node->id)->pluck('name', 'id')->all()), |                 ->options(fn (Server $server) => Node::whereNot('id', $server->node->id)->pluck('name', 'id')->all()), | ||||||
|             Select::make('allocation_id') |             Select::make('allocation_id') | ||||||
|                 ->label(trans('admin/server.primary_allocation')) |                 ->label(trans('admin/server.primary_allocation')) | ||||||
|                 ->required() |                 ->disabled(fn (Get $get, Server $server) => !$get('node_id') || !$server->allocation_id) | ||||||
|  |                 ->required(fn (Server $server) => $server->allocation_id) | ||||||
|                 ->prefixIcon('tabler-network') |                 ->prefixIcon('tabler-network') | ||||||
|                 ->disabled(fn (Get $get) => !$get('node_id')) |  | ||||||
|                 ->options(fn (Get $get) => Allocation::where('node_id', $get('node_id'))->whereNull('server_id')->get()->mapWithKeys(fn (Allocation $allocation) => [$allocation->id => $allocation->address])) |                 ->options(fn (Get $get) => Allocation::where('node_id', $get('node_id'))->whereNull('server_id')->get()->mapWithKeys(fn (Allocation $allocation) => [$allocation->id => $allocation->address])) | ||||||
|                 ->searchable(['ip', 'port', 'ip_alias']) |                 ->searchable(['ip', 'port', 'ip_alias']) | ||||||
|                 ->placeholder(trans('admin/server.select_allocation')), |                 ->placeholder(trans('admin/server.select_allocation')), | ||||||
|             Select::make('allocation_additional') |             Select::make('allocation_additional') | ||||||
|                 ->label(trans('admin/server.additional_allocations')) |                 ->label(trans('admin/server.additional_allocations')) | ||||||
|  |                 ->disabled(fn (Get $get, Server $server) => !$get('node_id') || $server->allocations->count() <= 1) | ||||||
|                 ->multiple() |                 ->multiple() | ||||||
|  |                 ->minItems(fn (Select $select) => $select->getMaxItems()) | ||||||
|  |                 ->maxItems(fn (Select $select, Server $server) => $select->isDisabled() ? null : $server->allocations->count() - 1) | ||||||
|                 ->prefixIcon('tabler-network') |                 ->prefixIcon('tabler-network') | ||||||
|                 ->disabled(fn (Get $get) => !$get('node_id')) |                 ->required(fn (Server $server) => $server->allocations->count() > 1) | ||||||
|                 ->options(fn (Get $get) => Allocation::where('node_id', $get('node_id'))->whereNull('server_id')->when($get('allocation_id'), fn ($query) => $query->whereNot('id', $get('allocation_id')))->get()->mapWithKeys(fn (Allocation $allocation) => [$allocation->id => $allocation->address])) |                 ->options(fn (Get $get) => Allocation::where('node_id', $get('node_id'))->whereNull('server_id')->when($get('allocation_id'), fn ($query) => $query->whereNot('id', $get('allocation_id')))->get()->mapWithKeys(fn (Allocation $allocation) => [$allocation->id => $allocation->address])) | ||||||
|                 ->searchable(['ip', 'port', 'ip_alias']) |                 ->searchable(['ip', 'port', 'ip_alias']) | ||||||
|                 ->placeholder(trans('admin/server.select_additional')), |                 ->placeholder(trans('admin/server.select_additional')), | ||||||
|  | |||||||
| @ -73,14 +73,17 @@ class ListServers extends ListRecords | |||||||
|                     ->searchable(), |                     ->searchable(), | ||||||
|                 SelectColumn::make('allocation_id') |                 SelectColumn::make('allocation_id') | ||||||
|                     ->label(trans('admin/server.primary_allocation')) |                     ->label(trans('admin/server.primary_allocation')) | ||||||
|                     ->hidden(!auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
 |                     ->hidden(fn () => !auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
 | ||||||
|  |                     ->disabled(fn (Server $server) => $server->allocations->count() <= 1) | ||||||
|                     ->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) |                     ->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) | ||||||
|                     ->selectablePlaceholder(false) |                     ->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1) | ||||||
|  |                     ->placeholder('None') | ||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|                 TextColumn::make('allocation_id_readonly') |                 TextColumn::make('allocation_id_readonly') | ||||||
|                     ->label(trans('admin/server.primary_allocation')) |                     ->label(trans('admin/server.primary_allocation')) | ||||||
|                     ->hidden(auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
 |                     ->hidden(fn () => auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
 | ||||||
|                     ->state(fn (Server $server) => $server->allocation->address), |                     ->disabled(fn (Server $server) => $server->allocations->count() <= 1) | ||||||
|  |                     ->state(fn (Server $server) => $server->allocation->address ?? 'None'), | ||||||
|                 TextColumn::make('image')->hidden(), |                 TextColumn::make('image')->hidden(), | ||||||
|                 TextColumn::make('backups_count') |                 TextColumn::make('backups_count') | ||||||
|                     ->counts('backups') |                     ->counts('backups') | ||||||
|  | |||||||
| @ -12,8 +12,6 @@ use Filament\Forms\Components\TextInput; | |||||||
| use Filament\Forms\Get; | use Filament\Forms\Get; | ||||||
| use Filament\Forms\Set; | use Filament\Forms\Set; | ||||||
| use Filament\Resources\RelationManagers\RelationManager; | use Filament\Resources\RelationManagers\RelationManager; | ||||||
| use Filament\Support\Exceptions\Halt; |  | ||||||
| use Filament\Tables\Actions\Action; |  | ||||||
| use Filament\Tables\Actions\AssociateAction; | use Filament\Tables\Actions\AssociateAction; | ||||||
| use Filament\Tables\Actions\CreateAction; | use Filament\Tables\Actions\CreateAction; | ||||||
| use Filament\Tables\Actions\DissociateAction; | use Filament\Tables\Actions\DissociateAction; | ||||||
| @ -22,7 +20,6 @@ use Filament\Tables\Columns\IconColumn; | |||||||
| use Filament\Tables\Columns\TextColumn; | use Filament\Tables\Columns\TextColumn; | ||||||
| use Filament\Tables\Columns\TextInputColumn; | use Filament\Tables\Columns\TextInputColumn; | ||||||
| use Filament\Tables\Table; | use Filament\Tables\Table; | ||||||
| use Illuminate\Database\Eloquent\Collection; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @method Server getOwnerRecord() |  * @method Server getOwnerRecord() | ||||||
| @ -37,7 +34,6 @@ class AllocationsRelationManager extends RelationManager | |||||||
|             ->selectCurrentPageOnly() |             ->selectCurrentPageOnly() | ||||||
|             ->recordTitleAttribute('address') |             ->recordTitleAttribute('address') | ||||||
|             ->recordTitle(fn (Allocation $allocation) => $allocation->address) |             ->recordTitle(fn (Allocation $allocation) => $allocation->address) | ||||||
|             ->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id) |  | ||||||
|             ->inverseRelationship('server') |             ->inverseRelationship('server') | ||||||
|             ->heading(trans('admin/server.allocations')) |             ->heading(trans('admin/server.allocations')) | ||||||
|             ->columns([ |             ->columns([ | ||||||
| @ -47,6 +43,9 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                     ->label(trans('admin/server.port')), |                     ->label(trans('admin/server.port')), | ||||||
|                 TextInputColumn::make('ip_alias') |                 TextInputColumn::make('ip_alias') | ||||||
|                     ->label(trans('admin/server.alias')), |                     ->label(trans('admin/server.alias')), | ||||||
|  |                 TextInputColumn::make('notes') | ||||||
|  |                     ->label(trans('admin/server.notes')) | ||||||
|  |                     ->placeholder(trans('admin/server.no_notes')), | ||||||
|                 IconColumn::make('primary') |                 IconColumn::make('primary') | ||||||
|                     ->icon(fn ($state) => match ($state) { |                     ->icon(fn ($state) => match ($state) { | ||||||
|                         true => 'tabler-star-filled', |                         true => 'tabler-star-filled', | ||||||
| @ -56,17 +55,17 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                         true => 'warning', |                         true => 'warning', | ||||||
|                         default => 'gray', |                         default => 'gray', | ||||||
|                     }) |                     }) | ||||||
|  |                     ->tooltip(fn (Allocation $allocation) => trans('admin/server.' . ($allocation->id === $this->getOwnerRecord()->allocation_id ? 'already' : 'make') . '_primary')) | ||||||
|                     ->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords()) |                     ->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords()) | ||||||
|                     ->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id) |                     ->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id) | ||||||
|                     ->label(trans('admin/server.primary')), |                     ->label(trans('admin/server.primary')), | ||||||
|             ]) |             ]) | ||||||
|             ->actions([ |             ->actions([ | ||||||
|                 Action::make('make-primary') |  | ||||||
|                     ->label(trans('admin/server.make_primary')) |  | ||||||
|                     ->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords()) |  | ||||||
|                     ->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id), |  | ||||||
|                 DissociateAction::make() |                 DissociateAction::make() | ||||||
|                     ->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id), |                     ->after(function (Allocation $allocation) { | ||||||
|  |                         $allocation->update(['notes' => null]); | ||||||
|  |                         $this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); | ||||||
|  |                     }), | ||||||
|             ]) |             ]) | ||||||
|             ->headerActions([ |             ->headerActions([ | ||||||
|                 CreateAction::make()->label(trans('admin/server.create_allocation')) |                 CreateAction::make()->label(trans('admin/server.create_allocation')) | ||||||
| @ -84,8 +83,7 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                             ->label(trans('admin/server.alias')) |                             ->label(trans('admin/server.alias')) | ||||||
|                             ->inlineLabel() |                             ->inlineLabel() | ||||||
|                             ->default(null) |                             ->default(null) | ||||||
|                             ->helperText(trans('admin/server.alias_helper')) |                             ->helperText(trans('admin/server.alias_helper')), | ||||||
|                             ->required(false), |  | ||||||
|                         TagsInput::make('allocation_ports') |                         TagsInput::make('allocation_ports') | ||||||
|                             ->placeholder('27015, 27017-27019') |                             ->placeholder('27015, 27017-27019') | ||||||
|                             ->label(trans('admin/server.ports')) |                             ->label(trans('admin/server.ports')) | ||||||
| @ -103,22 +101,14 @@ class AllocationsRelationManager extends RelationManager | |||||||
|                     ->preloadRecordSelect() |                     ->preloadRecordSelect() | ||||||
|                     ->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id')) |                     ->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id')) | ||||||
|                     ->recordSelectSearchColumns(['ip', 'port']) |                     ->recordSelectSearchColumns(['ip', 'port']) | ||||||
|                     ->label(trans('admin/server.add_allocation')), |                     ->label(trans('admin/server.add_allocation')) | ||||||
|  |                     ->after(fn (array $data) => !$this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $data['recordId'][0]])), | ||||||
|             ]) |             ]) | ||||||
|             ->groupedBulkActions([ |             ->groupedBulkActions([ | ||||||
|                 DissociateBulkAction::make() |                 DissociateBulkAction::make() | ||||||
|                     ->before(function (DissociateBulkAction $action, Collection $records) { |                     ->after(function () { | ||||||
|                         $records = $records->filter(function ($allocation) { |                         Allocation::whereNull('server_id')->update(['notes' => null]); | ||||||
|                             /** @var Allocation $allocation */ |                         $this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); | ||||||
|                             return $allocation->id !== $this->getOwnerRecord()->allocation_id; |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                         if ($records->isEmpty()) { |  | ||||||
|                             $action->failureNotificationTitle(trans('admin/server.notifications.dissociate_primary'))->failure(); |  | ||||||
|                             throw new Halt(); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         return $records; |  | ||||||
|                     }), |                     }), | ||||||
|             ]); |             ]); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -70,8 +70,9 @@ class ServersRelationManager extends RelationManager | |||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|                 SelectColumn::make('allocation.id') |                 SelectColumn::make('allocation.id') | ||||||
|                     ->label(trans('admin/server.primary_allocation')) |                     ->label(trans('admin/server.primary_allocation')) | ||||||
|                     ->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address]) |                     ->disabled() | ||||||
|                     ->selectablePlaceholder(false) |                     ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) | ||||||
|  |                     ->placeholder('None') | ||||||
|                     ->sortable(), |                     ->sortable(), | ||||||
|                 TextColumn::make('image')->hidden(), |                 TextColumn::make('image')->hidden(), | ||||||
|                 TextColumn::make('databases_count') |                 TextColumn::make('databases_count') | ||||||
|  | |||||||
| @ -74,7 +74,8 @@ class ListServers extends ListRecords | |||||||
|                 ->label('') |                 ->label('') | ||||||
|                 ->badge() |                 ->badge() | ||||||
|                 ->visibleFrom('md') |                 ->visibleFrom('md') | ||||||
|                 ->copyable(request()->isSecure()), |                 ->copyable(request()->isSecure()) | ||||||
|  |                 ->state(fn (Server $server) => $server->allocation->address ?? 'None'), | ||||||
|             TextColumn::make('cpuUsage') |             TextColumn::make('cpuUsage') | ||||||
|                 ->label('Resources') |                 ->label('Resources') | ||||||
|                 ->icon('tabler-cpu') |                 ->icon('tabler-cpu') | ||||||
|  | |||||||
| @ -65,11 +65,8 @@ class AllocationResource extends Resource | |||||||
|                         true => 'warning', |                         true => 'warning', | ||||||
|                         default => 'gray', |                         default => 'gray', | ||||||
|                     }) |                     }) | ||||||
|                     ->action(function (Allocation $allocation) use ($server) { |                     ->tooltip(fn (Allocation $allocation) => ($allocation->id === $server->allocation_id ? 'Already' : 'Make') . ' Primary') | ||||||
|                         if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) { |                     ->action(fn (Allocation $allocation) => auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server) && $server->update(['allocation_id' => $allocation->id])) | ||||||
|                             return $server->update(['allocation_id' => $allocation->id]); |  | ||||||
|                         } |  | ||||||
|                     }) |  | ||||||
|                     ->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) |                     ->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) | ||||||
|                     ->label('Primary'), |                     ->label('Primary'), | ||||||
|             ]) |             ]) | ||||||
| @ -78,7 +75,6 @@ class AllocationResource extends Resource | |||||||
|                     ->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server)) |                     ->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server)) | ||||||
|                     ->label('Delete') |                     ->label('Delete') | ||||||
|                     ->icon('tabler-trash') |                     ->icon('tabler-trash') | ||||||
|                     ->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) |  | ||||||
|                     ->action(function (Allocation $allocation) { |                     ->action(function (Allocation $allocation) { | ||||||
|                         Allocation::query()->where('id', $allocation->id)->update([ |                         Allocation::query()->where('id', $allocation->id)->update([ | ||||||
|                             'notes' => null, |                             'notes' => null, | ||||||
| @ -89,7 +85,8 @@ class AllocationResource extends Resource | |||||||
|                             ->subject($allocation) |                             ->subject($allocation) | ||||||
|                             ->property('allocation', $allocation->address) |                             ->property('allocation', $allocation->address) | ||||||
|                             ->log(); |                             ->log(); | ||||||
|                     }), |                     }) | ||||||
|  |                     ->after(fn (Allocation $allocation) => $allocation->id === $server->allocation_id && $server->update(['allocation_id' => $server->allocations()->first()?->id])), | ||||||
|             ]); |             ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -37,6 +37,10 @@ class ListAllocations extends ListRecords | |||||||
|                 ->action(function (FindAssignableAllocationService $service) use ($server) { |                 ->action(function (FindAssignableAllocationService $service) use ($server) { | ||||||
|                     $allocation = $service->handle($server); |                     $allocation = $service->handle($server); | ||||||
| 
 | 
 | ||||||
|  |                     if (!$server->allocation_id) { | ||||||
|  |                         $server->update(['allocation_id' => $allocation->id]); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|                     Activity::event('server:allocation.create') |                     Activity::event('server:allocation.create') | ||||||
|                         ->subject($allocation) |                         ->subject($allocation) | ||||||
|                         ->property('allocation', $allocation->address) |                         ->property('allocation', $allocation->address) | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ class ServerOverview extends StatsOverviewWidget | |||||||
|             SmallStatBlock::make('Name', $this->server->name) |             SmallStatBlock::make('Name', $this->server->name) | ||||||
|                 ->copyOnClick(fn () => request()->isSecure()), |                 ->copyOnClick(fn () => request()->isSecure()), | ||||||
|             SmallStatBlock::make('Status', $this->status()), |             SmallStatBlock::make('Status', $this->status()), | ||||||
|             SmallStatBlock::make('Address', $this->server->allocation->address) |             SmallStatBlock::make('Address', $this->server?->allocation->address ?? 'None') | ||||||
|                 ->copyOnClick(fn () => request()->isSecure()), |                 ->copyOnClick(fn () => request()->isSecure()), | ||||||
|             SmallStatBlock::make('CPU', $this->cpuUsage()), |             SmallStatBlock::make('CPU', $this->cpuUsage()), | ||||||
|             SmallStatBlock::make('Memory', $this->memoryUsage()), |             SmallStatBlock::make('Memory', $this->memoryUsage()), | ||||||
|  | |||||||
| @ -137,10 +137,6 @@ class NetworkAllocationController extends ClientApiController | |||||||
|             throw new DisplayException('You cannot delete allocations for this server: no allocation limit is set.'); |             throw new DisplayException('You cannot delete allocations for this server: no allocation limit is set.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ($allocation->id === $server->allocation_id) { |  | ||||||
|             throw new DisplayException('You cannot delete the primary allocation for this server.'); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Allocation::query()->where('id', $allocation->id)->update([ |         Allocation::query()->where('id', $allocation->id)->update([ | ||||||
|             'notes' => null, |             'notes' => null, | ||||||
|             'server_id' => null, |             'server_id' => null, | ||||||
|  | |||||||
| @ -50,17 +50,18 @@ class ServerTransferController extends Controller | |||||||
|             throw new ConflictHttpException('Server is not being transferred.'); |             throw new ConflictHttpException('Server is not being transferred.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $data = []; | ||||||
|         /** @var \App\Models\Server $server */ |         /** @var \App\Models\Server $server */ | ||||||
|         $server = $this->connection->transaction(function () use ($server, $transfer) { |         $server = $this->connection->transaction(function () use ($server, $transfer, $data) { | ||||||
|  |             if ($transfer->old_allocation || $transfer->old_additional_allocations) { | ||||||
|                 $allocations = array_merge([$transfer->old_allocation], $transfer->old_additional_allocations); |                 $allocations = array_merge([$transfer->old_allocation], $transfer->old_additional_allocations); | ||||||
| 
 |  | ||||||
|                 // Remove the old allocations for the server and re-assign the server to the new
 |                 // Remove the old allocations for the server and re-assign the server to the new
 | ||||||
|                 // primary allocation and node.
 |                 // primary allocation and node.
 | ||||||
|                 Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); |                 Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); | ||||||
|             $server->update([ |                 $data['allocation_id'] = $transfer->new_allocation; | ||||||
|                 'allocation_id' => $transfer->new_allocation, |             } | ||||||
|                 'node_id' => $transfer->new_node, |             $data['node_id'] = $transfer->new_node; | ||||||
|             ]); |             $server->update($data); | ||||||
| 
 | 
 | ||||||
|             $server = $server->fresh(); |             $server = $server->fresh(); | ||||||
|             $server->transfer->update(['successful' => true]); |             $server->transfer->update(['successful' => true]); | ||||||
| @ -93,8 +94,10 @@ class ServerTransferController extends Controller | |||||||
|         $this->connection->transaction(function () use (&$transfer) { |         $this->connection->transaction(function () use (&$transfer) { | ||||||
|             $transfer->forceFill(['successful' => false])->saveOrFail(); |             $transfer->forceFill(['successful' => false])->saveOrFail(); | ||||||
| 
 | 
 | ||||||
|  |             if ($transfer->new_allocation || $transfer->new_additional_allocations) { | ||||||
|                 $allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations); |                 $allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations); | ||||||
|                 Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); |                 Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         return new JsonResponse([], Response::HTTP_NO_CONTENT); |         return new JsonResponse([], Response::HTTP_NO_CONTENT); | ||||||
|  | |||||||
| @ -176,6 +176,7 @@ class StoreServerRequest extends ApplicationApiRequest | |||||||
|         $object->setDedicated($this->input('deploy.dedicated_ip', false)); |         $object->setDedicated($this->input('deploy.dedicated_ip', false)); | ||||||
|         $object->setTags($this->input('deploy.tags', $this->input('deploy.locations', []))); |         $object->setTags($this->input('deploy.tags', $this->input('deploy.locations', []))); | ||||||
|         $object->setPorts($this->input('deploy.port_range', [])); |         $object->setPorts($this->input('deploy.port_range', [])); | ||||||
|  |         $object->setNode($this->input('deploy.node_id')); | ||||||
| 
 | 
 | ||||||
|         return $object; |         return $object; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ class ServerEntry extends Component | |||||||
|                     <div class="hidden sm:block"> |                     <div class="hidden sm:block"> | ||||||
|                         <p class="text-sm dark:text-gray-400">Network</p> |                         <p class="text-sm dark:text-gray-400">Network</p> | ||||||
|                         <hr class="p-0.5"> |                         <hr class="p-0.5"> | ||||||
|                         <p class="text-md font-semibold">{{ $server->allocation->address }} </p> |                         <p class="text-md font-semibold">{{ $server->allocation?->address ?? 'None' }} </p> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -72,20 +72,6 @@ class Allocation extends Model | |||||||
|         static::deleting(function (self $allocation) { |         static::deleting(function (self $allocation) { | ||||||
|             throw_if($allocation->server_id, new ServerUsingAllocationException(trans('exceptions.allocations.server_using'))); |             throw_if($allocation->server_id, new ServerUsingAllocationException(trans('exceptions.allocations.server_using'))); | ||||||
|         }); |         }); | ||||||
| 
 |  | ||||||
|         static::updating(function ($allocation) { |  | ||||||
|             $originalServerId = $allocation->getOriginal('server_id'); |  | ||||||
|             if (!$originalServerId) { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             $server = Server::find($originalServerId); |  | ||||||
|             if (!$server) { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             if ($allocation->isDirty('server_id') && is_null($allocation->server_id) && $allocation->id === $server->allocation_id) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected function casts(): array |     protected function casts(): array | ||||||
|  | |||||||
| @ -2,8 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Models\Objects; | namespace App\Models\Objects; | ||||||
| 
 | 
 | ||||||
|  | use App\Models\Node; | ||||||
|  | 
 | ||||||
| class DeploymentObject | class DeploymentObject | ||||||
| { | { | ||||||
|  |     private ?Node $node = null; | ||||||
|  | 
 | ||||||
|     private bool $dedicated = false; |     private bool $dedicated = false; | ||||||
| 
 | 
 | ||||||
|     /** @var string[] */ |     /** @var string[] */ | ||||||
| @ -12,6 +16,18 @@ class DeploymentObject | |||||||
|     /** @var array<int|string> */ |     /** @var array<int|string> */ | ||||||
|     private array $ports = []; |     private array $ports = []; | ||||||
| 
 | 
 | ||||||
|  |     public function getNode(): ?Node | ||||||
|  |     { | ||||||
|  |         return $this->node; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function setNode(Node $node): self | ||||||
|  |     { | ||||||
|  |         $this->node = $node; | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function isDedicated(): bool |     public function isDedicated(): bool | ||||||
|     { |     { | ||||||
|         return $this->dedicated; |         return $this->dedicated; | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ use App\Services\Subusers\SubuserDeletionService; | |||||||
|  * @property int $cpu |  * @property int $cpu | ||||||
|  * @property string|null $threads |  * @property string|null $threads | ||||||
|  * @property bool $oom_killer |  * @property bool $oom_killer | ||||||
|  * @property int $allocation_id |  * @property int|null $allocation_id | ||||||
|  * @property int $egg_id |  * @property int $egg_id | ||||||
|  * @property string $startup |  * @property string $startup | ||||||
|  * @property string $image |  * @property string $image | ||||||
| @ -171,7 +171,7 @@ class Server extends Model implements Validatable | |||||||
|         'threads' => ['nullable', 'regex:/^[0-9-,]+$/'], |         'threads' => ['nullable', 'regex:/^[0-9-,]+$/'], | ||||||
|         'oom_killer' => ['sometimes', 'boolean'], |         'oom_killer' => ['sometimes', 'boolean'], | ||||||
|         'disk' => ['required', 'numeric', 'min:0'], |         'disk' => ['required', 'numeric', 'min:0'], | ||||||
|         'allocation_id' => ['required', 'bail', 'unique:servers', 'exists:allocations,id'], |         'allocation_id' => ['sometimes', 'nullable', 'unique:servers', 'exists:allocations,id'], | ||||||
|         'egg_id' => ['required', 'exists:eggs,id'], |         'egg_id' => ['required', 'exists:eggs,id'], | ||||||
|         'startup' => ['required', 'string'], |         'startup' => ['required', 'string'], | ||||||
|         'skip_scripts' => ['sometimes', 'boolean'], |         'skip_scripts' => ['sometimes', 'boolean'], | ||||||
| @ -220,10 +220,14 @@ class Server extends Model implements Validatable | |||||||
|     /** |     /** | ||||||
|      * Returns the format for server allocations when communicating with the Daemon. |      * Returns the format for server allocations when communicating with the Daemon. | ||||||
|      * |      * | ||||||
|      * @return array<int> |      * @return array<string, array<int>> | ||||||
|      */ |      */ | ||||||
|     public function getAllocationMappings(): array |     public function getAllocationMappings(): array | ||||||
|     { |     { | ||||||
|  |         if (!$this->allocation) { | ||||||
|  |             return ['' => []]; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) { |         return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) { | ||||||
|             return $item->pluck('port'); |             return $item->pluck('port'); | ||||||
|         })->toArray(); |         })->toArray(); | ||||||
| @ -272,6 +276,8 @@ class Server extends Model implements Validatable | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Gets all allocations associated with this server. |      * Gets all allocations associated with this server. | ||||||
|  |      * | ||||||
|  |      * @return HasMany<Allocation, $this> | ||||||
|      */ |      */ | ||||||
|     public function allocations(): HasMany |     public function allocations(): HasMany | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -13,8 +13,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; | |||||||
|  * @property int $server_id |  * @property int $server_id | ||||||
|  * @property int $old_node |  * @property int $old_node | ||||||
|  * @property int $new_node |  * @property int $new_node | ||||||
|  * @property int $old_allocation |  * @property int|null $old_allocation | ||||||
|  * @property int $new_allocation |  * @property int|null $new_allocation | ||||||
|  * @property array<int>|null $old_additional_allocations array of allocation.id's |  * @property array<int>|null $old_additional_allocations array of allocation.id's | ||||||
|  * @property array<int>|null $new_additional_allocations array of allocation.id's |  * @property array<int>|null $new_additional_allocations array of allocation.id's | ||||||
|  * @property bool|null $successful |  * @property bool|null $successful | ||||||
| @ -45,8 +45,8 @@ class ServerTransfer extends Model implements Validatable | |||||||
|         'server_id' => ['required', 'numeric', 'exists:servers,id'], |         'server_id' => ['required', 'numeric', 'exists:servers,id'], | ||||||
|         'old_node' => ['required', 'numeric'], |         'old_node' => ['required', 'numeric'], | ||||||
|         'new_node' => ['required', 'numeric'], |         'new_node' => ['required', 'numeric'], | ||||||
|         'old_allocation' => ['required', 'numeric'], |         'old_allocation' => ['nullable', 'numeric'], | ||||||
|         'new_allocation' => ['required', 'numeric'], |         'new_allocation' => ['nullable', 'numeric'], | ||||||
|         'old_additional_allocations' => ['nullable', 'array'], |         'old_additional_allocations' => ['nullable', 'array'], | ||||||
|         'old_additional_allocations.*' => ['numeric'], |         'old_additional_allocations.*' => ['numeric'], | ||||||
|         'new_additional_allocations' => ['nullable', 'array'], |         'new_additional_allocations' => ['nullable', 'array'], | ||||||
|  | |||||||
| @ -107,6 +107,10 @@ class AssignmentService | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if ($server && !$server->allocation_id) { | ||||||
|  |             $server->update(['allocation_id' => $ids[0]]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $this->connection->commit(); |         $this->connection->commit(); | ||||||
| 
 | 
 | ||||||
|         return $ids; |         return $ids; | ||||||
|  | |||||||
| @ -37,7 +37,9 @@ class FindAssignableAllocationService | |||||||
|         // server.
 |         // server.
 | ||||||
|         /** @var \App\Models\Allocation|null $allocation */ |         /** @var \App\Models\Allocation|null $allocation */ | ||||||
|         $allocation = $server->node->allocations() |         $allocation = $server->node->allocations() | ||||||
|             ->where('ip', $server->allocation->ip) |             ->when($server->allocation, function ($query) use ($server) { | ||||||
|  |                 $query->where('ip', $server->allocation->ip); | ||||||
|  |             }) | ||||||
|             ->whereNull('server_id') |             ->whereNull('server_id') | ||||||
|             ->inRandomOrder() |             ->inRandomOrder() | ||||||
|             ->first(); |             ->first(); | ||||||
|  | |||||||
| @ -80,12 +80,10 @@ class BuildModificationService | |||||||
|      * @param array{ |      * @param array{ | ||||||
|      *     add_allocations?: array<int>, |      *     add_allocations?: array<int>, | ||||||
|      *     remove_allocations?: array<int>, |      *     remove_allocations?: array<int>, | ||||||
|      *     allocation_id?: int, |      *     allocation_id: ?int, | ||||||
|      *     oom_killer?: bool, |      *     oom_killer?: bool, | ||||||
|      *     oom_disabled?: bool, |      *     oom_disabled?: bool, | ||||||
|      * } $data |      * } $data | ||||||
|      * |  | ||||||
|      * @throws \App\Exceptions\DisplayException |  | ||||||
|      */ |      */ | ||||||
|     private function processAllocations(Server $server, array &$data): void |     private function processAllocations(Server $server, array &$data): void | ||||||
|     { |     { | ||||||
| @ -101,35 +99,26 @@ class BuildModificationService | |||||||
|                 ->whereIn('id', $data['add_allocations']) |                 ->whereIn('id', $data['add_allocations']) | ||||||
|                 ->whereNull('server_id'); |                 ->whereNull('server_id'); | ||||||
| 
 | 
 | ||||||
|             // Keep track of all the allocations we're just now adding so that we can use the first
 |             $query->update(['server_id' => $server->id]); | ||||||
|             // one to reset the default allocation to.
 |  | ||||||
|             $freshlyAllocated = $query->first()?->id; |  | ||||||
| 
 |  | ||||||
|             $query->update(['server_id' => $server->id, 'notes' => null]); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!empty($data['remove_allocations'])) { |         if (!empty($data['remove_allocations'])) { | ||||||
|             foreach ($data['remove_allocations'] as $allocation) { |             $allocations = Allocation::query() | ||||||
|                 // If we are attempting to remove the default allocation for the server, see if we can reassign
 |                 ->where('server_id', $server->id) | ||||||
|                 // to the first provided value in add_allocations. If there is no new first allocation then we
 |                 // Only use the allocations that we didn't also attempt to add to the server...
 | ||||||
|                 // will throw an exception back.
 |                 ->whereIn('id', array_diff($data['remove_allocations'], $data['add_allocations'] ?? [])); | ||||||
|                 if ($allocation === ($data['allocation_id'] ?? $server->allocation_id)) { |  | ||||||
|                     if (empty($freshlyAllocated)) { |  | ||||||
|                         throw new DisplayException('You are attempting to delete the default allocation for this server but there is no fallback allocation to use.'); |  | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|                     // Update the default allocation to be the first allocation that we are creating.
 |             // If we are attempting to remove the default allocation for the server, see if we can reassign
 | ||||||
|                     $data['allocation_id'] = $freshlyAllocated; |             // to the first provided value in add_allocations.
 | ||||||
|                 } |             if ((clone $allocations)->where('id', $server->allocation_id)->exists()) { | ||||||
|  |                 $nonPrimaryAllocations = $server->allocations->whereNotIn('id', $data['remove_allocations']); | ||||||
|  |                 $data['allocation_id'] = $nonPrimaryAllocations->first()->id ?? ($data['add_allocations'][0] ?? null); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Remove any of the allocations we got that are currently assigned to this server on
 |             // Remove any of the allocations we got that are currently assigned to this server on
 | ||||||
|             // this node. Also set the notes to null, otherwise when re-allocated to a new server those
 |             // this node. Also set the notes to null, otherwise when re-allocated to a new server those
 | ||||||
|             // notes will be carried over.
 |             // notes will be carried over.
 | ||||||
|             Allocation::query()->where('node_id', $server->node_id) |             $allocations | ||||||
|                 ->where('server_id', $server->id) |  | ||||||
|                 // Only remove the allocations that we didn't also attempt to add to the server...
 |  | ||||||
|                 ->whereIn('id', array_diff($data['remove_allocations'], $data['add_allocations'] ?? [])) |  | ||||||
|                 ->update([ |                 ->update([ | ||||||
|                     'notes' => null, |                     'notes' => null, | ||||||
|                     'server_id' => null, |                     'server_id' => null, | ||||||
|  | |||||||
| @ -56,7 +56,7 @@ class ServerConfigurationStructureService | |||||||
|      *     allocations: array{ |      *     allocations: array{ | ||||||
|      *         force_outgoing_ip: bool, |      *         force_outgoing_ip: bool, | ||||||
|      *         default: array{ip: string, port: int}, |      *         default: array{ip: string, port: int}, | ||||||
|      *         mappings: array<int>, |      *         mappings: array<string, array<int>>, | ||||||
|      *     }, |      *     }, | ||||||
|      *     egg: array{id: string, file_denylist: string[]}, |      *     egg: array{id: string, file_denylist: string[]}, | ||||||
|      *     labels?: string[], |      *     labels?: string[], | ||||||
| @ -93,8 +93,8 @@ class ServerConfigurationStructureService | |||||||
|             'allocations' => [ |             'allocations' => [ | ||||||
|                 'force_outgoing_ip' => $server->egg->force_outgoing_ip, |                 'force_outgoing_ip' => $server->egg->force_outgoing_ip, | ||||||
|                 'default' => [ |                 'default' => [ | ||||||
|                     'ip' => $server->allocation->ip, |                     'ip' => $server->allocation->ip ?? '127.0.0.1', | ||||||
|                     'port' => $server->allocation->port, |                     'port' => $server->allocation->port ?? 0, | ||||||
|                 ], |                 ], | ||||||
|                 'mappings' => $server->getAllocationMappings(), |                 'mappings' => $server->getAllocationMappings(), | ||||||
|             ], |             ], | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| namespace App\Services\Servers; | namespace App\Services\Servers; | ||||||
| 
 | 
 | ||||||
| use App\Enums\ServerState; | use App\Enums\ServerState; | ||||||
|  | use App\Exceptions\Service\Deployment\NoViableNodeException; | ||||||
| use Illuminate\Http\Client\ConnectionException; | use Illuminate\Http\Client\ConnectionException; | ||||||
| use Ramsey\Uuid\Uuid; | use Ramsey\Uuid\Uuid; | ||||||
| use Illuminate\Support\Arr; | use Illuminate\Support\Arr; | ||||||
| @ -39,6 +40,7 @@ class ServerCreationService | |||||||
|      * no node_id the node_is will be picked from the allocation. |      * no node_id the node_is will be picked from the allocation. | ||||||
|      * |      * | ||||||
|      * @param array{ |      * @param array{ | ||||||
|  |      *     node_id?: int, | ||||||
|      *     oom_killer?: bool, |      *     oom_killer?: bool, | ||||||
|      *     oom_disabled?: bool, |      *     oom_disabled?: bool, | ||||||
|      *     egg_id?: int, |      *     egg_id?: int, | ||||||
| @ -67,19 +69,18 @@ class ServerCreationService | |||||||
| 
 | 
 | ||||||
|         // If a deployment object has been passed we need to get the allocation
 |         // If a deployment object has been passed we need to get the allocation
 | ||||||
|         // that the server should use, and assign the node from that allocation.
 |         // that the server should use, and assign the node from that allocation.
 | ||||||
|         if ($deployment instanceof DeploymentObject) { |         if ($deployment) { | ||||||
|             $allocation = $this->configureDeployment($data, $deployment); |             $allocation = $this->configureDeployment($data, $deployment); | ||||||
|             $data['allocation_id'] = $allocation->id; |  | ||||||
|             $data['node_id'] = $allocation->node_id; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|  |             if ($allocation) { | ||||||
|  |                 $data['allocation_id'] = $allocation->id; | ||||||
|                 // Auto-configure the node based on the selected allocation
 |                 // Auto-configure the node based on the selected allocation
 | ||||||
|                 // if no node was defined.
 |                 // if no node was defined.
 | ||||||
|         if (empty($data['node_id'])) { |                 $data['node_id'] = $allocation->node_id; | ||||||
|             Assert::false(empty($data['allocation_id']), 'Expected a non-empty allocation_id in server creation data.'); |  | ||||||
| 
 |  | ||||||
|             $data['node_id'] = Allocation::query()->findOrFail($data['allocation_id'])->node_id; |  | ||||||
|             } |             } | ||||||
|  |             $data['node_id'] ??= $deployment->getNode()->id; | ||||||
|  |         } | ||||||
|  |         Assert::false(empty($data['node_id']), 'Expected a non-empty node_id in server creation data.'); | ||||||
| 
 | 
 | ||||||
|         $eggVariableData = $this->validatorService |         $eggVariableData = $this->validatorService | ||||||
|             ->setUserLevel(User::USER_LEVEL_ADMIN) |             ->setUserLevel(User::USER_LEVEL_ADMIN) | ||||||
| @ -95,7 +96,10 @@ class ServerCreationService | |||||||
|             // Create the server and assign any additional allocations to it.
 |             // Create the server and assign any additional allocations to it.
 | ||||||
|             $server = $this->createModel($data); |             $server = $this->createModel($data); | ||||||
| 
 | 
 | ||||||
|  |             if ($server->allocation_id) { | ||||||
|                 $this->storeAssignedAllocations($server, $data); |                 $this->storeAssignedAllocations($server, $data); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $this->storeEggVariables($server, $eggVariableData); |             $this->storeEggVariables($server, $eggVariableData); | ||||||
| 
 | 
 | ||||||
|             return $server; |             return $server; | ||||||
| @ -119,10 +123,10 @@ class ServerCreationService | |||||||
|      * |      * | ||||||
|      * @param  array{memory?: ?int, disk?: ?int, cpu?: ?int, tags?: ?string[]}  $data |      * @param  array{memory?: ?int, disk?: ?int, cpu?: ?int, tags?: ?string[]}  $data | ||||||
|      * |      * | ||||||
|      * @throws \App\Exceptions\DisplayException |  | ||||||
|      * @throws \App\Exceptions\Service\Deployment\NoViableAllocationException |      * @throws \App\Exceptions\Service\Deployment\NoViableAllocationException | ||||||
|  |      * @throws \App\Exceptions\Service\Deployment\NoViableNodeException | ||||||
|      */ |      */ | ||||||
|     private function configureDeployment(array $data, DeploymentObject $deployment): Allocation |     private function configureDeployment(array $data, DeploymentObject $deployment): ?Allocation | ||||||
|     { |     { | ||||||
|         $nodes = $this->findViableNodesService->handle( |         $nodes = $this->findViableNodesService->handle( | ||||||
|             Arr::get($data, 'memory', 0), |             Arr::get($data, 'memory', 0), | ||||||
| @ -131,8 +135,18 @@ class ServerCreationService | |||||||
|             $deployment->getTags(), |             $deployment->getTags(), | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  |         $availableNodes = $nodes->pluck('id'); | ||||||
|  | 
 | ||||||
|  |         if ($availableNodes->isEmpty()) { | ||||||
|  |             throw new NoViableNodeException(trans('exceptions.deployment.no_viable_nodes')); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!$deployment->getPorts()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) |         return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) | ||||||
|             ->setNodes($nodes->pluck('id')->toArray()) |             ->setNodes($availableNodes->toArray()) | ||||||
|             ->setPorts($deployment->getPorts()) |             ->setPorts($deployment->getPorts()) | ||||||
|             ->handle(); |             ->handle(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -12,7 +12,11 @@ class StartupCommandService | |||||||
|     public function handle(Server $server, bool $hideAllValues = false): string |     public function handle(Server $server, bool $hideAllValues = false): string | ||||||
|     { |     { | ||||||
|         $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; |         $find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}']; | ||||||
|         $replace = [(string) $server->memory, $server->allocation->ip, (string) $server->allocation->port]; |         $replace = [ | ||||||
|  |             (string) $server->memory, | ||||||
|  |             $server->allocation->ip ?? '127.0.0.1', | ||||||
|  |             (string) ($server->allocation->port ?? '0'), | ||||||
|  |         ]; | ||||||
| 
 | 
 | ||||||
|         foreach ($server->variables as $variable) { |         foreach ($server->variables as $variable) { | ||||||
|             $find[] = '{{' . $variable->env_variable . '}}'; |             $find[] = '{{' . $variable->env_variable . '}}'; | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ class TransferServerService | |||||||
|      * |      * | ||||||
|      * @throws \Throwable |      * @throws \Throwable | ||||||
|      */ |      */ | ||||||
|     public function handle(Server $server, int $node_id, int $allocation_id, array $additional_allocations): bool |     public function handle(Server $server, int $node_id, ?int $allocation_id = null, ?array $additional_allocations = []): bool | ||||||
|     { |     { | ||||||
|         $additional_allocations = array_map(intval(...), $additional_allocations); |         $additional_allocations = array_map(intval(...), $additional_allocations); | ||||||
| 
 | 
 | ||||||
| @ -68,15 +68,17 @@ class TransferServerService | |||||||
|             $transfer->server_id = $server->id; |             $transfer->server_id = $server->id; | ||||||
|             $transfer->old_node = $server->node_id; |             $transfer->old_node = $server->node_id; | ||||||
|             $transfer->new_node = $node_id; |             $transfer->new_node = $node_id; | ||||||
|  |             if ($server->allocation_id) { | ||||||
|                 $transfer->old_allocation = $server->allocation_id; |                 $transfer->old_allocation = $server->allocation_id; | ||||||
|                 $transfer->new_allocation = $allocation_id; |                 $transfer->new_allocation = $allocation_id; | ||||||
|                 $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->all(); |                 $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->all(); | ||||||
|                 $transfer->new_additional_allocations = $additional_allocations; |                 $transfer->new_additional_allocations = $additional_allocations; | ||||||
| 
 | 
 | ||||||
|             $transfer->save(); |  | ||||||
| 
 |  | ||||||
|                 // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress.
 |                 // Add the allocations to the server, so they cannot be automatically assigned while the transfer is in progress.
 | ||||||
|                 $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); |                 $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $transfer->save(); | ||||||
| 
 | 
 | ||||||
|             // Generate a token for the destination node that the source node can use to authenticate with.
 |             // Generate a token for the destination node that the source node can use to authenticate with.
 | ||||||
|             $token = $this->nodeJWTService |             $token = $this->nodeJWTService | ||||||
|  | |||||||
| @ -0,0 +1,34 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('servers', function (Blueprint $table) { | ||||||
|  |             $table->dropForeign(['allocation_id']); | ||||||
|  |             $table->dropUnique(['allocation_id']); | ||||||
|  |             $table->unsignedInteger('allocation_id')->nullable()->change(); | ||||||
|  |             $table->foreign('allocation_id')->references('id')->on('allocations')->nullOnDelete(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         Schema::table('server_transfers', function (Blueprint $table) { | ||||||
|  |             $table->unsignedInteger('old_allocation')->nullable()->change(); | ||||||
|  |             $table->unsignedInteger('new_allocation')->nullable()->change(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         // Not needed
 | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @ -20,6 +20,8 @@ return [ | |||||||
|         'ip' => 'IP', |         'ip' => 'IP', | ||||||
|         'egg' => 'Egg', |         'egg' => 'Egg', | ||||||
|         'owner' => 'Owner', |         'owner' => 'Owner', | ||||||
|  |         'allocation_notes' => 'Notes', | ||||||
|  |         'no_notes' => 'No notes', | ||||||
|     ], |     ], | ||||||
|     'node_info' => 'Node Information', |     'node_info' => 'Node Information', | ||||||
|     'wings_version' => 'Wings Version', |     'wings_version' => 'Wings Version', | ||||||
| @ -109,4 +111,5 @@ return [ | |||||||
| 
 | 
 | ||||||
|     'error_connecting' => 'Error connecting to :node', |     'error_connecting' => 'Error connecting to :node', | ||||||
|     'error_connecting_description' => 'The configuration could not be automatically updated on Wings, you will need to manually update the configuration file.', |     'error_connecting_description' => 'The configuration could not be automatically updated on Wings, you will need to manually update the configuration file.', | ||||||
|  |     'allocation' => 'Allocation', | ||||||
| ]; | ]; | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ return [ | |||||||
|     'no' => 'No', |     'no' => 'No', | ||||||
|     'skip' => 'Skip', |     'skip' => 'Skip', | ||||||
|     'primary' => 'Primary', |     'primary' => 'Primary', | ||||||
|  |     'already_primary' => 'Already Primary', | ||||||
|     'make_primary' => 'Make Primary', |     'make_primary' => 'Make Primary', | ||||||
|     'startup_cmd' => 'Startup Command', |     'startup_cmd' => 'Startup Command', | ||||||
|     'default_startup' => 'Default Startup Command', |     'default_startup' => 'Default Startup Command', | ||||||
| @ -122,7 +123,6 @@ return [ | |||||||
|         'too_many_ports_body' => 'The current limit is :limit number of ports at one time.', |         'too_many_ports_body' => 'The current limit is :limit number of ports at one time.', | ||||||
|         'invalid_port' => 'Port not in valid range', |         'invalid_port' => 'Port not in valid range', | ||||||
|         'invalid_port_body' => ':i is not in the valid port range between :portFloor-:portCeil', |         'invalid_port_body' => ':i is not in the valid port range between :portFloor-:portCeil', | ||||||
|         'dissociate_primary' => 'Cannot dissociate primary allocation', |  | ||||||
|         'already_exists' => 'Port already in use', |         'already_exists' => 'Port already in use', | ||||||
|         'already_exists_body' => ':i is already with an allocation', |         'already_exists_body' => ':i is already with an allocation', | ||||||
|         'error_connecting' => 'Error connecting to :node', |         'error_connecting' => 'Error connecting to :node', | ||||||
| @ -133,4 +133,6 @@ return [ | |||||||
|         'reinstall_failed' => 'Could not start reinstall', |         'reinstall_failed' => 'Could not start reinstall', | ||||||
|         'log_failed' => 'Could not connect to Wings to retrieve server install log.', |         'log_failed' => 'Could not connect to Wings to retrieve server install log.', | ||||||
|     ], |     ], | ||||||
|  |     'notes' => 'Notes', | ||||||
|  |     'no_notes' => 'No Notes', | ||||||
| ]; | ]; | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ | |||||||
|                 <div class="hidden sm:block"> |                 <div class="hidden sm:block"> | ||||||
|                     <p class="text-sm dark:text-gray-400">Network</p> |                     <p class="text-sm dark:text-gray-400">Network</p> | ||||||
|                     <hr class="p-0.5"> |                     <hr class="p-0.5"> | ||||||
|                     <p class="text-md font-semibold">{{ $server->allocation->address }} </p> |                     <p class="text-md font-semibold">{{ $server->allocation?->address ?? 'None' }} </p> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -33,6 +33,23 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase | |||||||
|         $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null, 'notes' => null]); |         $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null, 'notes' => null]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Test that an allocation is deleted if it is currently marked as the primary allocation | ||||||
|  |      * for the server. | ||||||
|  |      */ | ||||||
|  |     public function test_primary_allocation_can_be_deleted_from_server(): void | ||||||
|  |     { | ||||||
|  |         /** @var \App\Models\Server $server */ | ||||||
|  |         [$user, $server] = $this->generateTestAccount(); | ||||||
|  |         $server->update(['allocation_limit' => 2]); | ||||||
|  | 
 | ||||||
|  |         $allocation = $server->allocation; | ||||||
|  | 
 | ||||||
|  |         $this->actingAs($user)->deleteJson($this->link($allocation))->assertStatus(Response::HTTP_NO_CONTENT); | ||||||
|  | 
 | ||||||
|  |         $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null, 'notes' => null]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Test that an error is returned if the user does not have permissiont to delete an allocation. |      * Test that an error is returned if the user does not have permissiont to delete an allocation. | ||||||
|      */ |      */ | ||||||
| @ -53,22 +70,6 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase | |||||||
|         $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => $server->id]); |         $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => $server->id]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Test that an allocation is not deleted if it is currently marked as the primary allocation |  | ||||||
|      * for the server. |  | ||||||
|      */ |  | ||||||
|     public function test_error_is_returned_if_allocation_is_primary(): void |  | ||||||
|     { |  | ||||||
|         /** @var \App\Models\Server $server */ |  | ||||||
|         [$user, $server] = $this->generateTestAccount(); |  | ||||||
|         $server->update(['allocation_limit' => 2]); |  | ||||||
| 
 |  | ||||||
|         $this->actingAs($user)->deleteJson($this->link($server->allocation)) |  | ||||||
|             ->assertStatus(Response::HTTP_BAD_REQUEST) |  | ||||||
|             ->assertJsonPath('errors.0.code', 'DisplayException') |  | ||||||
|             ->assertJsonPath('errors.0.detail', 'You cannot delete the primary allocation for this server.'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function test_allocation_cannot_be_deleted_if_server_limit_is_not_defined(): void |     public function test_allocation_cannot_be_deleted_if_server_limit_is_not_defined(): void | ||||||
|     { |     { | ||||||
|         [$user, $server] = $this->generateTestAccount(); |         [$user, $server] = $this->generateTestAccount(); | ||||||
|  | |||||||
| @ -62,7 +62,7 @@ class BuildModificationServiceTest extends IntegrationTestCase | |||||||
|         // Only one allocation should exist for this server now.
 |         // Only one allocation should exist for this server now.
 | ||||||
|         $this->assertCount(1, $response->allocations); |         $this->assertCount(1, $response->allocations); | ||||||
|         $this->assertSame($allocations[1]->id, $response->allocation_id); |         $this->assertSame($allocations[1]->id, $response->allocation_id); | ||||||
|         $this->assertNull($response->allocation->notes); |         $this->assertSame('Random notes', $response->allocation->notes); | ||||||
| 
 | 
 | ||||||
|         // These two allocations should not have been touched.
 |         // These two allocations should not have been touched.
 | ||||||
|         $this->assertDatabaseHas('allocations', ['id' => $allocations[2]->id, 'server_id' => $server2->id]); |         $this->assertDatabaseHas('allocations', ['id' => $allocations[2]->id, 'server_id' => $server2->id]); | ||||||
| @ -75,24 +75,36 @@ class BuildModificationServiceTest extends IntegrationTestCase | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Test that an exception is thrown if removing the default allocation without also assigning |      * Test that the primary allocation can be removed. | ||||||
|      * new allocations to the server. |  | ||||||
|      */ |      */ | ||||||
|     public function test_exception_is_thrown_if_removing_the_default_allocation(): void |     public function test_primary_allocation_can_be_removed(): void | ||||||
|     { |     { | ||||||
|         $server = $this->createServerModel(); |         $server = $this->createServerModel(); | ||||||
|         /** @var \App\Models\Allocation[] $allocations */ |         $server2 = $this->createServerModel(); | ||||||
|         $allocations = Allocation::factory()->times(4)->create(['node_id' => $server->node_id]); |  | ||||||
| 
 | 
 | ||||||
|         $allocations[0]->update(['server_id' => $server->id]); |         $server->allocation->update(['notes' => 'Random Notes']); | ||||||
|  |         $server2->allocation->update(['notes' => 'Random Notes']); | ||||||
| 
 | 
 | ||||||
|         $this->expectException(DisplayException::class); |         $initialAllocationId = $server->allocation->id; | ||||||
|         $this->expectExceptionMessage('You are attempting to delete the default allocation for this server but there is no fallback allocation to use.'); |  | ||||||
| 
 | 
 | ||||||
|         $this->getService()->handle($server, [ |         $this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined(); | ||||||
|             'add_allocations' => [], | 
 | ||||||
|             'remove_allocations' => [$server->allocation_id, $allocations[0]->id], |         $response = $this->getService()->handle($server, [ | ||||||
|  |             // Remove the default server allocation, ensuring that the new allocation passed through
 | ||||||
|  |             // in the data becomes the default allocation.
 | ||||||
|  |             'remove_allocations' => [$server->allocation->id, $server2->allocation->id], | ||||||
|         ]); |         ]); | ||||||
|  | 
 | ||||||
|  |         // No allocation should exist for this server now.
 | ||||||
|  |         $this->assertEmpty($response->allocations); | ||||||
|  |         $this->assertNull($response->allocation_id); | ||||||
|  | 
 | ||||||
|  |         // This allocation should not have been touched.
 | ||||||
|  |         $this->assertDatabaseHas('allocations', ['id' => $server2->allocation->id, 'server_id' => $server2->id, 'notes' => 'Random Notes']); | ||||||
|  | 
 | ||||||
|  |         // This allocation should have been removed from the server, and have had its
 | ||||||
|  |         // notes properly reset.
 | ||||||
|  |         $this->assertDatabaseHas('allocations', ['id' => $initialAllocationId, 'server_id' => null, 'notes' => null]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -129,16 +129,102 @@ class ServerCreationServiceTest extends IntegrationTestCase | |||||||
|             $this->assertSame($value, $response->{$key}, "Failed asserting equality of '$key' in server response. Got: [{$response->{$key}}] Expected: [$value]"); |             $this->assertSame($value, $response->{$key}, "Failed asserting equality of '$key' in server response. Got: [{$response->{$key}}] Expected: [$value]"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $this->assertFalse($response->isSuspended()); | ||||||
|  |         $this->assertFalse($response->oom_killer); | ||||||
|  |         $this->assertSame(0, $response->database_limit); | ||||||
|  |         $this->assertSame(0, $response->allocation_limit); | ||||||
|  |         $this->assertSame(0, $response->backup_limit); | ||||||
|  | 
 | ||||||
|         $this->assertCount(2, $response->allocations); |         $this->assertCount(2, $response->allocations); | ||||||
|         $this->assertSame($response->allocation_id, $response->allocations[0]->id); |         $this->assertSame($response->allocation_id, $response->allocations[0]->id); | ||||||
|         $this->assertSame($allocations[0]->id, $response->allocations[0]->id); |         $this->assertSame($allocations[0]->id, $response->allocations[0]->id); | ||||||
|         $this->assertSame($allocations[4]->id, $response->allocations[1]->id); |         $this->assertSame($allocations[4]->id, $response->allocations[1]->id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Test that a server without allocation can be created when a deployment object is | ||||||
|  |      * provided to the service. | ||||||
|  |      */ | ||||||
|  |     public function test_server_without_allocation_is_created_with_deployment_object(): void | ||||||
|  |     { | ||||||
|  |         /** @var \App\Models\User $user */ | ||||||
|  |         $user = User::factory()->create(); | ||||||
|  | 
 | ||||||
|  |         /** @var \App\Models\Node $node */ | ||||||
|  |         $node = Node::factory()->create(); | ||||||
|  | 
 | ||||||
|  |         $deployment = (new DeploymentObject())->setNode($node); | ||||||
|  | 
 | ||||||
|  |         $egg = $this->cloneEggAndVariables($this->bungeecord); | ||||||
|  |         // We want to make sure that the validator service runs as an admin, and not as a regular
 | ||||||
|  |         // user when saving variables.
 | ||||||
|  |         $egg->variables()->first()->update([ | ||||||
|  |             'user_editable' => false, | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         $data = [ | ||||||
|  |             'name' => $this->faker->name(), | ||||||
|  |             'description' => $this->faker->sentence(), | ||||||
|  |             'owner_id' => $user->id, | ||||||
|  |             'memory' => 256, | ||||||
|  |             'swap' => 128, | ||||||
|  |             'disk' => 100, | ||||||
|  |             'io' => 500, | ||||||
|  |             'cpu' => 0, | ||||||
|  |             'startup' => 'java server2.jar', | ||||||
|  |             'image' => 'java:8', | ||||||
|  |             'egg_id' => $egg->id, | ||||||
|  |             'allocation_additional' => [], | ||||||
|  |             'environment' => [ | ||||||
|  |                 'BUNGEE_VERSION' => '123', | ||||||
|  |                 'SERVER_JARFILE' => 'server2.jar', | ||||||
|  |             ], | ||||||
|  |             'start_on_completion' => true, | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         $this->daemonServerRepository->expects('setServer->create')->with(true)->andReturnUndefined(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             $this->getService()->handle(array_merge($data, [ | ||||||
|  |                 'environment' => [ | ||||||
|  |                     'BUNGEE_VERSION' => '', | ||||||
|  |                     'SERVER_JARFILE' => 'server2.jar', | ||||||
|  |                 ], | ||||||
|  |             ]), $deployment); | ||||||
|  | 
 | ||||||
|  |             $this->fail('This execution pathway should not be reached.'); | ||||||
|  |         } catch (ValidationException $exception) { | ||||||
|  |             $this->assertCount(1, $exception->errors()); | ||||||
|  |             $this->assertArrayHasKey('environment.BUNGEE_VERSION', $exception->errors()); | ||||||
|  |             $this->assertSame('The Bungeecord Version variable field is required.', $exception->errors()['environment.BUNGEE_VERSION'][0]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $response = $this->getService()->handle($data, $deployment); | ||||||
|  | 
 | ||||||
|  |         $this->assertInstanceOf(Server::class, $response); | ||||||
|  |         $this->assertNotNull($response->uuid); | ||||||
|  |         $this->assertSame($response->uuid_short, substr($response->uuid, 0, 8)); | ||||||
|  |         $this->assertSame($egg->id, $response->egg_id); | ||||||
|  |         $this->assertCount(2, $response->variables); | ||||||
|  |         $this->assertSame('123', $response->variables()->firstWhere('env_variable', 'BUNGEE_VERSION')->server_value); | ||||||
|  |         $this->assertSame('server2.jar', $response->variables()->firstWhere('env_variable', 'SERVER_JARFILE')->server_value); | ||||||
|  | 
 | ||||||
|  |         foreach ($data as $key => $value) { | ||||||
|  |             if (in_array($key, ['allocation_additional', 'environment', 'start_on_completion'])) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $this->assertSame($value, $response->{$key}, "Failed asserting equality of '$key' in server response. Got: [{$response->{$key}}] Expected: [$value]"); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         $this->assertFalse($response->isSuspended()); |         $this->assertFalse($response->isSuspended()); | ||||||
|         $this->assertFalse($response->oom_killer); |         $this->assertFalse($response->oom_killer); | ||||||
|         $this->assertSame(0, $response->database_limit); |         $this->assertSame(0, $response->database_limit); | ||||||
|         $this->assertSame(0, $response->allocation_limit); |         $this->assertSame(0, $response->allocation_limit); | ||||||
|         $this->assertSame(0, $response->backup_limit); |         $this->assertSame(0, $response->backup_limit); | ||||||
|  | 
 | ||||||
|  |         $this->assertEmpty($response->allocations); | ||||||
|  |         $this->assertNull($response->allocation_id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 JoanFo
						JoanFo