Files
panel-pelican-dev/app/Filament/Admin/Resources/Servers/Pages/EditServer.php

1006 lines
62 KiB
PHP
Raw Normal View History

2024-03-24 01:48:03 -04:00
<?php
namespace App\Filament\Admin\Resources\Servers\Pages;
2024-03-24 01:48:03 -04:00
use App\Enums\SuspendAction;
use App\Filament\Admin\Resources\Servers\RelationManagers\AllocationsRelationManager;
use App\Filament\Admin\Resources\Servers\RelationManagers\DatabasesRelationManager;
use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Components\Actions\PreviewStartupAction;
use App\Filament\Components\Forms\Fields\StartupVariable;
use App\Filament\Components\StateCasts\ServerConditionStateCast;
use App\Filament\Server\Pages\Console;
use App\Models\Allocation;
use App\Models\Egg;
use App\Models\Node;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Models\User;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Eggs\EggChangerService;
use App\Services\Servers\RandomWordService;
use App\Services\Servers\ReinstallServerService;
use App\Services\Servers\ServerDeletionService;
use App\Services\Servers\SuspensionService;
use App\Services\Servers\ToggleInstallService;
use App\Services\Servers\TransferServerService;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
2024-06-16 13:50:28 -04:00
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Notifications\Notification;
2024-03-24 01:48:03 -04:00
use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Fieldset;
2025-09-24 13:34:19 +02:00
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
2025-09-24 13:34:19 +02:00
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Alignment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use LogicException;
use Random\RandomException;
2024-03-24 01:48:03 -04:00
class EditServer extends EditRecord
{
use CanCustomizeHeaderActions;
use CanCustomizeHeaderWidgets;
2024-03-24 01:48:03 -04:00
protected static string $resource = ServerResource::class;
private DaemonServerRepository $daemonServerRepository;
public function boot(DaemonServerRepository $daemonServerRepository): void
{
$this->daemonServerRepository = $daemonServerRepository;
}
/**
* @throws RandomException
* @throws Exception
*/
public function form(Schema $schema): Schema
2024-04-23 19:45:11 -04:00
{
return $schema
->components([
Tabs::make('Tabs')
->persistTabInQueryString()
->columns([
2024-04-23 19:45:11 -04:00
'default' => 2,
'sm' => 2,
'md' => 4,
'lg' => 6,
])
2024-07-30 10:43:24 -04:00
->columnSpanFull()
->tabs([
Tab::make('information')
->label(trans('admin/server.tabs.information'))
2024-06-02 15:05:45 -04:00
->icon('tabler-info-circle')
->schema([
TextInput::make('name')
->prefixIcon('tabler-server')
->label(trans('admin/server.name'))
->suffixAction(Action::make('random')
->icon('tabler-dice-' . random_int(1, 6))
->action(function (Set $set, Get $get) {
$egg = Egg::find($get('egg_id'));
$prefix = $egg ? str($egg->name)->lower()->kebab() . '-' : '';
$word = (new RandomWordService())->word();
$set('name', $prefix . $word);
}))
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 3,
])
->required()
->maxLength(255),
Select::make('owner_id')
->prefixIcon('tabler-user')
->label(trans('admin/server.owner'))
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 2,
])
->relationship('user', 'username')
->searchable(['username', 'email'])
->getOptionLabelFromRecordUsing(fn (User $user) => "$user->username ($user->email)")
->preload()
->required(),
ToggleButtons::make('condition')
->label(trans('admin/server.server_status'))
->formatStateUsing(fn (Server $server) => $server->condition)
->options(fn ($state) => [$state->value => $state->getLabel()])
->colors(fn ($state) => [$state->value => $state->getColor()])
->icons(fn ($state) => [$state->value => $state->getIcon()])
->stateCast(new ServerConditionStateCast())
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 1,
'lg' => 1,
])
->hintAction(
Action::make('view_install_log')
->label(trans('admin/server.view_install_log'))
//->visible(fn (Server $server) => $server->isFailedInstall())
->modalHeading('')
->modalSubmitAction(false)
->modalFooterActionsAlignment(Alignment::Right)
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
->schema([
CodeEditor::make('logs')
->hiddenLabel()
->formatStateUsing(function (Server $server, DaemonServerRepository $serverRepository) {
try {
return $serverRepository->setServer($server)->getInstallLogs();
} catch (ConnectionException) {
Notification::make()
->title(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->body(trans('admin/server.notifications.log_failed'))
->color('warning')
->warning()
->send();
} catch (Exception) {
return '';
}
return '';
}),
])
),
Textarea::make('description')
->label(trans('admin/server.description'))
->columnSpanFull(),
2024-04-23 19:45:11 -04:00
TextInput::make('uuid')
->label(trans('admin/server.uuid'))
->copyable()
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 3,
])
->readOnly()
->dehydrated(false),
TextInput::make('uuid_short')
->label(trans('admin/server.short_uuid'))
->copyable()
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 3,
])
->readOnly()
->dehydrated(false),
TextInput::make('external_id')
->label(trans('admin/server.external_id'))
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 3,
])
->unique()
->maxLength(255),
Select::make('node_id')
->label(trans('admin/server.node'))
->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id')))
->columnSpan([
'default' => 2,
'sm' => 1,
'md' => 2,
'lg' => 3,
])
->disabled(),
]),
Tab::make('environment_configuration')
->label(trans('admin/server.tabs.environment_configuration'))
2024-06-02 15:05:45 -04:00
->icon('tabler-brand-docker')
2024-05-07 21:44:44 -04:00
->schema([
Fieldset::make(trans('admin/server.resource_limits'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
2024-05-19 21:15:43 -04:00
->schema([
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
ToggleButtons::make('unlimited_cpu')
->dehydrated()
->label(trans('admin/server.cpu'))->inlineLabel()->inline()
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
])
->columnSpan(2),
TextInput::make('cpu')
->dehydratedWhenHidden()
->hidden(fn (Get $get) => $get('unlimited_cpu'))
->label(trans('admin/server.cpu_limit'))->inlineLabel()
->suffix('%')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
ToggleButtons::make('unlimited_mem')
->dehydrated()
->label(trans('admin/server.memory'))->inlineLabel()->inline()
->afterStateUpdated(fn (Set $set) => $set('memory', 0))
->formatStateUsing(fn (Get $get) => $get('memory') == 0)
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
])
->columnSpan(2),
TextInput::make('memory')
->dehydratedWhenHidden()
->hidden(fn (Get $get) => $get('unlimited_mem'))
->label(trans('admin/server.memory_limit'))->inlineLabel()
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
->hintIcon('tabler-question-mark', trans('admin/server.memory_helper'))
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
2024-05-07 21:44:44 -04:00
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
ToggleButtons::make('unlimited_disk')
->dehydrated()
->label(trans('admin/server.disk'))->inlineLabel()->inline()
->live()
->afterStateUpdated(fn (Set $set) => $set('disk', 0))
->formatStateUsing(fn (Get $get) => $get('disk') == 0)
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
])
->columnSpan(2),
TextInput::make('disk')
->dehydratedWhenHidden()
->hidden(fn (Get $get) => $get('unlimited_disk'))
->label(trans('admin/server.disk_limit'))->inlineLabel()
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
->required()
->columnSpan(2)
->numeric()
->minValue(0),
]),
]),
2024-05-19 21:15:43 -04:00
Fieldset::make(trans('admin/server.advanced_limits'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
->schema([
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
ToggleButtons::make('cpu_pinning')
->label(trans('admin/server.cpu_pin'))->inlineLabel()->inline()
->default(0)
->afterStateUpdated(fn (Set $set) => $set('threads', []))
->formatStateUsing(fn (Get $get) => !empty($get('threads')))
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.disabled'),
1 => trans('admin/server.enabled'),
])
->colors([
0 => 'success',
1 => 'warning',
])
->columnSpan(2),
TagsInput::make('threads')
->dehydratedWhenHidden()
->hidden(fn (Get $get) => !$get('cpu_pinning'))
->label(trans('admin/server.threads'))->inlineLabel()
->required(fn (Get $get) => $get('cpu_pinning'))
->columnSpan(2)
->separator()
->splitKeys([','])
->placeholder(trans('admin/server.pin_help')),
]),
ToggleButtons::make('swap_support')
->live()
->label(trans('admin/server.swap'))->inlineLabel()->inline()
->columnSpan(2)
->afterStateUpdated(function ($state, Set $set) {
$value = match ($state) {
'unlimited' => -1,
'disabled' => 0,
'limited' => 128,
default => throw new LogicException('Invalid state')
};
$set('swap', $value);
})
->formatStateUsing(function (Get $get) {
return match (true) {
$get('swap') > 0 => 'limited',
$get('swap') == 0 => 'disabled',
$get('swap') < 0 => 'unlimited',
default => throw new LogicException('Invalid state')
};
})
->options([
'unlimited' => trans('admin/server.unlimited'),
'limited' => trans('admin/server.limited'),
'disabled' => trans('admin/server.disabled'),
])
->colors([
'unlimited' => 'primary',
'limited' => 'warning',
'disabled' => 'danger',
]),
TextInput::make('swap')
->dehydratedWhenHidden()
->hidden(fn (Get $get) => match ($get('swap_support')) {
'disabled', 'unlimited', true => true,
default => false,
})
->label(trans('admin/server.swap'))->inlineLabel()
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
->minValue(-1)
->columnSpan(2)
->required()
->integer(),
]),
Hidden::make('io')
->helperText('The IO performance relative to other running containers')
->label('Block IO Proportion'),
Grid::make()
->columns(4)
->columnSpanFull()
->schema([
ToggleButtons::make('oom_killer')
->dehydrated()
->label(trans('admin/server.oom'))
->formatStateUsing(fn ($state) => $state)
->inlineLabel()
->inline()
->columnSpan(2)
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.disabled'),
1 => trans('admin/server.enabled'),
])
->colors([
0 => 'success',
1 => 'danger',
]),
]),
2024-05-19 21:15:43 -04:00
]),
2024-04-23 19:45:11 -04:00
Fieldset::make(trans('admin/server.feature_limits'))
->inlineLabel()
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 3,
])
2024-05-19 21:15:43 -04:00
->schema([
TextInput::make('allocation_limit')
->label(trans('admin/server.allocations'))
->suffixIcon('tabler-network')
->required()
2024-06-15 12:33:33 -04:00
->minValue(0)
->numeric(),
TextInput::make('database_limit')
->label(trans('admin/server.databases'))
->suffixIcon('tabler-database')
->required()
2024-06-15 12:33:33 -04:00
->minValue(0)
->numeric(),
TextInput::make('backup_limit')
->label(trans('admin/server.backups'))
->suffixIcon('tabler-copy-check')
2024-05-19 21:15:43 -04:00
->required()
2024-06-15 12:33:33 -04:00
->minValue(0)
->numeric(),
2024-05-19 21:15:43 -04:00
]),
Fieldset::make(trans('admin/server.docker_settings'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 4,
])
2024-05-19 21:15:43 -04:00
->schema([
Select::make('select_image')
->label(trans('admin/server.image_name'))
->live()
->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
->options(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
$currentImage = $get('image');
if (!$currentImage && $images) {
$defaultImage = collect($images)->first();
$set('image', $defaultImage);
$set('select_image', $defaultImage);
}
return array_flip($images) + ['ghcr.io/custom-image' => 'Custom Image'];
2024-05-19 21:15:43 -04:00
})
->selectablePlaceholder(false)
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 2,
]),
TextInput::make('image')
->label(trans('admin/server.image'))
->required()
->afterStateUpdated(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
if (in_array($state, $images)) {
$set('select_image', $state);
} else {
$set('select_image', 'ghcr.io/custom-image');
}
2024-05-19 21:15:43 -04:00
})
->placeholder(trans('admin/server.image_placeholder'))
->columnSpan([
'default' => 1,
'sm' => 2,
'md' => 3,
'lg' => 2,
]),
2024-05-19 21:15:43 -04:00
KeyValue::make('docker_labels')
->live()
->label(trans('admin/server.container_labels'))
->keyLabel(trans('admin/server.title'))
->valueLabel(trans('admin/server.description'))
->columnSpanFull(),
2024-05-19 21:15:43 -04:00
]),
]),
Tab::make('egg')
->label(trans('admin/server.egg'))
2024-06-02 15:05:45 -04:00
->icon('tabler-egg')
->columns([
'default' => 1,
'sm' => 3,
'md' => 3,
'lg' => 5,
2024-04-23 19:45:11 -04:00
])
->schema([
Select::make('egg_id')
->disabled()
->prefixIcon('tabler-egg')
->columnSpan([
2024-07-30 10:43:24 -04:00
'default' => 6,
'sm' => 3,
'md' => 3,
2024-07-30 10:43:24 -04:00
'lg' => 4,
])
->relationship('egg', 'name')
->label(trans('admin/server.name'))
->searchable()
->preload()
->required()
->hintAction(
Action::make('change_egg')
->label(trans('admin/server.change_egg'))
->action(function (array $data, Server $server, EggChangerService $service) {
$service->handle($server, $data['egg_id'], $data['keep_old_variables']);
// Use redirect instead of fillForm to prevent server variables from duplicating
$this->redirect($this->getUrl(['record' => $server, 'tab' => 'egg::data::tab']), true);
})
->schema(fn (Server $server) => [
Select::make('egg_id')
->label(trans('admin/server.new_egg'))
->prefixIcon('tabler-egg')
->options(fn () => Egg::all()->filter(fn (Egg $egg) => $egg->id !== $server->egg->id)->mapWithKeys(fn (Egg $egg) => [$egg->id => $egg->name]))
->searchable()
->preload()
->required(),
Toggle::make('keep_old_variables')
->label(trans('admin/server.keep_old_variables'))
->default(true),
])
),
ToggleButtons::make('skip_scripts')
->label(trans('admin/server.install_script'))
->inline()
2024-07-30 10:43:24 -04:00
->columnSpan([
'default' => 6,
'sm' => 1,
'md' => 1,
'lg' => 2,
])
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.yes'),
1 => trans('admin/server.skip'),
])
->colors([
0 => 'primary',
1 => 'danger',
])
->icons([
0 => 'tabler-code',
1 => 'tabler-code-off',
])
->required(),
Hidden::make('previewing')
->default(false),
Textarea::make('startup')
->label(trans('admin/server.startup_cmd'))
->required()
2024-07-30 10:43:24 -04:00
->columnSpan(6)
->autosize()
->hintAction(PreviewStartupAction::make('preview')),
Textarea::make('defaultStartup')
->hintCopy()
->label(trans('admin/server.default_startup'))
->disabled()
feat: Client UI translate to Filament (from React) (#416) * Add new panel * Add some basic resource pages * Wip * Wip terminal * Wip * Add new panel * Add some basic resource pages * Wip * [Sub-Users] Add Invite TODO: The logic with permissions * [Sub-Users] Fix Creation * [Cron] Add basics * Add basic auth and messages * Add basic buttons * WIP on issue/353 * WIP on issue/353 * Add Database page * Update Database Page * Start of Backup Page * Composer Update * Changes * Send input * Remove this includes * Better offline handling * Consolidate top nav config * Update Backups Page * Update Backups * Change name * Add Assign All, Layout Fixes. * conflict * update schedule pages * fix phpstan * update pint.json * add cron presets to schedule * fix tests * fix task creation * schedules: disable task creation if limit is reached & disable backup action if backup limit is 0 * update activity pages * update resources * Update Edit User TODO: actually save permissions when they're changed. TODO: Figure out why Control does not update it's state... but the rest do... * .... Sure it works. TODO: Update permissions when you save editing a sub user. * user: update canAccessPanel & canAccessTenant * add helper to convert bytes into readable format * very basic file explorer * files: fix some stuff & remove dummy data * files: better error handling * files: basic file editor * files: add some actions * File manager updates * files: fix paths * Revery Composer Upgrade, Fixes SQLite * fix: Pint (#517) feat: MenuItems to and from admin * Update File Editing Updated File Editing to its own page, Added Permission checks for file manager. Co-authored-by: Boy132 <Boy132@users.noreply.github.com> * add enum for editor langs * files: add upload & pull actions * fix build * files: handle images * Update to Filament v3.2.98 * files: add remaining actions * use `authorize` instead of `hidden` * fix canAccessTenant * update date columns * files: testing & fixes * Fix File Names Co-authored-by: lancepioch <git@lance.sh> * Combine Pull/Upload * Fix BulkDelete * Uncontained tabs * Hide Lang Selection, Move Actions * Update Monaco, more custom * Add livewire config livewire limits uploads to 12MB... who knows why... Fixed uploading a single files failing * files: fix record url * basic setup for settings & startup page * make abstract class for simple app pages * Basic Startup Page * Update nav sort * small cleanup * startup: fix shouldHideComponent & getSelectOptionsFromRules * startup: fix non editable fields & set default value * startup: add todo for save button * Save Variables after update & off click Variables update when the user clicks off the input. * Notifications are cool * Add rule validation * Sort variables by sortid * pint * Settings Page + Startup Changes * settings: cleanup * refactor: use server model for ServerFormPage (formerly known as SimplePage) * Use Repeater for variables * Add Network, Remove breadcrumbs * Add paginated to file explorer * Fix updating variables * Add link to go to new client area * fix after merge * Add graphs to console page Graphs still need to get the data from the web socket. * fix pint & phpstan * fix authorizeAccess for EditFiles and Startup page * Fix rules on startup page * Update console size * Fix node name * add "global search" to files list requires https://github.com/pelican-dev/wings/pull/44 * remove debug dummy data * update view action on ListServers * enable SPA mode for app panel * remove colors from app panel they are defined globally in AppServiceProvider * update global search ui a bit (to be replaced with a custom page that is similar to the list files table) * add own page for global search untested - and route needs cleanup (if possible) * fix File getRows * remove "path" from SearchFiles (for now) * fix caching for searched files * add title and breadcrumbs to global search page * make cpu & memory charts on console page working * fix phpstan * add missing import * cleanup console views & widgets * add overview stats to console * don't be so lazy, console! * make history working * decode data to get array * add missing On * fix json_decode * change polling to 1 sec * hide "0" cpu/ memory * add data to network chart * Remove data labels * fix data on network chart * fix data on network chart (2nd try) * WIP Network Stats * Remove test * Change MaxWidth * run pint * fix phpstan * Fix storeStats cast * make $data a string this time for real * update visible check for "admin" menu item * remove account widget * rebrand "Dashboard" to "Server List" WIP - doesn't look good but is somewhat working * fix canAccessPanel * separate server list into own panel * change path to avoid conflicts with old client area (and remove sidebar width) * display correct icon and color on server list entries * show total memory if server is offline * replace custom server list page with ListRecords page * fix tests * fix namespace * remove "open" button and make whole column clickable * Update EditProfile * run pint * fix access to server list * add new login page to panels * fix next_run_at for new schedules * use new DateTimeColumn * add own column for file bytes * return to server list when clicking title * fix console loading * handle server with "conflict state" * add banner if server is in "conflict state" * fix phpstan * update docker image select * fix permission checks on Settings & Startup pages * fix query for activity log page * fix activity log not being logged * adjust ListActivities * fix phpstan * fix pint * fix profile menu item link on server panel * add ip tooltip to activity logs (and role permission) * change backup icon * update navigation sort * general code cleanup * more cleanup * Disable Restart/Stop if server is offline * Change rename notification * Remove negation on abort_unless * Add notification on save * Single disabled closure & comment unused import * Add required to Server Name & Nullable to description * mutateFormDataBeforeSave doesn't work since we use forceFill * Fix web socket connection not existing. * Fix some subuser permissions * add permission checks to resources * do not allow self-deletion * Update editing file permissions * Fix of the previous fix * add service for subuser updating * Only allow save if they have file_update * Remove unused import * Update backup delete button * Add Delete, remove bulks * Update Database page * Use Allocation Permissions * add canAccess check to startup * Add Permission checks to Settings page * add service for subuser deletion * Remove Kill permission * Updates * fix move files * add redirects * fix phpstan * activity: remove properties from tans for now * If alias, use that, else ip --------- Co-authored-by: notCharles <charles@pelican.dev> Co-authored-by: Boy132 <mail@boy132.de> Co-authored-by: Senna <62171904+Poseidon281@users.noreply.github.com> Co-authored-by: Boy132 <Boy132@users.noreply.github.com> Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-11-30 22:13:45 -05:00
->autosize()
->columnSpan(6)
->formatStateUsing(function ($state, Get $get) {
2024-05-19 21:15:43 -04:00
$egg = Egg::query()->find($get('egg_id'));
return $egg->startup;
feat: Client UI translate to Filament (from React) (#416) * Add new panel * Add some basic resource pages * Wip * Wip terminal * Wip * Add new panel * Add some basic resource pages * Wip * [Sub-Users] Add Invite TODO: The logic with permissions * [Sub-Users] Fix Creation * [Cron] Add basics * Add basic auth and messages * Add basic buttons * WIP on issue/353 * WIP on issue/353 * Add Database page * Update Database Page * Start of Backup Page * Composer Update * Changes * Send input * Remove this includes * Better offline handling * Consolidate top nav config * Update Backups Page * Update Backups * Change name * Add Assign All, Layout Fixes. * conflict * update schedule pages * fix phpstan * update pint.json * add cron presets to schedule * fix tests * fix task creation * schedules: disable task creation if limit is reached & disable backup action if backup limit is 0 * update activity pages * update resources * Update Edit User TODO: actually save permissions when they're changed. TODO: Figure out why Control does not update it's state... but the rest do... * .... Sure it works. TODO: Update permissions when you save editing a sub user. * user: update canAccessPanel & canAccessTenant * add helper to convert bytes into readable format * very basic file explorer * files: fix some stuff & remove dummy data * files: better error handling * files: basic file editor * files: add some actions * File manager updates * files: fix paths * Revery Composer Upgrade, Fixes SQLite * fix: Pint (#517) feat: MenuItems to and from admin * Update File Editing Updated File Editing to its own page, Added Permission checks for file manager. Co-authored-by: Boy132 <Boy132@users.noreply.github.com> * add enum for editor langs * files: add upload & pull actions * fix build * files: handle images * Update to Filament v3.2.98 * files: add remaining actions * use `authorize` instead of `hidden` * fix canAccessTenant * update date columns * files: testing & fixes * Fix File Names Co-authored-by: lancepioch <git@lance.sh> * Combine Pull/Upload * Fix BulkDelete * Uncontained tabs * Hide Lang Selection, Move Actions * Update Monaco, more custom * Add livewire config livewire limits uploads to 12MB... who knows why... Fixed uploading a single files failing * files: fix record url * basic setup for settings & startup page * make abstract class for simple app pages * Basic Startup Page * Update nav sort * small cleanup * startup: fix shouldHideComponent & getSelectOptionsFromRules * startup: fix non editable fields & set default value * startup: add todo for save button * Save Variables after update & off click Variables update when the user clicks off the input. * Notifications are cool * Add rule validation * Sort variables by sortid * pint * Settings Page + Startup Changes * settings: cleanup * refactor: use server model for ServerFormPage (formerly known as SimplePage) * Use Repeater for variables * Add Network, Remove breadcrumbs * Add paginated to file explorer * Fix updating variables * Add link to go to new client area * fix after merge * Add graphs to console page Graphs still need to get the data from the web socket. * fix pint & phpstan * fix authorizeAccess for EditFiles and Startup page * Fix rules on startup page * Update console size * Fix node name * add "global search" to files list requires https://github.com/pelican-dev/wings/pull/44 * remove debug dummy data * update view action on ListServers * enable SPA mode for app panel * remove colors from app panel they are defined globally in AppServiceProvider * update global search ui a bit (to be replaced with a custom page that is similar to the list files table) * add own page for global search untested - and route needs cleanup (if possible) * fix File getRows * remove "path" from SearchFiles (for now) * fix caching for searched files * add title and breadcrumbs to global search page * make cpu & memory charts on console page working * fix phpstan * add missing import * cleanup console views & widgets * add overview stats to console * don't be so lazy, console! * make history working * decode data to get array * add missing On * fix json_decode * change polling to 1 sec * hide "0" cpu/ memory * add data to network chart * Remove data labels * fix data on network chart * fix data on network chart (2nd try) * WIP Network Stats * Remove test * Change MaxWidth * run pint * fix phpstan * Fix storeStats cast * make $data a string this time for real * update visible check for "admin" menu item * remove account widget * rebrand "Dashboard" to "Server List" WIP - doesn't look good but is somewhat working * fix canAccessPanel * separate server list into own panel * change path to avoid conflicts with old client area (and remove sidebar width) * display correct icon and color on server list entries * show total memory if server is offline * replace custom server list page with ListRecords page * fix tests * fix namespace * remove "open" button and make whole column clickable * Update EditProfile * run pint * fix access to server list * add new login page to panels * fix next_run_at for new schedules * use new DateTimeColumn * add own column for file bytes * return to server list when clicking title * fix console loading * handle server with "conflict state" * add banner if server is in "conflict state" * fix phpstan * update docker image select * fix permission checks on Settings & Startup pages * fix query for activity log page * fix activity log not being logged * adjust ListActivities * fix phpstan * fix pint * fix profile menu item link on server panel * add ip tooltip to activity logs (and role permission) * change backup icon * update navigation sort * general code cleanup * more cleanup * Disable Restart/Stop if server is offline * Change rename notification * Remove negation on abort_unless * Add notification on save * Single disabled closure & comment unused import * Add required to Server Name & Nullable to description * mutateFormDataBeforeSave doesn't work since we use forceFill * Fix web socket connection not existing. * Fix some subuser permissions * add permission checks to resources * do not allow self-deletion * Update editing file permissions * Fix of the previous fix * add service for subuser updating * Only allow save if they have file_update * Remove unused import * Update backup delete button * Add Delete, remove bulks * Update Database page * Use Allocation Permissions * add canAccess check to startup * Add Permission checks to Settings page * add service for subuser deletion * Remove Kill permission * Updates * fix move files * add redirects * fix phpstan * activity: remove properties from tans for now * If alias, use that, else ip --------- Co-authored-by: notCharles <charles@pelican.dev> Co-authored-by: Boy132 <mail@boy132.de> Co-authored-by: Senna <62171904+Poseidon281@users.noreply.github.com> Co-authored-by: Boy132 <Boy132@users.noreply.github.com> Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
2024-11-30 22:13:45 -05:00
}),
Repeater::make('server_variables')
->hiddenLabel()
->relationship('serverVariables', function (Builder $query) {
/** @var Server $server */
$server = $this->getRecord();
foreach ($server->variables as $variable) {
ServerVariable::query()->firstOrCreate([
'server_id' => $server->id,
'variable_id' => $variable->id,
], [
'variable_value' => $variable->server_value ?? '',
]);
}
return $query->orderByPowerJoins('variable.sort');
})
->grid()
->mutateRelationshipDataBeforeSaveUsing(function (array $data): array {
$data['variable_value'] ??= '';
2024-05-19 21:15:43 -04:00
return $data;
2024-05-19 21:15:43 -04:00
})
->reorderable(false)->addable(false)->deletable(false)
->schema([
StartupVariable::make('variable_value')
->fromRecord(),
])
->columnSpan(6),
]),
Tab::make('mounts')
->label(trans('admin/server.mounts'))
2024-06-02 15:05:45 -04:00
->icon('tabler-layers-linked')
2025-05-09 19:18:20 +02:00
->schema(fn (Get $get) => [
ServerResource::getMountCheckboxList($get),
2024-05-29 19:18:09 -04:00
]),
Tab::make('actions')
->label(trans('admin/server.actions'))
2024-06-02 15:05:45 -04:00
->icon('tabler-settings')
->schema([
Fieldset::make(trans('admin/server.actions'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
'md' => 2,
'lg' => 6,
])
->schema([
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
Action::make('toggleInstall')
->label(trans('admin/server.toggle_install'))
->disabled(fn (Server $server) => $server->isSuspended())
->modal(fn (Server $server) => $server->isFailedInstall())
->modalHeading(trans('admin/server.toggle_install_failed_header'))
->modalDescription(trans('admin/server.toggle_install_failed_desc'))
->modalSubmitActionLabel(trans('admin/server.reinstall'))
->action(function (ToggleInstallService $toggleService, ReinstallServerService $reinstallService, Server $server) {
if ($server->isFailedInstall()) {
try {
$reinstallService->handle($server);
Notification::make()
->title(trans('admin/server.notifications.reinstall_started'))
->success()
->send();
} catch (Exception) {
Notification::make()
->title(trans('admin/server.notifications.reinstall_failed'))
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->danger()
->send();
}
} else {
try {
$toggleService->handle($server);
Notification::make()
->title(trans('admin/server.notifications.install_toggled'))
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title(trans('admin/server.notifications.install_toggle_failed'))
->body($exception->getMessage())
->danger()
->send();
}
}
}),
])->fullWidth(),
ToggleButtons::make('install_help')
->hiddenLabel()
->hint(trans('admin/server.toggle_install_help')),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
Action::make('toggleSuspend')
->label(trans('admin/server.suspend'))
->color('warning')
->hidden(fn (Server $server) => $server->isSuspended())
->action(function (SuspensionService $suspensionService, Server $server) {
try {
$suspensionService->handle($server, SuspendAction::Suspend);
Notification::make()
->success()
->title(trans('admin/server.notifications.server_suspended'))
->send();
} catch (Exception) {
Notification::make()
->warning()
->title(trans('admin/server.notifications.server_suspension'))
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->send();
}
}),
Action::make('toggleUnsuspend')
->label(trans('admin/server.unsuspend'))
->color('success')
->hidden(fn (Server $server) => !$server->isSuspended())
->action(function (SuspensionService $suspensionService, Server $server) {
try {
$suspensionService->handle($server, SuspendAction::Unsuspend);
Notification::make()
->success()
->title(trans('admin/server.notifications.server_unsuspended'))
->send();
} catch (Exception) {
Notification::make()
->warning()
->title(trans('admin/server.notifications.server_suspension'))
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->send();
}
}),
])->fullWidth(),
ToggleButtons::make('server_suspend')
->hiddenLabel()
->hidden(fn (Server $server) => $server->isSuspended())
->hint(trans('admin/server.notifications.server_suspend_help')),
ToggleButtons::make('server_unsuspend')
->hiddenLabel()
->hidden(fn (Server $server) => !$server->isSuspended())
->hint(trans('admin/server.notifications.server_unsuspend_help')),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
Action::make('transfer')
->label(trans('admin/server.transfer'))
->disabled(fn (Server $server) => Node::count() <= 1 || $server->isInConflictState())
->modalHeading(trans('admin/server.transfer'))
->schema($this->transferServer())
->action(function (TransferServerService $transfer, Server $server, $data) {
try {
$transfer->handle($server, Arr::get($data, 'node_id'), Arr::get($data, 'allocation_id'), Arr::get($data, 'allocation_additional', []));
Notification::make()
->title(trans('admin/server.notifications.transfer_started'))
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title(trans('admin/server.notifications.transfer_failed'))
->body($exception->getMessage())
->danger()
->send();
}
}),
])->fullWidth(),
ToggleButtons::make('server_transfer')
->hiddenLabel()
->hint(new HtmlString(trans('admin/server.transfer_help'))),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
Action::make('reinstall')
->label(trans('admin/server.reinstall'))
->color('danger')
->requiresConfirmation()
->modalHeading(trans('admin/server.reinstall_modal_heading'))
->modalDescription(trans('admin/server.reinstall_modal_description'))
->disabled(fn (Server $server) => $server->isSuspended())
->action(function (ReinstallServerService $service, Server $server) {
try {
$service->handle($server);
Notification::make()
->title(trans('admin/server.notifications.reinstall_started'))
->success()
->send();
} catch (Exception) {
Notification::make()
->title(trans('admin/server.notifications.reinstall_failed'))
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->danger()
->send();
}
}),
])->fullWidth(),
ToggleButtons::make('server_reinstall')
->hiddenLabel()
->hint(trans('admin/server.reinstall_help')),
]),
]),
2024-05-19 21:15:43 -04:00
]),
2024-04-23 19:45:11 -04:00
]),
]);
}
2024-05-29 18:27:54 -04:00
/** @return Component[]
* @throws Exception
*/
protected function transferServer(): array
2024-05-29 18:27:54 -04:00
{
return [
Select::make('node_id')
->label(trans('admin/server.node'))
->prefixIcon('tabler-server-2')
->selectablePlaceholder(false)
->default(fn (Server $server) => Node::whereNot('id', $server->node->id)->first()?->id)
->required()
->live()
->options(fn (Server $server) => Node::whereNot('id', $server->node->id)->pluck('name', 'id')->all()),
Select::make('allocation_id')
->label(trans('admin/server.primary_allocation'))
->disabled(fn (Get $get, Server $server) => !$get('node_id') || !$server->allocation_id)
->required(fn (Server $server) => $server->allocation_id)
->prefixIcon('tabler-network')
->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')
->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')),
];
2024-05-29 18:27:54 -04:00
}
2024-10-19 18:29:44 -04:00
/** @return array<Action|ActionGroup> */
protected function getDefaultHeaderActions(): array
2024-03-24 01:48:03 -04:00
{
2025-03-23 22:08:59 +01:00
/** @var Server $server */
$server = $this->getRecord();
$canForceDelete = cache()->get("servers.$server->uuid.canForceDelete", false);
2024-03-24 01:48:03 -04:00
return [
Action::make('Delete')
2024-04-23 20:03:57 -04:00
->color('danger')
2025-03-23 22:08:59 +01:00
->label(trans('filament-actions::delete.single.label'))
->modalHeading(trans('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
->modalSubmitActionLabel(trans('filament-actions::delete.single.label'))
->requiresConfirmation()
->action(function (Server $server, ServerDeletionService $service) {
try {
$service->handle($server);
return redirect(ListServers::getUrl(panel: 'admin'));
2025-03-23 22:08:59 +01:00
} catch (ConnectionException) {
cache()->put("servers.$server->uuid.canForceDelete", true, now()->addMinutes(5));
return Notification::make()
2025-03-23 22:08:59 +01:00
->title(trans('admin/server.notifications.error_server_delete'))
->body(trans('admin/server.notifications.error_server_delete_body'))
->color('warning')
->icon('tabler-database')
->warning()
->send();
}
})
->hidden(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Action::make('ForceDelete')
2025-03-23 22:08:59 +01:00
->color('danger')
->label(trans('filament-actions::force-delete.single.label'))
->modalHeading(trans('filament-actions::force-delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
->modalSubmitActionLabel(trans('filament-actions::force-delete.single.label'))
2024-09-27 14:28:32 -04:00
->requiresConfirmation()
2024-10-19 17:22:03 -04:00
->action(function (Server $server, ServerDeletionService $service) {
2025-03-23 22:08:59 +01:00
try {
$service->withForce()->handle($server);
2024-09-27 14:28:32 -04:00
2025-03-23 22:08:59 +01:00
return redirect(ListServers::getUrl(panel: 'admin'));
} catch (ConnectionException) {
return cache()->forget("servers.$server->uuid.canForceDelete");
2025-03-23 22:08:59 +01:00
}
})
2025-03-23 22:08:59 +01:00
->visible(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Action::make('console')
->label(trans('admin/server.console'))
2024-04-29 22:13:54 -04:00
->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)),
2024-04-29 22:13:54 -04:00
$this->getSaveFormAction()->formId('form'),
2024-03-24 01:48:03 -04:00
];
}
2024-10-19 18:29:44 -04:00
2024-04-30 19:49:18 -04:00
protected function getFormActions(): array
{
return [];
}
2024-04-25 19:48:46 -04:00
protected function mutateFormDataBeforeSave(array $data): array
{
if (!isset($data['description'])) {
$data['description'] = '';
}
unset($data['docker'], $data['status'], $data['allocation_id']);
2024-04-25 19:48:46 -04:00
return $data;
}
protected function afterSave(): void
{
/** @var Server $server */
$server = $this->record;
$changed = collect($server->getChanges())->except(['updated_at', 'name', 'owner_id', 'condition', 'description', 'external_id', 'tags', 'cpu_pinning', 'allocation_limit', 'database_limit', 'backup_limit', 'skip_scripts'])->all();
try {
if ($changed) {
$this->daemonServerRepository->setServer($server)->sync();
}
parent::getSavedNotification()?->send();
} catch (ConnectionException) {
Notification::make()
->title(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
->body(trans('admin/server.notifications.error_connecting_description'))
->color('warning')
->icon('tabler-database')
->warning()
->send();
}
}
protected function getSavedNotification(): ?Notification
{
return null;
}
public function getRelationManagers(): array
{
return [
AllocationsRelationManager::class,
DatabasesRelationManager::class,
];
}
2024-03-24 01:48:03 -04:00
}