Merge branch 'main' into vehikl/singleton

This commit is contained in:
Vehikl 2025-06-12 15:44:31 -04:00
commit 234cdda1e1
25 changed files with 358 additions and 382 deletions

View File

@ -18,6 +18,17 @@ class QueueWorkerServiceCommand extends Command
public function handle(): void
{
if (@file_exists('/.dockerenv')) {
$result = Process::run('supervisorctl restart queue-worker');
if ($result->failed()) {
$this->error('Error restarting service: ' . $result->errorOutput());
return;
}
$this->line('Queue worker service file updated successfully.');
return;
}
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
$path = '/etc/systemd/system/' . $serviceName . '.service';

View File

@ -88,7 +88,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
public function isStartable(): bool
{
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
}
public function isRestartable(): bool
@ -97,18 +97,16 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
return true;
}
return !in_array($this, [ContainerStatus::Offline]);
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
}
public function isStoppable(): bool
{
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]);
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
}
public function isKillable(): bool
{
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created]
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
}
}

View File

@ -27,8 +27,16 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
};
}
public function getColor(): string
public function getColor(bool $hex = false): string
{
if ($hex) {
return match ($this) {
self::Normal, self::Installing, self::RestoringBackup => '#2563EB',
self::Suspended => '#D97706',
self::InstallFailed, self::ReinstallFailed => '#EF4444',
};
}
return match ($this) {
self::Normal => 'primary',
self::Installing => 'primary',

View File

@ -147,6 +147,8 @@ class RoleResource extends Resource
*/
private static function makeSection(string $model, array $options): Section
{
$model = ucwords($model);
$icon = null;
if (class_exists('\App\Filament\Admin\Resources\\' . $model . 'Resource')) {

View File

@ -9,12 +9,13 @@ use App\Filament\Server\Pages\Console;
use App\Models\Permission;
use App\Models\Server;
use App\Repositories\Daemon\DaemonPowerRepository;
use AymanAlhattami\FilamentContextMenu\Columns\ContextMenuTextColumn;
use Filament\Notifications\Notification;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords;
use Filament\Support\Enums\Alignment;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\ColumnGroup;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
@ -38,121 +39,73 @@ class ListServers extends ListRecords
$this->daemonPowerRepository = new DaemonPowerRepository();
}
public function table(Table $table): Table
/** @return Stack[] */
protected function gridColumns(): array
{
$baseQuery = auth()->user()->accessibleServers();
return [
Stack::make([
ServerEntryColumn::make('server_entry')
->searchable(['name']),
]),
];
}
$menuOptions = function (Server $server) {
$status = $server->retrieveStatus();
return [
Action::make('start')
->color('primary')
->authorize(fn () => auth()->user()->can(Permission::ACTION_CONTROL_START, $server))
->visible(fn () => $status->isStartable())
->dispatch('powerAction', ['server' => $server, 'action' => 'start'])
->icon('tabler-player-play-filled'),
Action::make('restart')
->color('gray')
->authorize(fn () => auth()->user()->can(Permission::ACTION_CONTROL_RESTART, $server))
->visible(fn () => $status->isRestartable())
->dispatch('powerAction', ['server' => $server, 'action' => 'restart'])
->icon('tabler-refresh'),
Action::make('stop')
->color('danger')
->authorize(fn () => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server))
->visible(fn () => $status->isStoppable())
->dispatch('powerAction', ['server' => $server, 'action' => 'stop'])
->icon('tabler-player-stop-filled'),
Action::make('kill')
->color('danger')
->tooltip('This can result in data corruption and/or data loss!')
->dispatch('powerAction', ['server' => $server, 'action' => 'kill'])
->authorize(fn () => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server))
->visible(fn () => $status->isKillable())
->icon('tabler-alert-square'),
];
};
$viewOne = [
ContextMenuTextColumn::make('condition')
->label('')
->default('unknown')
->wrap()
/** @return Column[] */
protected function tableColumns(): array
{
return [
TextColumn::make('condition')
->label('Status')
->badge()
->alignCenter()
->tooltip(fn (Server $server) => $server->formatResource('uptime', type: ServerResourceType::Time))
->icon(fn (Server $server) => $server->condition->getIcon())
->color(fn (Server $server) => $server->condition->getColor())
->contextMenuActions($menuOptions)
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
];
$viewTwo = [
ContextMenuTextColumn::make('name')
->label('')
->size('md')
->searchable()
->contextMenuActions($menuOptions)
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
ContextMenuTextColumn::make('allocation.address')
->color(fn (Server $server) => $server->condition->getColor()),
TextColumn::make('name')
->label('Server')
->description(fn (Server $server) => $server->description)
->grow()
->searchable(),
TextColumn::make('allocation.address')
->label('')
->badge()
->copyable(request()->isSecure())
->contextMenuActions($menuOptions)
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
];
$viewThree = [
->visibleFrom('md')
->copyable(request()->isSecure()),
TextColumn::make('cpuUsage')
->label('')
->label('Resources')
->icon('tabler-cpu')
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('cpu', limit: true, type: ServerResourceType::Percentage, precision: 0))
->state(fn (Server $server) => $server->formatResource('cpu_absolute', type: ServerResourceType::Percentage))
->color(fn (Server $server) => $this->getResourceColor($server, 'cpu')),
TextColumn::make('memoryUsage')
->label('')
->icon('tabler-memory')
->icon('tabler-device-desktop-analytics')
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('memory', limit: true))
->state(fn (Server $server) => $server->formatResource('memory_bytes'))
->color(fn (Server $server) => $this->getResourceColor($server, 'memory')),
TextColumn::make('diskUsage')
->label('')
->icon('tabler-device-floppy')
->icon('tabler-device-sd-card')
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('disk', limit: true))
->state(fn (Server $server) => $server->formatResource('disk_bytes'))
->color(fn (Server $server) => $this->getResourceColor($server, 'disk')),
];
}
public function table(Table $table): Table
{
$baseQuery = auth()->user()->accessibleServers();
$usingGrid = (auth()->user()->getCustomization()['dashboard_layout'] ?? 'grid') === 'grid';
return $table
->paginated(false)
->query(fn () => $baseQuery)
->poll('15s')
->columns(
(auth()->user()->getCustomization()['dashboard_layout'] ?? 'grid') === 'grid'
? [
Stack::make([
ServerEntryColumn::make('server_entry')
->searchable(['name']),
]),
]
: [
ColumnGroup::make('Status')
->label('Status')
->columns($viewOne),
ColumnGroup::make('Server')
->label('Servers')
->columns($viewTwo),
ColumnGroup::make('Resources')
->label('Resources')
->columns($viewThree),
]
)
->recordUrl(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
->contentGrid([
'default' => 1,
'md' => 2,
])
->columns($usingGrid ? $this->gridColumns() : $this->tableColumns())
->recordUrl(!$usingGrid ? (fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)) : null)
->actions(!$usingGrid ? ActionGroup::make(static::getPowerActions()) : [])
->actionsAlignment(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!')
@ -195,36 +148,33 @@ class ListServers extends ListRecords
];
}
public function getResourceColor(Server $server, string $resource): ?string
protected function getResourceColor(Server $server, string $resource): ?string
{
$current = null;
$limit = null;
switch ($resource) {
case 'cpu':
$current = $server->resources()['cpu_absolute'] ?? 0;
$current = $server->retrieveResources()['cpu_absolute'] ?? 0;
$limit = $server->cpu;
if ($server->cpu === 0) {
return null;
}
break;
case 'memory':
$current = $server->resources()['memory_bytes'] ?? 0;
$current = $server->retrieveResources()['memory_bytes'] ?? 0;
$limit = $server->memory * 2 ** 20;
if ($server->memory === 0) {
return null;
}
break;
case 'disk':
$current = $server->resources()['disk_bytes'] ?? 0;
$current = $server->retrieveResources()['disk_bytes'] ?? 0;
$limit = $server->disk * 2 ** 20;
if ($server->disk === 0) {
return null;
}
break;
default:
return null;
}
@ -238,7 +188,6 @@ class ListServers extends ListRecords
}
return null;
}
#[On('powerAction')]
@ -253,6 +202,8 @@ class ListServers extends ListRecords
->success()
->send();
cache()->forget("servers.$server->uuid.status");
$this->redirect(self::getUrl(['activeTab' => $this->activeTab]));
} catch (ConnectionException) {
Notification::make()
@ -261,4 +212,36 @@ class ListServers extends ListRecords
->send();
}
}
/** @return Action[] */
public static function getPowerActions(): array
{
return [
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']),
];
}
}

View File

@ -32,6 +32,7 @@ use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Pages\Auth\EditProfile as BaseEditProfile;
use Filament\Support\Colors\Color;
@ -405,30 +406,38 @@ class EditProfile extends BaseEditProfile
})
->reactive()
->default('monospace')
->afterStateUpdated(fn ($state, callable $set) => $set('font_preview', $state)),
->afterStateUpdated(fn ($state, Set $set) => $set('font_preview', $state)),
Placeholder::make('font_preview')
->label(trans('profile.font_preview'))
->columnSpan(2)
->content(function (Get $get) {
$fontName = $get('console_font') ?? 'monospace';
$fontSize = $get('console_font_size') . 'px';
$fontUrl = asset("storage/fonts/{$fontName}.ttf");
$style = <<<CSS
.preview-text {
font-family: $fontName;
font-size: $fontSize;
margin-top: 10px;
display: block;
}
CSS;
if ($fontName !== 'monospace') {
$fontUrl = asset("storage/fonts/$fontName.ttf");
$style = <<<CSS
@font-face {
font-family: $fontName;
src: url("$fontUrl");
}
$style
CSS;
}
return new HtmlString(<<<HTML
<style>
@font-face {
font-family: "CustomPreviewFont";
src: url("$fontUrl");
}
.preview-text {
font-family: "CustomPreviewFont";
font-size: $fontSize;
margin-top: 10px;
display: block;
}
</style>
<span class="preview-text">The quick blue pelican jumps over the lazy pterodactyl. :)</span>
HTML);
<style>
{$style}
</style>
<span class="preview-text">The quick blue pelican jumps over the lazy pterodactyl. :)</span>
HTML);
}),
TextInput::make('console_graph_period')
->label(trans('profile.graph_period'))

View File

@ -14,6 +14,7 @@ use App\Repositories\Daemon\DaemonBackupRepository;
use App\Services\Backups\DownloadLinkService;
use App\Filament\Components\Tables\Columns\BytesColumn;
use App\Filament\Components\Tables\Columns\DateTimeColumn;
use App\Services\Backups\DeleteBackupService;
use Filament\Facades\Filament;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Placeholder;
@ -30,6 +31,7 @@ use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Request;
class BackupResource extends Resource
@ -142,7 +144,7 @@ class BackupResource extends Resource
->send();
}
if (!$backup->is_successful && is_null($backup->completed_at)) { //TODO Change to Notifications
if (!$backup->is_successful && is_null($backup->completed_at)) {
return Notification::make()
->danger()
->title('Backup Restore Failed')
@ -175,9 +177,26 @@ class BackupResource extends Resource
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
DeleteAction::make('delete')
->disabled(fn (Backup $backup) => $backup->is_locked)
->modalDescription(fn (Backup $backup) => 'Do you wish to delete, ' . $backup->name . '?')
->modalDescription(fn (Backup $backup) => 'Do you wish to delete ' . $backup->name . '?')
->modalSubmitActionLabel('Delete Backup')
->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->delete($request, $server, $backup))
->action(function (Backup $backup, DeleteBackupService $deleteBackupService) {
try {
$deleteBackupService->handle($backup);
} catch (ConnectionException) {
Notification::make()
->title('Could not delete backup')
->body('Connection to node failed')
->danger()
->send();
return;
}
Activity::event('server:backup.delete')
->subject($backup)
->property(['name' => $backup->name, 'failed' => !$backup->is_successful])
->log();
})
->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress),
]),
]);

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Client\Servers;
use App\Facades\Activity;
use App\Http\Controllers\Api\Client\ClientApiController;
use App\Http\Requests\Api\Client\Servers\Settings\DescriptionServerRequest;
use App\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest;
use App\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest;
use App\Http\Requests\Api\Client\Servers\Settings\SetDockerImageRequest;
@ -34,24 +35,36 @@ class SettingsController extends ClientApiController
public function rename(RenameServerRequest $request, Server $server): JsonResponse
{
$name = $request->input('name');
$description = $request->has('description') ? (string) $request->input('description') : $server->description;
if ($server->name !== $name) {
$server->update(['name' => $name]);
if ($server->wasChanged('name')) {
Activity::event('server:settings.rename')
->property(['old' => $server->name, 'new' => $name])
->property(['old' => $server->getOriginal('name'), 'new' => $name])
->log();
$server->name = $name;
}
if ($server->description !== $description && config('panel.editable_server_descriptions')) {
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
* Update server description
*/
public function description(DescriptionServerRequest $request, Server $server): JsonResponse
{
if (!config('panel.editable_server_descriptions')) {
return new JsonResponse([], Response::HTTP_FORBIDDEN);
}
$description = $request->input('description');
$server->update(['description' => $description ?? '']);
if ($server->wasChanged('description')) {
Activity::event('server:settings.description')
->property(['old' => $server->description, 'new' => $description])
->property(['old' => $server->getOriginal('description'), 'new' => $description])
->log();
$server->description = $description;
}
$server->save();
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
@ -80,7 +93,7 @@ class SettingsController extends ClientApiController
*/
public function dockerImage(SetDockerImageRequest $request, Server $server): JsonResponse
{
if (!in_array($server->image, array_values($server->egg->docker_images))) {
if (!in_array($server->image, $server->egg->docker_images)) {
throw new BadRequestHttpException('This server\'s Docker image has been manually set by an administrator and cannot be updated.');
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Api\Client\Servers\Settings;
use App\Contracts\Http\ClientPermissionsRequest;
use App\Http\Requests\Api\Client\ClientApiRequest;
use App\Models\Permission;
class DescriptionServerRequest extends ClientApiRequest implements ClientPermissionsRequest
{
/**
* Returns the permissions string indicating which permission should be used to
* validate that the authenticated user has permission to perform this action against
* the given resource (server).
*/
public function permission(): string
{
return Permission::ACTION_SETTINGS_DESCRIPTION;
}
/**
* The rules to apply when validating this request.
*/
public function rules(): array
{
return [
'description' => 'string|nullable',
];
}
}

View File

@ -26,7 +26,6 @@ class RenameServerRequest extends ClientApiRequest implements ClientPermissionsR
{
return [
'name' => Server::getRules()['name'],
'description' => 'string|nullable',
];
}
}

View File

@ -333,26 +333,6 @@ class Node extends Model implements Validatable
});
}
/**
* @return array<array-key, mixed>
*/
public function serverStatuses(): array
{
$statuses = [];
try {
$statuses = Http::daemon($this)->connectTimeout(1)->timeout(1)->get('/api/servers')->json() ?? [];
} catch (Exception $exception) {
report($exception);
}
foreach ($statuses as $status) {
$uuid = fluent($status)->get('configuration.uuid');
cache()->remember("servers.$uuid.container.status", now()->addMinute(), fn () => fluent($status)->get('state'));
}
return $statuses;
}
/** @return array{
* memory_total: int, memory_used: int,
* swap_total: int, swap_used: int,

View File

@ -97,6 +97,8 @@ class Permission extends Model implements Validatable
public const ACTION_SETTINGS_RENAME = 'settings.rename';
public const ACTION_SETTINGS_DESCRIPTION = 'settings.description';
public const ACTION_SETTINGS_REINSTALL = 'settings.reinstall';
public const ACTION_ACTIVITY_READ = 'activity.read';
@ -176,7 +178,7 @@ class Permission extends Model implements Validatable
[
'name' => 'settings',
'icon' => 'tabler-settings',
'permissions' => ['rename', 'reinstall'],
'permissions' => ['rename', 'description', 'reinstall'],
],
[
'name' => 'activity',

View File

@ -435,25 +435,24 @@ class Server extends Model implements Validatable
public function retrieveStatus(): ContainerStatus
{
$status = cache()->get("servers.$this->uuid.container.status");
return cache()->remember("servers.$this->uuid.status", now()->addSeconds(15), function () {
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
$details = app(DaemonServerRepository::class)->setServer($this)->getDetails();
if ($status === null) {
$this->node->serverStatuses();
$status = cache()->get("servers.$this->uuid.container.status");
}
return ContainerStatus::tryFrom($status) ?? ContainerStatus::Missing;
return ContainerStatus::tryFrom(Arr::get($details, 'state')) ?? ContainerStatus::Missing;
});
}
/**
* @return array<mixed>
*/
public function resources(): array
public function retrieveResources(): array
{
return cache()->remember("resources:$this->uuid", now()->addSeconds(15), function () {
return cache()->remember("servers.$this->uuid.resources", now()->addSeconds(15), function () {
// @phpstan-ignore myCustomRules.forbiddenGlobalFunctions
return Arr::get(app(DaemonServerRepository::class)->setServer($this)->getDetails(), 'utilization', []);
$details = app(DaemonServerRepository::class)->setServer($this)->getDetails();
return Arr::get($details, 'utilization', []);
});
}
@ -461,7 +460,7 @@ class Server extends Model implements Validatable
{
$resourceAmount = $this->{$resourceKey} ?? 0;
if (!$limit) {
$resourceAmount = $this->resources()[$resourceKey] ?? 0;
$resourceAmount = $this->retrieveResources()[$resourceKey] ?? 0;
}
if ($type === ServerResourceType::Time) {
@ -494,7 +493,7 @@ class Server extends Model implements Validatable
public function condition(): Attribute
{
return Attribute::make(
get: fn () => $this->isSuspended() ? ServerState::Suspended : $this->status ?? $this->retrieveStatus(),
get: fn () => $this->status ?? $this->retrieveStatus(),
);
}
}

View File

@ -64,7 +64,7 @@ class ServerPanelProvider extends PanelProvider
->navigationItems([
NavigationItem::make('Open in Admin')
->url(fn () => EditServer::getUrl(['record' => Filament::getTenant()], panel: 'admin'))
->visible(fn () => auth()->user()->can('view server', Filament::getTenant()))
->visible(fn () => auth()->user()->canAccessPanel(Filament::getPanel('admin')) && auth()->user()->can('view server', Filament::getTenant()))
->icon('tabler-arrow-back')
->sort(99),
])

View File

@ -6,12 +6,11 @@ use App\Extensions\Filesystem\S3Filesystem;
use Aws\S3\S3Client;
use Illuminate\Http\Response;
use App\Models\Backup;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Database\ConnectionInterface;
use App\Extensions\Backups\BackupManager;
use App\Repositories\Daemon\DaemonBackupRepository;
use App\Exceptions\Service\Backup\BackupLockedException;
use Illuminate\Http\Client\ConnectionException;
use Exception;
class DeleteBackupService
{
@ -48,12 +47,10 @@ class DeleteBackupService
$this->connection->transaction(function () use ($backup) {
try {
$this->daemonBackupRepository->setServer($backup->server)->delete($backup);
} catch (ConnectionException $exception) {
$previous = $exception->getPrevious();
} catch (Exception $exception) {
// Don't fail the request if the Daemon responds with a 404, just assume the backup
// doesn't actually exist and remove its reference from the Panel as well.
if (!$previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) {
if ($exception->getCode() !== Response::HTTP_NOT_FOUND) {
throw $exception;
}
}

View File

@ -10,15 +10,14 @@
"ext-pdo": "*",
"ext-zip": "*",
"abdelhamiderrahmouni/filament-monaco-editor": "^0.2.5",
"aws/aws-sdk-php": "^3.343",
"aymanalhattami/filament-context-menu": "^1.0",
"aws/aws-sdk-php": "^3.344",
"calebporzio/sushi": "^2.5",
"chillerlan/php-qrcode": "^5.0.2",
"dedoc/scramble": "^0.12.10",
"doctrine/dbal": "~3.6.0",
"filament/filament": "^3.3",
"guzzlehttp/guzzle": "^7.9",
"laravel/framework": "^12.17",
"laravel/framework": "^12.18",
"laravel/helpers": "^1.7",
"laravel/sanctum": "^4.1",
"laravel/socialite": "^5.21",

219
composer.lock generated
View File

@ -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": "9a250ae6162d7d877649172284b56c13",
"content-hash": "a006241b5687d547b51a60e6ac50ccae",
"packages": [
{
"name": "abdelhamiderrahmouni/filament-monaco-editor",
@ -1020,16 +1020,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.343.23",
"version": "3.344.3",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "010869992129557cfbf2740d94d82ef3b5228462"
"reference": "0cf789243c7de82be7d3f7641cab37b5bb5d937d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/010869992129557cfbf2740d94d82ef3b5228462",
"reference": "010869992129557cfbf2740d94d82ef3b5228462",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0cf789243c7de82be7d3f7641cab37b5bb5d937d",
"reference": "0cf789243c7de82be7d3f7641cab37b5bb5d937d",
"shasum": ""
},
"require": {
@ -1111,86 +1111,10 @@
"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.343.23"
"source": "https://github.com/aws/aws-sdk-php/tree/3.344.3"
},
"time": "2025-06-02T18:04:47+00:00"
},
{
"name": "aymanalhattami/filament-context-menu",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/aymanalhattami/filament-context-menu.git",
"reference": "5118d36303e86891d3037e6e26882d548b880b9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aymanalhattami/filament-context-menu/zipball/5118d36303e86891d3037e6e26882d548b880b9c",
"reference": "5118d36303e86891d3037e6e26882d548b880b9c",
"shasum": ""
},
"require": {
"filament/filament": "^3.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.15.0"
},
"require-dev": {
"larastan/larastan": "^2.0",
"laravel/pint": "^1.0",
"nunomaduro/collision": "^8.0",
"orchestra/testbench": "^9.0",
"pestphp/pest": "^3.0",
"pestphp/pest-plugin-arch": "^3.0",
"pestphp/pest-plugin-laravel": "^3.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Skeleton": "AymanAlhattami\\FilamentContextMenu\\Facades\\FilamentContextMenu"
},
"providers": [
"AymanAlhattami\\FilamentContextMenu\\FilamentContextMenuServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"AymanAlhattami\\FilamentContextMenu\\": "src/",
"AymanAlhattami\\FilamentContextMenu\\Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ayman Alhattami",
"email": "ayman.m.alhattami@gmail.com",
"role": "Developer"
}
],
"description": "context menu (right click menu) for filament",
"homepage": "https://github.com/aymanalhattami/filament-context-menu",
"keywords": [
"ayman alhattami",
"context menu",
"filament",
"filament admin panel",
"filament context menu",
"filament_context_menu",
"laravel"
],
"support": {
"issues": "https://github.com/aymanalhattami/filament-context-menu/issues",
"source": "https://github.com/aymanalhattami/filament-context-menu"
},
"time": "2024-09-22T10:47:31+00:00"
},
{
"name": "blade-ui-kit/blade-heroicons",
"version": "2.6.0",
@ -2634,7 +2558,7 @@
},
{
"name": "filament/actions",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/actions.git",
@ -2687,16 +2611,16 @@
},
{
"name": "filament/filament",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/panels.git",
"reference": "94ee92244d2a64666fb8c1ea50cb7315ebceb63b"
"reference": "8d915ef313835f46f49175396de82feb0166d8a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/panels/zipball/94ee92244d2a64666fb8c1ea50cb7315ebceb63b",
"reference": "94ee92244d2a64666fb8c1ea50cb7315ebceb63b",
"url": "https://api.github.com/repos/filamentphp/panels/zipball/8d915ef313835f46f49175396de82feb0166d8a8",
"reference": "8d915ef313835f46f49175396de82feb0166d8a8",
"shasum": ""
},
"require": {
@ -2748,20 +2672,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-05-27T18:46:23+00:00"
"time": "2025-06-10T16:10:42+00:00"
},
{
"name": "filament/forms",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/forms.git",
"reference": "d73cdda057a4f5bd409eab9573101e73edb404cc"
"reference": "014dd23a7691dc25bb037f26df852cfec5602d01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/forms/zipball/d73cdda057a4f5bd409eab9573101e73edb404cc",
"reference": "d73cdda057a4f5bd409eab9573101e73edb404cc",
"url": "https://api.github.com/repos/filamentphp/forms/zipball/014dd23a7691dc25bb037f26df852cfec5602d01",
"reference": "014dd23a7691dc25bb037f26df852cfec5602d01",
"shasum": ""
},
"require": {
@ -2804,11 +2728,11 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-06-03T13:40:37+00:00"
"time": "2025-06-10T16:10:45+00:00"
},
{
"name": "filament/infolists",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/infolists.git",
@ -2859,7 +2783,7 @@
},
{
"name": "filament/notifications",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/notifications.git",
@ -2911,16 +2835,16 @@
},
{
"name": "filament/support",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/support.git",
"reference": "4f9793ad3339301ca53ea6f2c984734f7ac38ce7"
"reference": "5c140580d7feeabb4d2b0007c854676ae87be1b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/support/zipball/4f9793ad3339301ca53ea6f2c984734f7ac38ce7",
"reference": "4f9793ad3339301ca53ea6f2c984734f7ac38ce7",
"url": "https://api.github.com/repos/filamentphp/support/zipball/5c140580d7feeabb4d2b0007c854676ae87be1b3",
"reference": "5c140580d7feeabb4d2b0007c854676ae87be1b3",
"shasum": ""
},
"require": {
@ -2966,20 +2890,20 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-06-03T06:16:13+00:00"
"time": "2025-06-10T16:10:55+00:00"
},
{
"name": "filament/tables",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/tables.git",
"reference": "1a107a8411549297b97d1142b1f7a5fa7a65e32b"
"reference": "3d52c23443f6846774a6a2ce60f6e6173ce20943"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filamentphp/tables/zipball/1a107a8411549297b97d1142b1f7a5fa7a65e32b",
"reference": "1a107a8411549297b97d1142b1f7a5fa7a65e32b",
"url": "https://api.github.com/repos/filamentphp/tables/zipball/3d52c23443f6846774a6a2ce60f6e6173ce20943",
"reference": "3d52c23443f6846774a6a2ce60f6e6173ce20943",
"shasum": ""
},
"require": {
@ -3018,11 +2942,11 @@
"issues": "https://github.com/filamentphp/filament/issues",
"source": "https://github.com/filamentphp/filament"
},
"time": "2025-06-02T09:43:47+00:00"
"time": "2025-06-10T16:10:40+00:00"
},
{
"name": "filament/widgets",
"version": "v3.3.20",
"version": "v3.3.21",
"source": {
"type": "git",
"url": "https://github.com/filamentphp/widgets.git",
@ -3794,16 +3718,16 @@
},
{
"name": "laravel/framework",
"version": "v12.17.0",
"version": "v12.18.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "8729d084510480fdeec9b6ad198180147d4a7f06"
"reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/8729d084510480fdeec9b6ad198180147d4a7f06",
"reference": "8729d084510480fdeec9b6ad198180147d4a7f06",
"url": "https://api.github.com/repos/laravel/framework/zipball/7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d",
"reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d",
"shasum": ""
},
"require": {
@ -4005,7 +3929,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-06-03T14:04:18+00:00"
"time": "2025-06-10T14:48:34+00:00"
},
{
"name": "laravel/helpers",
@ -6461,16 +6385,16 @@
},
{
"name": "phpdocumentor/reflection",
"version": "6.2.1",
"version": "6.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/Reflection.git",
"reference": "ab969af5e53c3bb400eea958b90b87ecc34f4dff"
"reference": "d91b3270832785602adcc24ae2d0974ba99a8ff8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/ab969af5e53c3bb400eea958b90b87ecc34f4dff",
"reference": "ab969af5e53c3bb400eea958b90b87ecc34f4dff",
"url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/d91b3270832785602adcc24ae2d0974ba99a8ff8",
"reference": "d91b3270832785602adcc24ae2d0974ba99a8ff8",
"shasum": ""
},
"require": {
@ -6527,9 +6451,9 @@
],
"support": {
"issues": "https://github.com/phpDocumentor/Reflection/issues",
"source": "https://github.com/phpDocumentor/Reflection/tree/6.2.1"
"source": "https://github.com/phpDocumentor/Reflection/tree/6.3.0"
},
"time": "2025-05-30T18:42:55+00:00"
"time": "2025-06-06T13:39:18+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@ -7998,16 +7922,16 @@
},
{
"name": "secondnetwork/blade-tabler-icons",
"version": "v3.33.0",
"version": "v3.34.0",
"source": {
"type": "git",
"url": "https://github.com/secondnetwork/blade-tabler-icons.git",
"reference": "523b3ede493ce9f8cbe3a5bdce21769976a80b28"
"reference": "e48c0a5a53798d42c7beff760de8cbc7dbbccff4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/secondnetwork/blade-tabler-icons/zipball/523b3ede493ce9f8cbe3a5bdce21769976a80b28",
"reference": "523b3ede493ce9f8cbe3a5bdce21769976a80b28",
"url": "https://api.github.com/repos/secondnetwork/blade-tabler-icons/zipball/e48c0a5a53798d42c7beff760de8cbc7dbbccff4",
"reference": "e48c0a5a53798d42c7beff760de8cbc7dbbccff4",
"shasum": ""
},
"require": {
@ -8050,9 +7974,9 @@
],
"support": {
"issues": "https://github.com/secondnetwork/blade-tabler-icons/issues",
"source": "https://github.com/secondnetwork/blade-tabler-icons/tree/v3.33.0"
"source": "https://github.com/secondnetwork/blade-tabler-icons/tree/v3.34.0"
},
"time": "2025-05-17T06:28:48+00:00"
"time": "2025-06-09T08:41:55+00:00"
},
{
"name": "socialiteproviders/authentik",
@ -8701,16 +8625,16 @@
},
{
"name": "spatie/laravel-health",
"version": "1.34.2",
"version": "1.34.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-health.git",
"reference": "e7d9d6e0807a9de46f544c76d54badc19af36ddc"
"reference": "d421bc223c7a8c872ad944706d98a74b1056f761"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-health/zipball/e7d9d6e0807a9de46f544c76d54badc19af36ddc",
"reference": "e7d9d6e0807a9de46f544c76d54badc19af36ddc",
"url": "https://api.github.com/repos/spatie/laravel-health/zipball/d421bc223c7a8c872ad944706d98a74b1056f761",
"reference": "d421bc223c7a8c872ad944706d98a74b1056f761",
"shasum": ""
},
"require": {
@ -8782,7 +8706,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-health/tree/1.34.2"
"source": "https://github.com/spatie/laravel-health/tree/1.34.3"
},
"funding": [
{
@ -8790,7 +8714,7 @@
"type": "github"
}
],
"time": "2025-05-20T08:31:02+00:00"
"time": "2025-06-04T22:04:19+00:00"
},
{
"name": "spatie/laravel-package-tools",
@ -12744,16 +12668,16 @@
},
{
"name": "filp/whoops",
"version": "2.18.0",
"version": "2.18.1",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e"
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e",
"url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
"reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26",
"shasum": ""
},
"require": {
@ -12803,7 +12727,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.18.0"
"source": "https://github.com/filp/whoops/tree/2.18.1"
},
"funding": [
{
@ -12811,7 +12735,7 @@
"type": "github"
}
],
"time": "2025-03-15T12:00:00+00:00"
"time": "2025-06-03T18:56:14+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -12967,16 +12891,16 @@
},
{
"name": "larastan/larastan",
"version": "v3.4.0",
"version": "v3.4.1",
"source": {
"type": "git",
"url": "https://github.com/larastan/larastan.git",
"reference": "1042fa0c2ee490bb6da7381f3323f7292ad68222"
"reference": "dc20d24871d5a2138b292b0430d94d18da3dbc53"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/larastan/larastan/zipball/1042fa0c2ee490bb6da7381f3323f7292ad68222",
"reference": "1042fa0c2ee490bb6da7381f3323f7292ad68222",
"url": "https://api.github.com/repos/larastan/larastan/zipball/dc20d24871d5a2138b292b0430d94d18da3dbc53",
"reference": "dc20d24871d5a2138b292b0430d94d18da3dbc53",
"shasum": ""
},
"require": {
@ -13044,7 +12968,7 @@
],
"support": {
"issues": "https://github.com/larastan/larastan/issues",
"source": "https://github.com/larastan/larastan/tree/v3.4.0"
"source": "https://github.com/larastan/larastan/tree/v3.4.1"
},
"funding": [
{
@ -13052,20 +12976,20 @@
"type": "github"
}
],
"time": "2025-04-22T09:44:59+00:00"
"time": "2025-06-09T21:23:36+00:00"
},
{
"name": "laravel/pail",
"version": "v1.2.2",
"version": "v1.2.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/pail.git",
"reference": "f31f4980f52be17c4667f3eafe034e6826787db2"
"reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pail/zipball/f31f4980f52be17c4667f3eafe034e6826787db2",
"reference": "f31f4980f52be17c4667f3eafe034e6826787db2",
"url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a",
"reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a",
"shasum": ""
},
"require": {
@ -13085,7 +13009,7 @@
"orchestra/testbench-core": "^8.13|^9.0|^10.0",
"pestphp/pest": "^2.20|^3.0",
"pestphp/pest-plugin-type-coverage": "^2.3|^3.0",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan": "^1.12.27",
"symfony/var-dumper": "^6.3|^7.0"
},
"type": "library",
@ -13121,6 +13045,7 @@
"description": "Easily delve into your Laravel application's log files directly from the command line.",
"homepage": "https://github.com/laravel/pail",
"keywords": [
"dev",
"laravel",
"logs",
"php",
@ -13130,7 +13055,7 @@
"issues": "https://github.com/laravel/pail/issues",
"source": "https://github.com/laravel/pail"
},
"time": "2025-01-28T15:15:15+00:00"
"time": "2025-06-05T13:55:57+00:00"
},
{
"name": "laravel/pint",

View File

@ -4,7 +4,7 @@
"version": "PLCN_v1",
"update_url": "https:\/\/github.com\/pelican-dev\/panel\/raw\/main\/database\/Seeders\/eggs\/rust\/egg-rust.json"
},
"exported_at": "2025-03-18T12:36:48+00:00",
"exported_at": "2025-06-06T11:57:17+00:00",
"name": "Rust",
"author": "panel@example.com",
"uuid": "bace2dfb-209c-452a-9459-7d6f340b07ae",
@ -20,7 +20,7 @@
"ghcr.io\/parkervcp\/games:rust": "ghcr.io\/parkervcp\/games:rust"
},
"file_denylist": [],
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
"startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.queryport {{QUERY_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{SERVER_HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}",
"config": {
"files": "{}",
"startup": "{\r\n \"done\": \"Server startup complete\"\r\n}",
@ -38,7 +38,7 @@
{
"name": "Server Name",
"description": "The name of your server in the public server list.",
"env_variable": "HOSTNAME",
"env_variable": "SERVER_HOSTNAME",
"default_value": "A Rust Server",
"user_viewable": true,
"user_editable": true,

View File

@ -16,7 +16,8 @@ return [
'startup_update' => 'Allows a user to modify the startup variables for the server.',
'startup_docker_image' => 'Allows a user to modify the Docker image used when running the server.',
'settings_reinstall' => 'Allows a user to trigger a reinstall of this server.',
'settings_rename' => 'Allows a user to rename this server and change the description of it.',
'settings_rename' => 'Allows a user to rename this server.',
'settings_description' => 'Allows a user to change the description of this server.',
'activity_read' => 'Allows a user to view the activity logs for the server.',
'websocket_*' => 'Allows a user access to the websocket for this server.',
'control_console' => 'Allows a user to send data to the server console.',

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
$userFontSize = auth()->user()->getCustomization()['console_font_size'] ?? 14;
$userRows = auth()->user()->getCustomization()['console_rows'] ?? 30;
@endphp
@if($userFont)
@if($userFont !== "monospace")
<link rel="preload" href="{{ asset("storage/fonts/{$userFont}.ttf") }}" as="font" crossorigin>
<style>
@font-face {

View File

@ -1,47 +1,55 @@
<div class="relative">
<div
class="absolute left-0 top-1 bottom-0 w-1 rounded-lg"
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 items-center mb-5 gap-2">
<x-filament::icon-button
:icon="$server->condition->getIcon()"
:color="$server->condition->getColor()"
:tooltip="$server->condition->getLabel()"
size="xl"
/>
<h2 class="text-xl font-bold">
{{ $server->name }}
<span class="dark:text-gray-400">({{ $server->formatResource('uptime', type: \App\Enums\ServerResourceType::Time) }})</span>
</h2>
<div wire:poll.15s class="relative">
<a href="{{ \App\Filament\Server\Pages\Console::getUrl(panel: 'server', tenant: $server) }}" wire:navigate>
<div
class="absolute left-0 top-1 bottom-0 w-1 rounded-lg"
style="background-color: {{ $server->condition->getColor(true) }};">
</div>
<div class="flex justify-between text-center">
<div>
<p class="text-sm dark:text-gray-400">CPU</p>
<p class="text-md font-semibold">{{ $server->formatResource('cpu_absolute', type: \App\Enums\ServerResourceType::Percentage) }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('cpu', type: \App\Enums\ServerResourceType::Percentage, limit: true) }}</p>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
<div class="flex items-center mb-5 gap-2">
<x-filament::icon-button
:icon="$server->condition->getIcon()"
:color="$server->condition->getColor()"
:tooltip="$server->condition->getLabel()"
size="xl"
/>
<h2 class="text-xl font-bold">
{{ $server->name }}
<span class="dark:text-gray-400">({{ $server->formatResource('uptime', type: \App\Enums\ServerResourceType::Time) }})</span>
</h2>
</div>
<div>
<p class="text-sm dark:text-gray-400">Memory</p>
<p class="text-md font-semibold">{{ $server->formatResource('memory_bytes') }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('memory', limit: true) }}</p>
</div>
<div>
<p class="text-sm dark:text-gray-400">Disk</p>
<p class="text-md font-semibold">{{ $server->formatResource('disk_bytes') }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('disk', limit: true) }}</p>
</div>
<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>
<div class="flex justify-between text-center">
<div>
<p class="text-sm dark:text-gray-400">CPU</p>
<p class="text-md font-semibold">{{ $server->formatResource('cpu_absolute', type: \App\Enums\ServerResourceType::Percentage) }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('cpu', type: \App\Enums\ServerResourceType::Percentage, limit: true) }}</p>
</div>
<div>
<p class="text-sm dark:text-gray-400">Memory</p>
<p class="text-md font-semibold">{{ $server->formatResource('memory_bytes') }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('memory', limit: true) }}</p>
</div>
<div>
<p class="text-sm dark:text-gray-400">Disk</p>
<p class="text-md font-semibold">{{ $server->formatResource('disk_bytes') }}</p>
<hr class="p-0.5">
<p class="text-xs dark:text-gray-400">{{ $server->formatResource('disk', limit: true) }}</p>
</div>
<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>
</div>
</div>
</div>
</div>
</a>
<x-filament-tables::actions
:actions="\App\Filament\App\Resources\ServerResource\Pages\ListServers::getPowerActions()"
:alignment="\Filament\Support\Enums\Alignment::Center"
:record="$server"
/>
</div>

View File

@ -129,6 +129,7 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
Route::prefix('/settings')->group(function () {
Route::post('/rename', [Client\Servers\SettingsController::class, 'rename']);
Route::post('/description', [Client\Servers\SettingsController::class, 'description']);
Route::post('/reinstall', [Client\Servers\SettingsController::class, 'reinstall']);
Route::put('/docker-image', [Client\Servers\SettingsController::class, 'dockerImage']);
});

View File

@ -21,11 +21,9 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase
/** @var \App\Models\Server $server */
[$user, $server] = $this->generateTestAccount($permissions);
$originalName = $server->name;
$originalDescription = $server->description;
$response = $this->actingAs($user)->postJson("/api/client/servers/$server->uuid/settings/rename", [
'name' => '',
'description' => '',
]);
$response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
@ -33,18 +31,15 @@ class SettingsControllerTest extends ClientApiIntegrationTestCase
$server = $server->refresh();
$this->assertSame($originalName, $server->name);
$this->assertSame($originalDescription, $server->description);
$this->actingAs($user)
->postJson("/api/client/servers/$server->uuid/settings/rename", [
'name' => 'Test Server Name',
'description' => 'This is a test server.',
])
->assertStatus(Response::HTTP_NO_CONTENT);
$server = $server->refresh();
$this->assertSame('Test Server Name', $server->name);
$this->assertSame('This is a test server.', $server->description);
}
/**

View File

@ -2,10 +2,8 @@
namespace App\Tests\Integration\Services\Backups;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use App\Models\Backup;
use GuzzleHttp\Exception\ClientException;
use App\Extensions\Backups\BackupManager;
use App\Extensions\Filesystem\S3Filesystem;
use App\Services\Backups\DeleteBackupService;
@ -54,7 +52,7 @@ class DeleteBackupServiceTest extends IntegrationTestCase
$backup = Backup::factory()->create(['server_id' => $server->id]);
$mock = $this->mock(DaemonBackupRepository::class);
$mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(previous: new ClientException('', new Request('DELETE', '/'), new Response(404))));
$mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(code: 404));
$this->app->make(DeleteBackupService::class)->handle($backup);
@ -69,7 +67,7 @@ class DeleteBackupServiceTest extends IntegrationTestCase
$backup = Backup::factory()->create(['server_id' => $server->id]);
$mock = $this->mock(DaemonBackupRepository::class);
$mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(previous: new ClientException('', new Request('DELETE', '/'), new Response(500))));
$mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(code: 500));
$this->expectException(ConnectionException::class);