mirror of
https://github.com/pelican-dev/panel.git
synced 2025-06-30 17:51:08 +02:00
Merge remote-tracking branch 'origin/main' into filament-v4
This commit is contained in:
commit
4f0b6c888e
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
46
app/Console/Commands/Egg/UpdateEggIndexCommand.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdateEggIndexCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:egg:update-index';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
$data = file_get_contents('https://raw.githubusercontent.com/pelican-eggs/pelican-eggs.github.io/refs/heads/main/content/pelican.json');
|
||||
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$index = [];
|
||||
foreach ($data['nests'] as $nest) {
|
||||
$nestName = $nest['nest_type'];
|
||||
|
||||
$this->info("Nest: $nestName");
|
||||
|
||||
$nestEggs = [];
|
||||
foreach ($nest['Eggs'] as $egg) {
|
||||
$eggName = $egg['egg']['name'];
|
||||
|
||||
$this->comment("Egg: $eggName");
|
||||
|
||||
$nestEggs[$egg['download_url']] = $eggName;
|
||||
}
|
||||
$index[$nestName] = $nestEggs;
|
||||
|
||||
$this->info('');
|
||||
}
|
||||
|
||||
cache()->forever('eggs.index', $index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
@ -41,7 +42,9 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||
$schedule->command(PruneImagesCommand::class)->daily();
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
||||
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->daily();
|
||||
$schedule->command(UpdateEggIndexCommand::class)->daily();
|
||||
|
||||
if (config('backups.prune_age')) {
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||
|
@ -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')),
|
||||
SelectColumn::make('allocation.id')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address])
|
||||
->selectablePlaceholder(false)
|
||||
->disabled()
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->placeholder('None')
|
||||
->sortable(),
|
||||
]);
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ class AllocationsRelationManager extends RelationManager
|
||||
TextInputColumn::make('ip_alias')
|
||||
->searchable()
|
||||
->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')
|
||||
->options(fn (Allocation $allocation) => collect($this->getOwnerRecord()->ipAddresses())->merge([$allocation->ip])->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->selectablePlaceholder(false)
|
||||
@ -85,8 +88,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label(trans('admin/node.table.alias'))
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->helperText(trans('admin/node.alias_help'))
|
||||
->required(false),
|
||||
->helperText(trans('admin/node.alias_help')),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('27015, 27017-27019')
|
||||
->label(trans('admin/node.ports'))
|
||||
|
@ -43,8 +43,10 @@ class NodesRelationManager extends RelationManager
|
||||
->sortable(),
|
||||
SelectColumn::make('allocation.id')
|
||||
->label(trans('admin/node.primary_allocation'))
|
||||
->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address])
|
||||
->selectablePlaceholder(false)
|
||||
->disabled(fn (Server $server) => $server->allocations->count() <= 1)
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->selectablePlaceholder(fn (SelectColumn $select) => !$select->isDisabled())
|
||||
->placeholder('None')
|
||||
->sortable(),
|
||||
TextColumn::make('memory')->label(trans('admin/node.memory'))->icon('tabler-device-desktop-analytics'),
|
||||
TextColumn::make('cpu')->label(trans('admin/node.cpu'))->icon('tabler-cpu'),
|
||||
|
@ -132,12 +132,12 @@ class CreateServer extends CreateRecord
|
||||
->live()
|
||||
->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id')))
|
||||
->searchable()
|
||||
->required()
|
||||
->preload()
|
||||
->afterStateUpdated(function (Set $set, $state) {
|
||||
$set('allocation_id', null);
|
||||
$this->node = Node::find($state);
|
||||
})
|
||||
->required(),
|
||||
}),
|
||||
|
||||
Select::make('owner_id')
|
||||
->preload()
|
||||
@ -198,7 +198,7 @@ class CreateServer extends CreateRecord
|
||||
$set('allocation_additional', null);
|
||||
$set('allocation_additional.needstobeastringhere.extra_allocations', null);
|
||||
})
|
||||
->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address)
|
||||
->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address ?? '')
|
||||
->placeholder(function (Get $get) {
|
||||
$node = Node::find($get('node_id'));
|
||||
|
||||
@ -252,9 +252,7 @@ class CreateServer extends CreateRecord
|
||||
return collect(
|
||||
$assignmentService->handle(Node::find($get('node_id')), $data)
|
||||
)->first();
|
||||
})
|
||||
->required(),
|
||||
|
||||
}),
|
||||
Repeater::make('allocation_additional')
|
||||
->label(trans('admin/server.additional_allocations'))
|
||||
->columnSpan([
|
||||
@ -274,7 +272,7 @@ class CreateServer extends CreateRecord
|
||||
->prefixIcon('tabler-network')
|
||||
->label('Additional Allocations')
|
||||
->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'])
|
||||
->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address)
|
||||
->placeholder(trans('admin/server.select_additional'))
|
||||
@ -837,7 +835,9 @@ class CreateServer extends CreateRecord
|
||||
|
||||
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 {
|
||||
return $this->serverCreationService->handle($data);
|
||||
|
@ -1032,17 +1032,20 @@ class EditServer extends EditRecord
|
||||
->options(fn (Server $server) => Node::whereNot('id', $server->node->id)->pluck('name', 'id')->all()),
|
||||
Select::make('allocation_id')
|
||||
->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')
|
||||
->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]))
|
||||
->searchable(['ip', 'port', 'ip_alias'])
|
||||
->placeholder(trans('admin/server.select_allocation')),
|
||||
Select::make('allocation_additional')
|
||||
->label(trans('admin/server.additional_allocations'))
|
||||
->disabled(fn (Get $get, Server $server) => !$get('node_id') || $server->allocations->count() <= 1)
|
||||
->multiple()
|
||||
->minItems(fn (Select $select) => $select->getMaxItems())
|
||||
->maxItems(fn (Select $select, Server $server) => $select->isDisabled() ? null : $server->allocations->count() - 1)
|
||||
->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]))
|
||||
->searchable(['ip', 'port', 'ip_alias'])
|
||||
->placeholder(trans('admin/server.select_additional')),
|
||||
|
@ -73,14 +73,17 @@ class ListServers extends ListRecords
|
||||
->searchable(),
|
||||
SelectColumn::make('allocation_id')
|
||||
->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]))
|
||||
->selectablePlaceholder(false)
|
||||
->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1)
|
||||
->placeholder('None')
|
||||
->sortable(),
|
||||
TextColumn::make('allocation_id_readonly')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->hidden(auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
|
||||
->state(fn (Server $server) => $server->allocation->address),
|
||||
->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)
|
||||
->state(fn (Server $server) => $server->allocation->address ?? 'None'),
|
||||
TextColumn::make('image')->hidden(),
|
||||
TextColumn::make('backups_count')
|
||||
->counts('backups')
|
||||
|
@ -13,16 +13,15 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\AssociateAction;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DissociateBulkAction;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\AssociateAction;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* @method Server getOwnerRecord()
|
||||
@ -37,7 +36,6 @@ class AllocationsRelationManager extends RelationManager
|
||||
->selectCurrentPageOnly()
|
||||
->recordTitleAttribute('address')
|
||||
->recordTitle(fn (Allocation $allocation) => $allocation->address)
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id)
|
||||
->inverseRelationship('server')
|
||||
->heading(trans('admin/server.allocations'))
|
||||
->columns([
|
||||
@ -47,6 +45,9 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label(trans('admin/server.port')),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->label(trans('admin/server.alias')),
|
||||
TextInputColumn::make('notes')
|
||||
->label(trans('admin/server.notes'))
|
||||
->placeholder(trans('admin/server.no_notes')),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
@ -56,6 +57,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
true => 'warning',
|
||||
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())
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id)
|
||||
->label(trans('admin/server.primary')),
|
||||
@ -66,7 +68,10 @@ class AllocationsRelationManager extends RelationManager
|
||||
->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()
|
||||
->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([
|
||||
CreateAction::make()->label(trans('admin/server.create_allocation'))
|
||||
@ -84,8 +89,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label(trans('admin/server.alias'))
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->helperText(trans('admin/server.alias_helper'))
|
||||
->required(false),
|
||||
->helperText(trans('admin/server.alias_helper')),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('27015, 27017-27019')
|
||||
->label(trans('admin/server.ports'))
|
||||
@ -103,22 +107,14 @@ class AllocationsRelationManager extends RelationManager
|
||||
->preloadRecordSelect()
|
||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
||||
->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([
|
||||
DissociateBulkAction::make()
|
||||
->before(function (DissociateBulkAction $action, Collection $records) {
|
||||
$records = $records->filter(function ($allocation) {
|
||||
/** @var Allocation $allocation */
|
||||
return $allocation->id !== $this->getOwnerRecord()->allocation_id;
|
||||
});
|
||||
|
||||
if ($records->isEmpty()) {
|
||||
$action->failureNotificationTitle(trans('admin/server.notifications.dissociate_primary'))->failure();
|
||||
throw new Halt();
|
||||
}
|
||||
|
||||
return $records;
|
||||
->after(function () {
|
||||
Allocation::whereNull('server_id')->update(['notes' => null]);
|
||||
$this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
@ -70,8 +70,9 @@ class ServersRelationManager extends RelationManager
|
||||
->sortable(),
|
||||
SelectColumn::make('allocation.id')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->options(fn (Server $server) => [$server->allocation->id => $server->allocation->address])
|
||||
->selectablePlaceholder(false)
|
||||
->disabled()
|
||||
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->placeholder('None')
|
||||
->sortable(),
|
||||
TextColumn::make('image')->hidden(),
|
||||
TextColumn::make('databases_count')
|
||||
|
@ -84,7 +84,8 @@ class ListServers extends ListRecords
|
||||
->label('')
|
||||
->badge()
|
||||
->visibleFrom('md')
|
||||
->copyable(request()->isSecure()),
|
||||
->copyable(request()->isSecure())
|
||||
->state(fn (Server $server) => $server->allocation->address ?? 'None'),
|
||||
TextColumn::make('cpuUsage')
|
||||
->label('Resources')
|
||||
->size(TextSize::Medium)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Closure;
|
||||
@ -10,10 +11,14 @@ use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
@ -97,7 +102,28 @@ class ImportEggAction extends Action
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
Select::make('github')
|
||||
->label(trans('admin/egg.import.github'))
|
||||
->options(cache('eggs.index'))
|
||||
->selectablePlaceholder(false)
|
||||
->searchable()
|
||||
->preload()
|
||||
->live()
|
||||
->hintIcon('tabler-refresh')
|
||||
->hintIconTooltip(trans('admin/egg.import.refresh'))
|
||||
->hintAction(function () {
|
||||
Artisan::call(UpdateEggIndexCommand::class);
|
||||
})
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) use ($isMultiple) {
|
||||
if ($state) {
|
||||
$urls = $isMultiple ? $get('urls') : [];
|
||||
$urls[Str::uuid()->toString()] = ['url' => $state];
|
||||
$set('urls', $urls);
|
||||
$set('github', null);
|
||||
}
|
||||
}),
|
||||
Repeater::make('urls')
|
||||
->label('')
|
||||
->itemLabel(fn (array $state) => str($state['url'])->afterLast('/egg-')->before('.json')->headline())
|
||||
->hint(trans('admin/egg.import.url_help'))
|
||||
->addActionLabel(trans('admin/egg.import.add_url'))
|
||||
|
@ -11,9 +11,12 @@ use Filament\Forms\Components\Repeater;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
@ -97,6 +100,21 @@ class ImportEggAction extends Action
|
||||
Tab::make(trans('admin/egg.import.url'))
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
Select::make('github')
|
||||
->label(trans('admin/egg.import.github'))
|
||||
->options(cache('eggs.index'))
|
||||
->selectablePlaceholder(false)
|
||||
->searchable()
|
||||
->preload()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) use ($isMultiple) {
|
||||
if ($state) {
|
||||
$urls = $isMultiple ? $get('urls') : [];
|
||||
$urls[Str::uuid()->toString()] = ['url' => $state];
|
||||
$set('urls', $urls);
|
||||
$set('github', null);
|
||||
}
|
||||
}),
|
||||
Repeater::make('urls')
|
||||
->itemLabel(fn (array $state) => str($state['url'])->afterLast('/egg-')->before('.json')->headline())
|
||||
->hint(trans('admin/egg.import.url_help'))
|
||||
|
@ -69,11 +69,8 @@ class AllocationResource extends Resource
|
||||
true => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->action(function (Allocation $allocation) use ($server) {
|
||||
if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) {
|
||||
return $server->update(['allocation_id' => $allocation->id]);
|
||||
}
|
||||
})
|
||||
->tooltip(fn (Allocation $allocation) => ($allocation->id === $server->allocation_id ? 'Already' : 'Make') . ' Primary')
|
||||
->action(fn (Allocation $allocation) => auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server) && $server->update(['allocation_id' => $allocation->id]))
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->label('Primary'),
|
||||
])
|
||||
@ -82,7 +79,6 @@ class AllocationResource extends Resource
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server))
|
||||
->label('Delete')
|
||||
->icon('tabler-trash')
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->action(function (Allocation $allocation) {
|
||||
Allocation::query()->where('id', $allocation->id)->update([
|
||||
'notes' => null,
|
||||
@ -93,7 +89,8 @@ class AllocationResource extends Resource
|
||||
->subject($allocation)
|
||||
->property('allocation', $allocation->address)
|
||||
->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) {
|
||||
$allocation = $service->handle($server);
|
||||
|
||||
if (!$server->allocation_id) {
|
||||
$server->update(['allocation_id' => $allocation->id]);
|
||||
}
|
||||
|
||||
Activity::event('server:allocation.create')
|
||||
->subject($allocation)
|
||||
->property('allocation', $allocation->address)
|
||||
|
@ -23,7 +23,7 @@ class ServerOverview extends StatsOverviewWidget
|
||||
SmallStatBlock::make('Name', $this->server->name)
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('Status', $this->status()),
|
||||
SmallStatBlock::make('Address', $this->server->allocation->address)
|
||||
SmallStatBlock::make('Address', $this->server?->allocation->address ?? 'None')
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('CPU', $this->cpuUsage()),
|
||||
SmallStatBlock::make('Memory', $this->memoryUsage()),
|
||||
@ -68,7 +68,7 @@ class ServerOverview extends StatsOverviewWidget
|
||||
}
|
||||
|
||||
$latestMemoryUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))->last(default: 0);
|
||||
$totalMemory = collect(cache()->get("servers.{$this->server->id}.memory_limit_bytes"))->last(default: 0);
|
||||
$totalMemory = $this->server->memory * 2 ** 20;
|
||||
|
||||
$used = convert_bytes_to_readable($latestMemoryUsed);
|
||||
$total = convert_bytes_to_readable($totalMemory);
|
||||
|
@ -138,10 +138,6 @@ class NetworkAllocationController extends ClientApiController
|
||||
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([
|
||||
'notes' => null,
|
||||
'server_id' => null,
|
||||
|
@ -51,17 +51,18 @@ class ServerTransferController extends Controller
|
||||
throw new ConflictHttpException('Server is not being transferred.');
|
||||
}
|
||||
|
||||
/** @var Server $server */
|
||||
$server = $this->connection->transaction(function () use ($server, $transfer) {
|
||||
$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
|
||||
// primary allocation and node.
|
||||
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
|
||||
$server->update([
|
||||
'allocation_id' => $transfer->new_allocation,
|
||||
'node_id' => $transfer->new_node,
|
||||
]);
|
||||
$data = [];
|
||||
/** @var \App\Models\Server $server */
|
||||
$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);
|
||||
// Remove the old allocations for the server and re-assign the server to the new
|
||||
// primary allocation and node.
|
||||
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
|
||||
$data['allocation_id'] = $transfer->new_allocation;
|
||||
}
|
||||
$data['node_id'] = $transfer->new_node;
|
||||
$server->update($data);
|
||||
|
||||
$server = $server->fresh();
|
||||
$server->transfer->update(['successful' => true]);
|
||||
@ -94,8 +95,10 @@ class ServerTransferController extends Controller
|
||||
$this->connection->transaction(function () use (&$transfer) {
|
||||
$transfer->forceFill(['successful' => false])->saveOrFail();
|
||||
|
||||
$allocations = array_merge([$transfer->new_allocation], $transfer->new_additional_allocations);
|
||||
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
|
||||
if ($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]);
|
||||
}
|
||||
});
|
||||
|
||||
return new JsonResponse([], Response::HTTP_NO_CONTENT);
|
||||
|
@ -176,6 +176,7 @@ class StoreServerRequest extends ApplicationApiRequest
|
||||
$object->setDedicated($this->input('deploy.dedicated_ip', false));
|
||||
$object->setTags($this->input('deploy.tags', $this->input('deploy.locations', [])));
|
||||
$object->setPorts($this->input('deploy.port_range', []));
|
||||
$object->setNode($this->input('deploy.node_id'));
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class ServerEntry extends Component
|
||||
style="background-color: #D97706;">
|
||||
</div>
|
||||
|
||||
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
|
||||
<div class="flex-1 dark:bg-gray-850 dark:text-white rounded-lg overflow-hidden p-2">
|
||||
<div class="flex items-center mb-5 gap-2">
|
||||
<x-filament::loading-indicator class="h-5 w-5" />
|
||||
<h2 class="text-xl font-bold">
|
||||
@ -54,7 +54,7 @@ class ServerEntry extends Component
|
||||
<div class="hidden sm:block">
|
||||
<p class="text-sm dark:text-gray-400">Network</p>
|
||||
<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>
|
||||
|
@ -75,20 +75,6 @@ class Allocation extends Model
|
||||
static::deleting(function (self $allocation) {
|
||||
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
|
||||
|
@ -2,8 +2,12 @@
|
||||
|
||||
namespace App\Models\Objects;
|
||||
|
||||
use App\Models\Node;
|
||||
|
||||
class DeploymentObject
|
||||
{
|
||||
private ?Node $node = null;
|
||||
|
||||
private bool $dedicated = false;
|
||||
|
||||
/** @var string[] */
|
||||
@ -12,6 +16,18 @@ class DeploymentObject
|
||||
/** @var array<int|string> */
|
||||
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
|
||||
{
|
||||
return $this->dedicated;
|
||||
|
@ -52,7 +52,7 @@ use App\Services\Subusers\SubuserDeletionService;
|
||||
* @property int $cpu
|
||||
* @property string|null $threads
|
||||
* @property bool $oom_killer
|
||||
* @property int $allocation_id
|
||||
* @property int|null $allocation_id
|
||||
* @property int $egg_id
|
||||
* @property string $startup
|
||||
* @property string $image
|
||||
@ -177,7 +177,7 @@ class Server extends Model implements Validatable
|
||||
'threads' => ['nullable', 'regex:/^[0-9-,]+$/'],
|
||||
'oom_killer' => ['sometimes', 'boolean'],
|
||||
'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'],
|
||||
'startup' => ['required', 'string'],
|
||||
'skip_scripts' => ['sometimes', 'boolean'],
|
||||
@ -226,10 +226,14 @@ class Server extends Model implements Validatable
|
||||
/**
|
||||
* Returns the format for server allocations when communicating with the Daemon.
|
||||
*
|
||||
* @return array<int>
|
||||
* @return array<string, array<int>>
|
||||
*/
|
||||
public function getAllocationMappings(): array
|
||||
{
|
||||
if (!$this->allocation) {
|
||||
return ['' => []];
|
||||
}
|
||||
|
||||
return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) {
|
||||
return $item->pluck('port');
|
||||
})->toArray();
|
||||
@ -278,6 +282,8 @@ class Server extends Model implements Validatable
|
||||
|
||||
/**
|
||||
* Gets all allocations associated with this server.
|
||||
*
|
||||
* @return HasMany<Allocation, $this>
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
|
@ -14,8 +14,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @property int $server_id
|
||||
* @property int $old_node
|
||||
* @property int $new_node
|
||||
* @property int $old_allocation
|
||||
* @property int $new_allocation
|
||||
* @property int|null $old_allocation
|
||||
* @property int|null $new_allocation
|
||||
* @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 bool|null $successful
|
||||
@ -46,8 +46,8 @@ class ServerTransfer extends Model implements Validatable
|
||||
'server_id' => ['required', 'numeric', 'exists:servers,id'],
|
||||
'old_node' => ['required', 'numeric'],
|
||||
'new_node' => ['required', 'numeric'],
|
||||
'old_allocation' => ['required', 'numeric'],
|
||||
'new_allocation' => ['required', 'numeric'],
|
||||
'old_allocation' => ['nullable', 'numeric'],
|
||||
'new_allocation' => ['nullable', 'numeric'],
|
||||
'old_additional_allocations' => ['nullable', 'array'],
|
||||
'old_additional_allocations.*' => ['numeric'],
|
||||
'new_additional_allocations' => ['nullable', 'array'],
|
||||
|
@ -108,6 +108,10 @@ class AssignmentService
|
||||
}
|
||||
}
|
||||
|
||||
if ($server && !$server->allocation_id) {
|
||||
$server->update(['allocation_id' => $ids[0]]);
|
||||
}
|
||||
|
||||
$this->connection->commit();
|
||||
|
||||
return $ids;
|
||||
|
@ -42,7 +42,9 @@ class FindAssignableAllocationService
|
||||
// server.
|
||||
/** @var Allocation|null $allocation */
|
||||
$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')
|
||||
->inRandomOrder()
|
||||
->first();
|
||||
|
@ -81,7 +81,7 @@ class BuildModificationService
|
||||
* @param array{
|
||||
* add_allocations?: array<int>,
|
||||
* remove_allocations?: array<int>,
|
||||
* allocation_id?: int,
|
||||
* allocation_id: ?int,
|
||||
* oom_killer?: bool,
|
||||
* oom_disabled?: bool,
|
||||
* } $data
|
||||
@ -102,35 +102,26 @@ class BuildModificationService
|
||||
->whereIn('id', $data['add_allocations'])
|
||||
->whereNull('server_id');
|
||||
|
||||
// Keep track of all the allocations we're just now adding so that we can use the first
|
||||
// one to reset the default allocation to.
|
||||
$freshlyAllocated = $query->first()?->id;
|
||||
|
||||
$query->update(['server_id' => $server->id, 'notes' => null]);
|
||||
$query->update(['server_id' => $server->id]);
|
||||
}
|
||||
|
||||
if (!empty($data['remove_allocations'])) {
|
||||
foreach ($data['remove_allocations'] as $allocation) {
|
||||
// If we are attempting to remove the default allocation for the server, see if we can reassign
|
||||
// to the first provided value in add_allocations. If there is no new first allocation then we
|
||||
// will throw an exception back.
|
||||
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.');
|
||||
}
|
||||
$allocations = Allocation::query()
|
||||
->where('server_id', $server->id)
|
||||
// Only use the allocations that we didn't also attempt to add to the server...
|
||||
->whereIn('id', array_diff($data['remove_allocations'], $data['add_allocations'] ?? []));
|
||||
|
||||
// Update the default allocation to be the first allocation that we are creating.
|
||||
$data['allocation_id'] = $freshlyAllocated;
|
||||
}
|
||||
// If we are attempting to remove the default allocation for the server, see if we can reassign
|
||||
// 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
|
||||
// this node. Also set the notes to null, otherwise when re-allocated to a new server those
|
||||
// notes will be carried over.
|
||||
Allocation::query()->where('node_id', $server->node_id)
|
||||
->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'] ?? []))
|
||||
$allocations
|
||||
->update([
|
||||
'notes' => null,
|
||||
'server_id' => null,
|
||||
|
@ -56,7 +56,7 @@ class ServerConfigurationStructureService
|
||||
* allocations: array{
|
||||
* force_outgoing_ip: bool,
|
||||
* default: array{ip: string, port: int},
|
||||
* mappings: array<int>,
|
||||
* mappings: array<string, array<int>>,
|
||||
* },
|
||||
* egg: array{id: string, file_denylist: string[]},
|
||||
* labels?: string[],
|
||||
@ -93,8 +93,8 @@ class ServerConfigurationStructureService
|
||||
'allocations' => [
|
||||
'force_outgoing_ip' => $server->egg->force_outgoing_ip,
|
||||
'default' => [
|
||||
'ip' => $server->allocation->ip,
|
||||
'port' => $server->allocation->port,
|
||||
'ip' => $server->allocation->ip ?? '127.0.0.1',
|
||||
'port' => $server->allocation->port ?? 0,
|
||||
],
|
||||
'mappings' => $server->getAllocationMappings(),
|
||||
],
|
||||
|
@ -8,6 +8,7 @@ use Illuminate\Validation\ValidationException;
|
||||
use App\Exceptions\Service\Deployment\NoViableAllocationException;
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Service\Deployment\NoViableNodeException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -44,6 +45,7 @@ class ServerCreationService
|
||||
* no node_id the node_is will be picked from the allocation.
|
||||
*
|
||||
* @param array{
|
||||
* node_id?: int,
|
||||
* oom_killer?: bool,
|
||||
* oom_disabled?: bool,
|
||||
* egg_id?: int,
|
||||
@ -72,19 +74,18 @@ class ServerCreationService
|
||||
|
||||
// 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.
|
||||
if ($deployment instanceof DeploymentObject) {
|
||||
if ($deployment) {
|
||||
$allocation = $this->configureDeployment($data, $deployment);
|
||||
$data['allocation_id'] = $allocation->id;
|
||||
$data['node_id'] = $allocation->node_id;
|
||||
}
|
||||
|
||||
// Auto-configure the node based on the selected allocation
|
||||
// if no node was defined.
|
||||
if (empty($data['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;
|
||||
if ($allocation) {
|
||||
$data['allocation_id'] = $allocation->id;
|
||||
// Auto-configure the node based on the selected allocation
|
||||
// if no node was defined.
|
||||
$data['node_id'] = $allocation->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
|
||||
->setUserLevel(User::USER_LEVEL_ADMIN)
|
||||
@ -100,7 +101,10 @@ class ServerCreationService
|
||||
// Create the server and assign any additional allocations to it.
|
||||
$server = $this->createModel($data);
|
||||
|
||||
$this->storeAssignedAllocations($server, $data);
|
||||
if ($server->allocation_id) {
|
||||
$this->storeAssignedAllocations($server, $data);
|
||||
}
|
||||
|
||||
$this->storeEggVariables($server, $eggVariableData);
|
||||
|
||||
return $server;
|
||||
@ -127,17 +131,27 @@ class ServerCreationService
|
||||
* @throws DisplayException
|
||||
* @throws NoViableAllocationException
|
||||
*/
|
||||
private function configureDeployment(array $data, DeploymentObject $deployment): Allocation
|
||||
private function configureDeployment(array $data, DeploymentObject $deployment): ?Allocation
|
||||
{
|
||||
$nodes = $this->findViableNodesService->handle(
|
||||
Arr::get($data, 'memory', 0),
|
||||
Arr::get($data, 'disk', 0),
|
||||
Arr::get($data, 'cpu', 0),
|
||||
Arr::get($data, 'tags', []),
|
||||
$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())
|
||||
->setNodes($nodes->pluck('id')->toArray())
|
||||
->setNodes($availableNodes->toArray())
|
||||
->setPorts($deployment->getPorts())
|
||||
->handle();
|
||||
}
|
||||
|
@ -12,7 +12,11 @@ class StartupCommandService
|
||||
public function handle(Server $server, bool $hideAllValues = false): string
|
||||
{
|
||||
$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) {
|
||||
$find[] = '{{' . $variable->env_variable . '}}';
|
||||
|
@ -42,7 +42,7 @@ class TransferServerService
|
||||
*
|
||||
* @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);
|
||||
|
||||
@ -69,16 +69,18 @@ class TransferServerService
|
||||
$transfer->server_id = $server->id;
|
||||
$transfer->old_node = $server->node_id;
|
||||
$transfer->new_node = $node_id;
|
||||
$transfer->old_allocation = $server->allocation_id;
|
||||
$transfer->new_allocation = $allocation_id;
|
||||
$transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->all();
|
||||
$transfer->new_additional_allocations = $additional_allocations;
|
||||
if ($server->allocation_id) {
|
||||
$transfer->old_allocation = $server->allocation_id;
|
||||
$transfer->new_allocation = $allocation_id;
|
||||
$transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->all();
|
||||
$transfer->new_additional_allocations = $additional_allocations;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
$transfer->save();
|
||||
|
||||
// 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);
|
||||
|
||||
// Generate a token for the destination node that the source node can use to authenticate with.
|
||||
$token = $this->nodeJWTService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
|
@ -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
|
||||
}
|
||||
};
|
@ -18,6 +18,8 @@ return [
|
||||
'add_url' => 'New URL',
|
||||
'import_failed' => 'Import Failed',
|
||||
'import_success' => 'Import Success',
|
||||
'github' => 'Add from Github',
|
||||
'refresh' => 'Refresh',
|
||||
],
|
||||
'in_use' => 'In Use',
|
||||
'servers' => 'Servers',
|
||||
|
@ -20,6 +20,8 @@ return [
|
||||
'ip' => 'IP',
|
||||
'egg' => 'Egg',
|
||||
'owner' => 'Owner',
|
||||
'allocation_notes' => 'Notes',
|
||||
'no_notes' => 'No notes',
|
||||
],
|
||||
'node_info' => 'Node Information',
|
||||
'wings_version' => 'Wings Version',
|
||||
@ -109,4 +111,5 @@ return [
|
||||
|
||||
'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.',
|
||||
'allocation' => 'Allocation',
|
||||
];
|
||||
|
@ -22,6 +22,7 @@ return [
|
||||
'no' => 'No',
|
||||
'skip' => 'Skip',
|
||||
'primary' => 'Primary',
|
||||
'already_primary' => 'Already Primary',
|
||||
'make_primary' => 'Make Primary',
|
||||
'startup_cmd' => '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.',
|
||||
'invalid_port' => 'Port not in valid range',
|
||||
'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_body' => ':i is already with an allocation',
|
||||
'error_connecting' => 'Error connecting to :node',
|
||||
@ -133,4 +133,6 @@ return [
|
||||
'reinstall_failed' => 'Could not start reinstall',
|
||||
'log_failed' => 'Could not connect to Wings to retrieve server install log.',
|
||||
],
|
||||
'notes' => 'Notes',
|
||||
'no_notes' => 'No Notes',
|
||||
];
|
||||
|
@ -33,6 +33,23 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase
|
||||
$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.
|
||||
*/
|
||||
@ -53,22 +70,6 @@ class DeleteAllocationTest extends ClientApiIntegrationTestCase
|
||||
$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
|
||||
{
|
||||
[$user, $server] = $this->generateTestAccount();
|
||||
|
@ -62,7 +62,7 @@ class BuildModificationServiceTest extends IntegrationTestCase
|
||||
// Only one allocation should exist for this server now.
|
||||
$this->assertCount(1, $response->allocations);
|
||||
$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.
|
||||
$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
|
||||
* new allocations to the server.
|
||||
* Test that the primary allocation can be removed.
|
||||
*/
|
||||
public function test_exception_is_thrown_if_removing_the_default_allocation(): void
|
||||
public function test_primary_allocation_can_be_removed(): void
|
||||
{
|
||||
$server = $this->createServerModel();
|
||||
/** @var \App\Models\Allocation[] $allocations */
|
||||
$allocations = Allocation::factory()->times(4)->create(['node_id' => $server->node_id]);
|
||||
$server2 = $this->createServerModel();
|
||||
|
||||
$allocations[0]->update(['server_id' => $server->id]);
|
||||
$server->allocation->update(['notes' => 'Random Notes']);
|
||||
$server2->allocation->update(['notes' => 'Random Notes']);
|
||||
|
||||
$this->expectException(DisplayException::class);
|
||||
$this->expectExceptionMessage('You are attempting to delete the default allocation for this server but there is no fallback allocation to use.');
|
||||
$initialAllocationId = $server->allocation->id;
|
||||
|
||||
$this->getService()->handle($server, [
|
||||
'add_allocations' => [],
|
||||
'remove_allocations' => [$server->allocation_id, $allocations[0]->id],
|
||||
$this->daemonServerRepository->expects('setServer->sync')->andReturnUndefined();
|
||||
|
||||
$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->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->assertSame($response->allocation_id, $response->allocations[0]->id);
|
||||
$this->assertSame($allocations[0]->id, $response->allocations[0]->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->oom_killer);
|
||||
$this->assertSame(0, $response->database_limit);
|
||||
$this->assertSame(0, $response->allocation_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