daemonServerRepository = new DaemonServerRepository(); } /** @return Stack[] */ protected function gridColumns(): array { return [ Stack::make([ ServerEntryColumn::make('server_entry') ->searchable(['name']), ]), ]; } /** @return Column[] */ protected function tableColumns(): array { return [ TextColumn::make('condition') ->label(trans('server/dashboard.status')) ->badge() ->tooltip(fn (Server $server) => $server->formatResource(ServerResourceType::Uptime)) ->icon(fn (Server $server) => $server->condition->getIcon()) ->color(fn (Server $server) => $server->condition->getColor()), TextColumn::make('name') ->label(trans('server/dashboard.title')) ->description(fn (Server $server) => $server->description) ->grow() ->searchable(), TextColumn::make('allocation.address') ->label('') ->badge() ->visibleFrom('md') ->copyable(request()->isSecure()) ->state(fn (Server $server) => $server->allocation->address ?? 'None'), TextColumn::make('cpuUsage') ->label(trans('server/dashboard.resources')) ->icon('tabler-cpu') ->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::CPULimit)])) ->state(fn (Server $server) => $server->formatResource(ServerResourceType::CPU)) ->color(fn (Server $server) => $this->getResourceColor($server, 'cpu')), TextColumn::make('memoryUsage') ->label('') ->icon('tabler-device-desktop-analytics') ->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::MemoryLimit)])) ->state(fn (Server $server) => $server->formatResource(ServerResourceType::Memory)) ->color(fn (Server $server) => $this->getResourceColor($server, 'memory')), TextColumn::make('diskUsage') ->label('') ->icon('tabler-device-sd-card') ->tooltip(fn (Server $server) => trans('server/dashboard.usage_limit', ['resource' => $server->formatResource(ServerResourceType::DiskLimit)])) ->state(fn (Server $server) => $server->formatResource(ServerResourceType::Disk)) ->color(fn (Server $server) => $this->getResourceColor($server, 'disk')), ]; } public function table(Table $table): Table { $baseQuery = auth()->user()->accessibleServers(); $usingGrid = auth()->user()->getCustomization(CustomizationKey::DashboardLayout) === 'grid'; return $table ->paginated(false) ->query(fn () => $baseQuery) ->poll('15s') ->columns($usingGrid ? $this->gridColumns() : $this->tableColumns()) ->recordUrl(!$usingGrid ? (fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)) : null) ->recordActions(!$usingGrid ? ActionGroup::make(static::getPowerActions(view: 'table')) : []) ->recordActionsAlignment(Alignment::Center->value) ->contentGrid($usingGrid ? ['default' => 1, 'md' => 2] : null) ->emptyStateIcon('tabler-brand-docker') ->emptyStateDescription('') ->emptyStateHeading(fn () => $this->activeTab === 'my' ? 'You don\'t own any servers!' : 'You don\'t have access to any servers!') ->persistFiltersInSession() ->filters([ SelectFilter::make('egg') ->relationship('egg', 'name', fn (Builder $query) => $query->whereIn('id', $baseQuery->pluck('egg_id'))) ->searchable() ->preload(), SelectFilter::make('owner') ->relationship('user', 'username', fn (Builder $query) => $query->whereIn('id', $baseQuery->pluck('owner_id'))) ->searchable() ->hidden(fn () => $this->activeTab === 'my') ->preload(), ]); } public function updatedActiveTab(): void { $this->resetTable(); } public function getTabs(): array { $all = auth()->user()->accessibleServers(); $my = (clone $all)->where('owner_id', auth()->user()->id); $other = (clone $all)->whereNot('owner_id', auth()->user()->id); return [ 'my' => Tab::make('my') ->label(trans('server/dashboard.tabs.my')) ->badge(fn () => $my->count()) ->modifyQueryUsing(fn () => $my), 'other' => Tab::make('other') ->label(trans('server/dashboard.tabs.other')) ->badge(fn () => $other->count()) ->modifyQueryUsing(fn () => $other), 'all' => Tab::make('all') ->label(trans('server/dashboard.tabs.all')) ->badge($all->count()), ]; } protected function getResourceColor(Server $server, string $resource): ?string { $current = null; $limit = null; switch ($resource) { case 'cpu': $current = $server->retrieveResources()['cpu_absolute'] ?? 0; $limit = $server->cpu; if ($server->cpu === 0) { return null; } break; case 'memory': $current = $server->retrieveResources()['memory_bytes'] ?? 0; $limit = $server->memory * 2 ** 20; if ($server->memory === 0) { return null; } break; case 'disk': $current = $server->retrieveResources()['disk_bytes'] ?? 0; $limit = $server->disk * 2 ** 20; if ($server->disk === 0) { return null; } break; default: return null; } if ($current >= $limit * self::DANGER_THRESHOLD) { return 'danger'; } if ($current >= $limit * self::WARNING_THRESHOLD) { return 'warning'; } return null; } #[On('powerAction')] public function powerAction(Server $server, string $action): void { try { $this->daemonServerRepository->setServer($server)->power($action); Notification::make() ->title(trans('server/dashboard.power_actions')) ->body(trans('server/dashboard.power_action_sent', ['action' => $action, 'name' => $server->name])) ->success() ->send(); cache()->forget("servers.$server->uuid.status"); $this->redirect(self::getUrl(['tab' => $this->activeTab])); } catch (ConnectionException) { Notification::make() ->title(trans('exceptions.node.error_connecting', ['node' => $server->node->name])) ->danger() ->send(); } } /** @return Action[]|ActionGroup[] */ public static function getPowerActions(string $view): array { $actions = [ Action::make('start') ->color('primary') ->icon('tabler-player-play-filled') ->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_START, $server)) ->visible(fn (Server $server) => !$server->isInConflictState() && $server->retrieveStatus()->isStartable()) ->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'start']), Action::make('restart') ->color('gray') ->icon('tabler-reload') ->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_RESTART, $server)) ->visible(fn (Server $server) => !$server->isInConflictState() && $server->retrieveStatus()->isRestartable()) ->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'restart']), Action::make('stop') ->color('danger') ->icon('tabler-player-stop-filled') ->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server)) ->visible(fn (Server $server) => !$server->isInConflictState() && $server->retrieveStatus()->isStoppable()) ->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'stop']), Action::make('kill') ->color('danger') ->icon('tabler-alert-square') ->tooltip('This can result in data corruption and/or data loss!') ->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server)) ->visible(fn (Server $server) => !$server->isInConflictState() && $server->retrieveStatus()->isKillable()) ->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'kill']), ]; if ($view === 'table') { return $actions; } else { return [ ActionGroup::make($actions) ->icon('tabler-power') ->color('primary') ->tooltip('Power Actions') ->iconSize(IconSize::Large), ]; } } }