mirror of
https://github.com/pelican-dev/panel.git
synced 2025-06-28 21:01:07 +02:00
Merge branch 'main' into vehikl/singleton
This commit is contained in:
commit
6ef4e32960
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.
|
||||
|
7
app/Exceptions/Repository/FileExistsException.php
Normal file
7
app/Exceptions/Repository/FileExistsException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions\Repository;
|
||||
|
||||
use Exception;
|
||||
|
||||
class FileExistsException extends Exception {}
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
@ -58,6 +58,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)
|
||||
@ -81,8 +84,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'),
|
||||
|
@ -128,12 +128,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()
|
||||
@ -194,7 +194,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'));
|
||||
|
||||
@ -248,9 +248,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([
|
||||
@ -270,7 +268,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'))
|
||||
@ -833,7 +831,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);
|
||||
|
@ -1020,17 +1020,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')
|
||||
|
@ -12,8 +12,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\AssociateAction;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DissociateAction;
|
||||
@ -22,7 +20,6 @@ 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 +34,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 +43,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,17 +55,17 @@ 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')),
|
||||
])
|
||||
->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()
|
||||
->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 +83,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 +101,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')
|
||||
|
@ -74,7 +74,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')
|
||||
->icon('tabler-cpu')
|
||||
|
@ -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;
|
||||
@ -9,11 +10,16 @@ use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
@ -97,7 +103,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'))
|
||||
|
@ -8,12 +8,16 @@ use Closure;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
@ -97,6 +101,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'))
|
||||
|
@ -65,11 +65,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'),
|
||||
])
|
||||
@ -78,7 +75,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,
|
||||
@ -89,7 +85,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)
|
||||
|
@ -4,6 +4,7 @@ namespace App\Filament\Server\Resources\FileResource\Pages;
|
||||
|
||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||
use App\Enums\EditorLanguages;
|
||||
use App\Exceptions\Repository\FileExistsException;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\FileResource;
|
||||
use App\Models\File;
|
||||
@ -12,6 +13,7 @@ use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonFileRepository;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action as HeaderAction;
|
||||
@ -419,11 +421,22 @@ class ListFiles extends ListRecords
|
||||
->keyBindings('')
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function ($data) {
|
||||
$this->getDaemonFileRepository()->putContent(join_paths($this->path, $data['name']), $data['editor'] ?? '');
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
try {
|
||||
$this->getDaemonFileRepository()->putContent($path, $data['editor'] ?? '');
|
||||
|
||||
Activity::event('server:file.write')
|
||||
->property('file', join_paths($this->path, $data['name']))
|
||||
->property('file', join_paths($path, $data['name']))
|
||||
->log();
|
||||
} catch (FileExistsException) {
|
||||
AlertBanner::make()
|
||||
->title('<code>' . $path . '</code> already exists!')
|
||||
->danger()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
@ -448,11 +461,22 @@ class ListFiles extends ListRecords
|
||||
->label('New Folder')
|
||||
->color('gray')
|
||||
->action(function ($data) {
|
||||
try {
|
||||
$this->getDaemonFileRepository()->createDirectory($data['name'], $this->path);
|
||||
|
||||
Activity::event('server:file.create-directory')
|
||||
->property(['directory' => $this->path, 'name' => $data['name']])
|
||||
->log();
|
||||
} catch (FileExistsException) {
|
||||
$path = join_paths($this->path, $data['name']);
|
||||
AlertBanner::make()
|
||||
->title('<code>' . $path . '</code> already exists!')
|
||||
->danger()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
$this->redirect(self::getUrl(['path' => dirname($path)]));
|
||||
}
|
||||
})
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
|
@ -6,6 +6,7 @@ use App\Facades\Activity;
|
||||
use App\Models\Schedule;
|
||||
use App\Models\Task;
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
@ -45,10 +46,11 @@ class TasksRelationManager extends RelationManager
|
||||
Select::make('action')
|
||||
->required()
|
||||
->live()
|
||||
->disableOptionWhen(fn (string $value): bool => $value === Task::ACTION_BACKUP && $schedule->server->backup_limit === 0)
|
||||
->disableOptionWhen(fn (string $value) => $value === Task::ACTION_BACKUP && $schedule->server->backup_limit === 0)
|
||||
->options($this->getActionOptions())
|
||||
->selectablePlaceholder(false)
|
||||
->default(Task::ACTION_POWER),
|
||||
->default(Task::ACTION_POWER)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('payload', $state === Task::ACTION_POWER ? 'restart' : null)),
|
||||
Textarea::make('payload')
|
||||
->hidden(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label(fn (Get $get) => $this->getActionOptions(false)[$get('action')] ?? 'Payload'),
|
||||
@ -81,7 +83,8 @@ class TasksRelationManager extends RelationManager
|
||||
$schedule = $this->getOwnerRecord();
|
||||
|
||||
return $table
|
||||
->reorderable('sequence_id', true)
|
||||
->reorderable('sequence_id')
|
||||
->defaultSort('sequence_id')
|
||||
->columns([
|
||||
TextColumn::make('action')
|
||||
->state(fn (Task $task) => $this->getActionOptions()[$task->action] ?? $task->action),
|
||||
|
@ -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);
|
||||
|
@ -137,10 +137,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,
|
||||
|
@ -50,17 +50,18 @@ class ServerTransferController extends Controller
|
||||
throw new ConflictHttpException('Server is not being transferred.');
|
||||
}
|
||||
|
||||
$data = [];
|
||||
/** @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);
|
||||
|
||||
// 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['allocation_id'] = $transfer->new_allocation;
|
||||
}
|
||||
$data['node_id'] = $transfer->new_node;
|
||||
$server->update($data);
|
||||
|
||||
$server = $server->fresh();
|
||||
$server->transfer->update(['successful' => true]);
|
||||
@ -93,8 +94,10 @@ class ServerTransferController extends Controller
|
||||
$this->connection->transaction(function () use (&$transfer) {
|
||||
$transfer->forceFill(['successful' => false])->saveOrFail();
|
||||
|
||||
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>
|
||||
|
@ -72,20 +72,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;
|
||||
|
@ -46,7 +46,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
|
||||
@ -171,7 +171,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'],
|
||||
@ -220,10 +220,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();
|
||||
@ -272,6 +276,8 @@ class Server extends Model implements Validatable
|
||||
|
||||
/**
|
||||
* Gets all allocations associated with this server.
|
||||
*
|
||||
* @return HasMany<Allocation, $this>
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
|
@ -13,8 +13,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
|
||||
@ -45,8 +45,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'],
|
||||
|
@ -5,6 +5,7 @@ namespace App\Repositories\Daemon;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use App\Exceptions\Http\Server\FileSizeTooLargeException;
|
||||
use App\Exceptions\Repository\FileExistsException;
|
||||
use App\Exceptions\Repository\FileNotEditableException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
|
||||
@ -46,13 +47,20 @@ class DaemonFileRepository extends DaemonRepository
|
||||
* a file.
|
||||
*
|
||||
* @throws ConnectionException
|
||||
* @throws FileExistsException
|
||||
*/
|
||||
public function putContent(string $path, string $content): Response
|
||||
{
|
||||
return $this->getHttpClient()
|
||||
$response = $this->getHttpClient()
|
||||
->withQueryParameters(['file' => $path])
|
||||
->withBody($content)
|
||||
->post("/api/servers/{$this->server->uuid}/files/write");
|
||||
|
||||
if ($response->getStatusCode() === 400) {
|
||||
throw new FileExistsException();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,15 +81,22 @@ class DaemonFileRepository extends DaemonRepository
|
||||
* Creates a new directory for the server in the given $path.
|
||||
*
|
||||
* @throws ConnectionException
|
||||
* @throws FileExistsException
|
||||
*/
|
||||
public function createDirectory(string $name, string $path): Response
|
||||
{
|
||||
return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/create-directory",
|
||||
$response = $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/create-directory",
|
||||
[
|
||||
'name' => $name,
|
||||
'path' => $path,
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() === 400) {
|
||||
throw new FileExistsException();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +57,9 @@ abstract class DaemonRepository
|
||||
if (is_bool($condition)) {
|
||||
return $condition;
|
||||
}
|
||||
if ($condition->clientError()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$header = $condition->header('User-Agent');
|
||||
if (
|
||||
|
@ -107,6 +107,10 @@ class AssignmentService
|
||||
}
|
||||
}
|
||||
|
||||
if ($server && !$server->allocation_id) {
|
||||
$server->update(['allocation_id' => $ids[0]]);
|
||||
}
|
||||
|
||||
$this->connection->commit();
|
||||
|
||||
return $ids;
|
||||
|
@ -37,7 +37,9 @@ class FindAssignableAllocationService
|
||||
// server.
|
||||
/** @var \App\Models\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();
|
||||
|
@ -80,12 +80,10 @@ 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
|
||||
*
|
||||
* @throws \App\Exceptions\DisplayException
|
||||
*/
|
||||
private function processAllocations(Server $server, array &$data): void
|
||||
{
|
||||
@ -101,35 +99,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,
|
||||
|
@ -58,7 +58,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[], features: string[][]},
|
||||
* labels?: string[],
|
||||
@ -95,8 +95,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(),
|
||||
],
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Services\Servers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Exceptions\Service\Deployment\NoViableNodeException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -39,6 +40,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,
|
||||
@ -67,19 +69,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;
|
||||
}
|
||||
|
||||
if ($allocation) {
|
||||
$data['allocation_id'] = $allocation->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;
|
||||
$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)
|
||||
@ -95,7 +96,10 @@ class ServerCreationService
|
||||
// Create the server and assign any additional allocations to it.
|
||||
$server = $this->createModel($data);
|
||||
|
||||
if ($server->allocation_id) {
|
||||
$this->storeAssignedAllocations($server, $data);
|
||||
}
|
||||
|
||||
$this->storeEggVariables($server, $eggVariableData);
|
||||
|
||||
return $server;
|
||||
@ -119,20 +123,30 @@ class ServerCreationService
|
||||
*
|
||||
* @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\NoViableNodeException
|
||||
*/
|
||||
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 . '}}';
|
||||
|
@ -41,7 +41,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);
|
||||
|
||||
@ -68,15 +68,17 @@ class TransferServerService
|
||||
$transfer->server_id = $server->id;
|
||||
$transfer->old_node = $server->node_id;
|
||||
$transfer->new_node = $node_id;
|
||||
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;
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
$transfer->save();
|
||||
|
||||
// Generate a token for the destination node that the source node can use to authenticate with.
|
||||
$token = $this->nodeJWTService
|
||||
|
@ -17,7 +17,7 @@
|
||||
"doctrine/dbal": "~3.6.0",
|
||||
"filament/filament": "^3.3",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"laravel/framework": "^12.18",
|
||||
"laravel/framework": "^12.19",
|
||||
"laravel/helpers": "^1.7",
|
||||
"laravel/sanctum": "^4.1",
|
||||
"laravel/socialite": "^5.21",
|
||||
|
241
composer.lock
generated
241
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a006241b5687d547b51a60e6ac50ccae",
|
||||
"content-hash": "ee36fd7a30d4f56f0d3019267e62f834",
|
||||
"packages": [
|
||||
{
|
||||
"name": "abdelhamiderrahmouni/filament-monaco-editor",
|
||||
@ -1020,16 +1020,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.344.3",
|
||||
"version": "3.345.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "0cf789243c7de82be7d3f7641cab37b5bb5d937d"
|
||||
"reference": "61b4675bc02db8d7f3e1ba6931dc827c5ae23aa8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0cf789243c7de82be7d3f7641cab37b5bb5d937d",
|
||||
"reference": "0cf789243c7de82be7d3f7641cab37b5bb5d937d",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/61b4675bc02db8d7f3e1ba6931dc827c5ae23aa8",
|
||||
"reference": "61b4675bc02db8d7f3e1ba6931dc827c5ae23aa8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1111,9 +1111,9 @@
|
||||
"support": {
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.344.3"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.345.0"
|
||||
},
|
||||
"time": "2025-06-02T18:04:47+00:00"
|
||||
"time": "2025-06-17T18:09:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "blade-ui-kit/blade-heroicons",
|
||||
@ -1758,16 +1758,16 @@
|
||||
},
|
||||
{
|
||||
"name": "dedoc/scramble",
|
||||
"version": "v0.12.22",
|
||||
"version": "v0.12.23",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dedoc/scramble.git",
|
||||
"reference": "3c06a756d4fc20a281638e8ba9941f6463000d78"
|
||||
"reference": "5b650167c81c59138e844c2ae550c14dc1a249d0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dedoc/scramble/zipball/3c06a756d4fc20a281638e8ba9941f6463000d78",
|
||||
"reference": "3c06a756d4fc20a281638e8ba9941f6463000d78",
|
||||
"url": "https://api.github.com/repos/dedoc/scramble/zipball/5b650167c81c59138e844c2ae550c14dc1a249d0",
|
||||
"reference": "5b650167c81c59138e844c2ae550c14dc1a249d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1826,7 +1826,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dedoc/scramble/issues",
|
||||
"source": "https://github.com/dedoc/scramble/tree/v0.12.22"
|
||||
"source": "https://github.com/dedoc/scramble/tree/v0.12.23"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1834,7 +1834,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-03T07:50:53+00:00"
|
||||
"time": "2025-06-15T09:04:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
@ -2558,16 +2558,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/actions",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/actions.git",
|
||||
"reference": "151f776552ee10d70591c2649708bc4b0a7cba91"
|
||||
"reference": "67dd0da772f19e2d74e60eb53f99330faf183892"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/151f776552ee10d70591c2649708bc4b0a7cba91",
|
||||
"reference": "151f776552ee10d70591c2649708bc4b0a7cba91",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/67dd0da772f19e2d74e60eb53f99330faf183892",
|
||||
"reference": "67dd0da772f19e2d74e60eb53f99330faf183892",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2607,20 +2607,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-06-03T06:15:27+00:00"
|
||||
"time": "2025-06-13T14:47:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/filament",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/panels.git",
|
||||
"reference": "8d915ef313835f46f49175396de82feb0166d8a8"
|
||||
"reference": "b060d2d01a969e3b6541ab4f1e24c745352f51c1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/8d915ef313835f46f49175396de82feb0166d8a8",
|
||||
"reference": "8d915ef313835f46f49175396de82feb0166d8a8",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/b060d2d01a969e3b6541ab4f1e24c745352f51c1",
|
||||
"reference": "b060d2d01a969e3b6541ab4f1e24c745352f51c1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2672,20 +2672,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-06-10T16:10:42+00:00"
|
||||
"time": "2025-06-12T15:10:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/forms",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/forms.git",
|
||||
"reference": "014dd23a7691dc25bb037f26df852cfec5602d01"
|
||||
"reference": "586a13f9d2a6f395ffdc8557730c85a5858b4c4f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/014dd23a7691dc25bb037f26df852cfec5602d01",
|
||||
"reference": "014dd23a7691dc25bb037f26df852cfec5602d01",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/586a13f9d2a6f395ffdc8557730c85a5858b4c4f",
|
||||
"reference": "586a13f9d2a6f395ffdc8557730c85a5858b4c4f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2728,11 +2728,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-06-10T16:10:45+00:00"
|
||||
"time": "2025-06-12T15:10:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/infolists",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/infolists.git",
|
||||
@ -2783,7 +2783,7 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/notifications",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/notifications.git",
|
||||
@ -2835,16 +2835,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/support.git",
|
||||
"reference": "5c140580d7feeabb4d2b0007c854676ae87be1b3"
|
||||
"reference": "7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/5c140580d7feeabb4d2b0007c854676ae87be1b3",
|
||||
"reference": "5c140580d7feeabb4d2b0007c854676ae87be1b3",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe",
|
||||
"reference": "7d850347ffbd8c84d84040ffb8c5ceb0f709a9fe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2890,20 +2890,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-06-10T16:10:55+00:00"
|
||||
"time": "2025-06-12T15:02:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/tables",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/tables.git",
|
||||
"reference": "3d52c23443f6846774a6a2ce60f6e6173ce20943"
|
||||
"reference": "920204cd5ec1550209cf398fea8dba3dece979de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/3d52c23443f6846774a6a2ce60f6e6173ce20943",
|
||||
"reference": "3d52c23443f6846774a6a2ce60f6e6173ce20943",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/920204cd5ec1550209cf398fea8dba3dece979de",
|
||||
"reference": "920204cd5ec1550209cf398fea8dba3dece979de",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2942,20 +2942,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-06-10T16:10:40+00:00"
|
||||
"time": "2025-06-12T15:01:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/widgets",
|
||||
"version": "v3.3.21",
|
||||
"version": "v3.3.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/widgets.git",
|
||||
"reference": "048c5a4bf0477efbe2910c54a1aeb55c64cf1348"
|
||||
"reference": "5b956f884aaef479f6091463cb829e7c9f2afc2c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/048c5a4bf0477efbe2910c54a1aeb55c64cf1348",
|
||||
"reference": "048c5a4bf0477efbe2910c54a1aeb55c64cf1348",
|
||||
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/5b956f884aaef479f6091463cb829e7c9f2afc2c",
|
||||
"reference": "5b956f884aaef479f6091463cb829e7c9f2afc2c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2986,7 +2986,7 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2025-04-23T06:39:59+00:00"
|
||||
"time": "2025-06-12T15:11:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
@ -3718,16 +3718,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.18.0",
|
||||
"version": "v12.19.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d"
|
||||
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d",
|
||||
"reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
|
||||
"reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3929,7 +3929,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-06-10T14:48:34+00:00"
|
||||
"time": "2025-06-18T12:56:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/helpers",
|
||||
@ -5776,16 +5776,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.9.1",
|
||||
"version": "3.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||
"reference": "ced71f79398ece168e24f7f7710462f462310d4d"
|
||||
"reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d",
|
||||
"reference": "ced71f79398ece168e24f7f7710462f462310d4d",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
|
||||
"reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5793,9 +5793,9 @@
|
||||
"ext-json": "*",
|
||||
"php": "^8.1",
|
||||
"psr/clock": "^1.0",
|
||||
"symfony/clock": "^6.3 || ^7.0",
|
||||
"symfony/clock": "^6.3.12 || ^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.0",
|
||||
"symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0"
|
||||
"symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/clock-implementation": "1.0"
|
||||
@ -5803,14 +5803,13 @@
|
||||
"require-dev": {
|
||||
"doctrine/dbal": "^3.6.3 || ^4.0",
|
||||
"doctrine/orm": "^2.15.2 || ^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.57.2",
|
||||
"friendsofphp/php-cs-fixer": "^3.75.0",
|
||||
"kylekatarnls/multi-tester": "^2.5.3",
|
||||
"ondrejmirtes/better-reflection": "^6.25.0.4",
|
||||
"phpmd/phpmd": "^2.15.0",
|
||||
"phpstan/extension-installer": "^1.3.1",
|
||||
"phpstan/phpstan": "^1.11.2",
|
||||
"phpunit/phpunit": "^10.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.9.0"
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan": "^2.1.17",
|
||||
"phpunit/phpunit": "^10.5.46",
|
||||
"squizlabs/php_codesniffer": "^3.13.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/carbon"
|
||||
@ -5878,7 +5877,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-01T19:51:51+00:00"
|
||||
"time": "2025-06-12T10:24:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
@ -6707,16 +6706,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.43",
|
||||
"version": "3.0.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02"
|
||||
"reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02",
|
||||
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/1d0b5e7e1434678411787c5a0535e68907cf82d9",
|
||||
"reference": "1d0b5e7e1434678411787c5a0535e68907cf82d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -6797,7 +6796,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.43"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -6813,7 +6812,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-14T21:12:59+00:00"
|
||||
"time": "2025-06-15T09:59:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
@ -8461,16 +8460,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-data",
|
||||
"version": "4.15.1",
|
||||
"version": "4.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-data.git",
|
||||
"reference": "cb97afe6c0dadeb2e76ea1b7220cd04ed33dcca9"
|
||||
"reference": "50f5abe716ff1ad9a3e96dcfdeb4ad00f014bf8d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-data/zipball/cb97afe6c0dadeb2e76ea1b7220cd04ed33dcca9",
|
||||
"reference": "cb97afe6c0dadeb2e76ea1b7220cd04ed33dcca9",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-data/zipball/50f5abe716ff1ad9a3e96dcfdeb4ad00f014bf8d",
|
||||
"reference": "50f5abe716ff1ad9a3e96dcfdeb4ad00f014bf8d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8532,7 +8531,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-data/issues",
|
||||
"source": "https://github.com/spatie/laravel-data/tree/4.15.1"
|
||||
"source": "https://github.com/spatie/laravel-data/tree/4.15.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8540,7 +8539,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-10T06:06:27+00:00"
|
||||
"time": "2025-06-12T09:42:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-fractal",
|
||||
@ -8779,16 +8778,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-permission",
|
||||
"version": "6.19.0",
|
||||
"version": "6.20.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/laravel-permission.git",
|
||||
"reference": "0cd412dcad066d75caf0b977716809be7e7642fd"
|
||||
"reference": "31c05679102c73f3b0d05790d2400182745a5615"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/0cd412dcad066d75caf0b977716809be7e7642fd",
|
||||
"reference": "0cd412dcad066d75caf0b977716809be7e7642fd",
|
||||
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/31c05679102c73f3b0d05790d2400182745a5615",
|
||||
"reference": "31c05679102c73f3b0d05790d2400182745a5615",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8850,7 +8849,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/laravel-permission/issues",
|
||||
"source": "https://github.com/spatie/laravel-permission/tree/6.19.0"
|
||||
"source": "https://github.com/spatie/laravel-permission/tree/6.20.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8858,7 +8857,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-31T00:50:27+00:00"
|
||||
"time": "2025-06-05T07:33:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/laravel-query-builder",
|
||||
@ -12668,16 +12667,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.18.1",
|
||||
"version": "2.18.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filp/whoops.git",
|
||||
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26"
|
||||
"reference": "59a123a3d459c5a23055802237cb317f609867e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
|
||||
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5",
|
||||
"reference": "59a123a3d459c5a23055802237cb317f609867e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -12727,7 +12726,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/filp/whoops/issues",
|
||||
"source": "https://github.com/filp/whoops/tree/2.18.1"
|
||||
"source": "https://github.com/filp/whoops/tree/2.18.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -12735,7 +12734,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-03T18:56:14+00:00"
|
||||
"time": "2025-06-16T00:02:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest/hamcrest-php",
|
||||
@ -12891,16 +12890,16 @@
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.4.1",
|
||||
"version": "v3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "dc20d24871d5a2138b292b0430d94d18da3dbc53"
|
||||
"reference": "36706736a0c51d3337478fab9c919d78d2e03404"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/dc20d24871d5a2138b292b0430d94d18da3dbc53",
|
||||
"reference": "dc20d24871d5a2138b292b0430d94d18da3dbc53",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/36706736a0c51d3337478fab9c919d78d2e03404",
|
||||
"reference": "36706736a0c51d3337478fab9c919d78d2e03404",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -12968,7 +12967,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/larastan/larastan/issues",
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.4.1"
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -12976,7 +12975,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-09T21:23:36+00:00"
|
||||
"time": "2025-06-10T09:34:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pail",
|
||||
@ -13271,23 +13270,23 @@
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
"version": "v8.8.0",
|
||||
"version": "v8.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nunomaduro/collision.git",
|
||||
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8"
|
||||
"reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8",
|
||||
"reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
|
||||
"reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filp/whoops": "^2.18.0",
|
||||
"nunomaduro/termwind": "^2.3.0",
|
||||
"filp/whoops": "^2.18.1",
|
||||
"nunomaduro/termwind": "^2.3.1",
|
||||
"php": "^8.2.0",
|
||||
"symfony/console": "^7.2.5"
|
||||
"symfony/console": "^7.3.0"
|
||||
},
|
||||
"conflict": {
|
||||
"laravel/framework": "<11.44.2 || >=13.0.0",
|
||||
@ -13295,15 +13294,15 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.8.3",
|
||||
"larastan/larastan": "^3.2",
|
||||
"laravel/framework": "^11.44.2 || ^12.6",
|
||||
"laravel/pint": "^1.21.2",
|
||||
"laravel/sail": "^1.41.0",
|
||||
"laravel/sanctum": "^4.0.8",
|
||||
"larastan/larastan": "^3.4.2",
|
||||
"laravel/framework": "^11.44.2 || ^12.18",
|
||||
"laravel/pint": "^1.22.1",
|
||||
"laravel/sail": "^1.43.1",
|
||||
"laravel/sanctum": "^4.1.1",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"orchestra/testbench-core": "^9.12.0 || ^10.1",
|
||||
"pestphp/pest": "^3.8.0",
|
||||
"sebastian/environment": "^7.2.0 || ^8.0"
|
||||
"orchestra/testbench-core": "^9.12.0 || ^10.4",
|
||||
"pestphp/pest": "^3.8.2",
|
||||
"sebastian/environment": "^7.2.1 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@ -13366,7 +13365,7 @@
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-03T14:33:09+00:00"
|
||||
"time": "2025-06-11T01:04:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pestphp/pest",
|
||||
@ -14001,16 +14000,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "11.0.9",
|
||||
"version": "11.0.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7"
|
||||
"reference": "1a800a7446add2d79cc6b3c01c45381810367d76"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||
"reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76",
|
||||
"reference": "1a800a7446add2d79cc6b3c01c45381810367d76",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -14067,15 +14066,27 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://liberapay.com/sebastianbergmann",
|
||||
"type": "liberapay"
|
||||
},
|
||||
{
|
||||
"url": "https://thanks.dev/u/gh/sebastianbergmann",
|
||||
"type": "thanks_dev"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-25T13:26:39+00:00"
|
||||
"time": "2025-06-18T08:56:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
@ -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',
|
||||
];
|
||||
|
@ -5,7 +5,7 @@
|
||||
style="background-color: {{ $server->condition->getColor(true) }};">
|
||||
</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-t-lg overflow-hidden p-2">
|
||||
<div class="flex items-center mb-5 gap-2">
|
||||
<x-filament::icon-button
|
||||
:icon="$server->condition->getIcon()"
|
||||
@ -41,15 +41,17 @@
|
||||
<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>
|
||||
</a>
|
||||
|
||||
<div class="flex-1 dark:bg-gray-850 dark:text-white rounded-b-lg overflow-hidden p-1">
|
||||
<x-filament-tables::actions
|
||||
:actions="\App\Filament\App\Resources\ServerResource\Pages\ListServers::getPowerActions()"
|
||||
:alignment="\Filament\Support\Enums\Alignment::Center"
|
||||
:record="$server"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
@ -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