Merge branch 'main' into boy132/allocation-marker

This commit is contained in:
Boy132 2025-11-03 18:45:56 +01:00
commit 67c0461fd8
80 changed files with 1311 additions and 500 deletions

View File

@ -18,7 +18,7 @@ enum CustomizationKey: string
self::ConsoleFont => 'monospace',
self::ConsoleFontSize => 14,
self::ConsoleGraphPeriod => 30,
self::TopNavigation => false,
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
self::DashboardLayout => 'grid',
};
}

View File

@ -56,8 +56,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
->default(fn () => $server->image)
->notIn(fn () => $server->image)
->required()
->preload()
->native(false),
->preload(),
])
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server) {
try {

View File

@ -4,6 +4,10 @@ namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use SocialiteProviders\Authentik\Provider;
final class AuthentikSchema extends OAuthSchema
@ -20,11 +24,27 @@ final class AuthentikSchema extends OAuthSchema
public function getServiceConfig(): array
{
return [
return array_merge(parent::getServiceConfig(), [
'base_url' => env('OAUTH_AUTHENTIK_BASE_URL'),
'client_id' => env('OAUTH_AUTHENTIK_CLIENT_ID'),
'client_secret' => env('OAUTH_AUTHENTIK_CLIENT_SECRET'),
];
]);
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Create Authentik Application')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>On your Authentik dashboard select <b>Applications</b>, then select <b>Create with Provider</b>.</p><p>On the creation step select <b>OAuth2/OpenID Provider</b> and on the configure step set <b>Redirect URIs/Origins</b> to the value below.</p>'))),
TextInput::make('_noenv_callback')
->label('Callback URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/authentik')),
]),
], parent::getSetupSteps());
}
public function getSettingsForm(): array

View File

@ -0,0 +1,45 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class BitbucketSchema extends OAuthSchema
{
public function getId(): string
{
return 'bitbucket';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Register new Bitbucket Consumer')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud" target="_blank">Bitbucket OAuth Documentation</x-filament::link> and follow the steps in <b>Create a consumer</b>.</p><p>For the <b>Callback URL</b> use the value below.</p>'))),
TextInput::make('_noenv_callback')
->label('Callback URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/bitbucket')),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-bitbucket-f';
}
public function getHexColor(): string
{
return '#205081';
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class FacebookSchema extends OAuthSchema
{
public function getId(): string
{
return 'facebook';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Register new Facebook Application')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://developers.facebook.com/apps" target="_blank">Facebook Developer Dashboard</x-filament::link> and select or create a new app you will use for authentication. Make sure to have "Authenticate and request data from users with Facebook Login" as one of the Use Cases.</p><p>Once selected go to <b>Use Cases</b> and customize "Authenticate and request data from users with Facebook Login", from there go to <b>Settings</b> and add <b>Valid OAuth Redirect URIs</b> using the value below.</p>'))),
TextInput::make('_noenv_callback')
->label('Valid OAuth Redirect URIs')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/facebook')),
TextEntry::make('get_app_info')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>To obtain the OAuth values go to <b>App Settings > Basic</b>.</p>'))),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-facebook-f';
}
public function getHexColor(): string
{
return '#1877f2';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class GoogleSchema extends OAuthSchema
{
public function getId(): string
{
return 'google';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Register new OAuth client')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://console.developers.google.com/" target="_blank">Google API Console</x-filament::link> and create or select the project you want to use.</p><p>Navigate or search <b>Credentials</b>, click on the <b>Create Credentials</b> button and select <b>OAuth client ID</b>. On the Application type select <b>Web Application</b>.</p><p>On <b>Authorized JavaScript origins</b> and <b>Authorized redirect URIs</b> add and use the values below.</p>'))),
TextInput::make('_noenv_origin')
->label('Authorized JavaScript origins')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('')),
TextInput::make('_noenv_callback')
->label('Authorized redirect URIs')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/google')),
TextEntry::make('register_application')
->hiddenLabel()
->state(new HtmlString('<p>When you filled all fields click on <b>Create</b>.</p>')),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-google-f';
}
public function getHexColor(): string
{
return '#4285f4';
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class LinkedinSchema extends OAuthSchema
{
public function getId(): string
{
return 'linkedin';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Obtain Linkedin App OAuth Config')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p><x-filament::link href="https://www.linkedin.com/developers/apps/new" target="_blank">Create</x-filament::link> or <x-filament::link href="https://www.linkedin.com/developers/apps" target="_blank">select</x-filament::link> the one you will be using for authentication.</p><p>Select the <b>Auth</b> tab and set <b>Authorized redirect URLs for your app</b> to the value below.</p>'))),
TextInput::make('_noenv_callback')
->label('Authorized redirect URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/linkedin')),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-linkedin-f';
}
public function getHexColor(): string
{
return '#0a66c2';
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class SlackSchema extends OAuthSchema
{
public function getId(): string
{
return 'slack';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Register new Slack OAuth')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p><x-filament::link href="https://api.slack.com/apps?new_app=1" target="_blank">Create</x-filament::link> a slack app or <x-filament::link href="https://api.slack.com/apps" target="_blank">select</x-filament::link> the one you will be using for authentication.</p><p>Navigate to the <b>OAuth & Permissions</b> section and configure the <b>Redirect URL</b> using the value below.</p>'))),
TextInput::make('_noenv_callback')
->label('Redirect URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/slack')),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-slack';
}
public function getHexColor(): string
{
return '#6ecadc';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
final class XSchema extends OAuthSchema
{
public function getId(): string
{
return 'x';
}
public function getSetupSteps(): array
{
return array_merge([
Step::make('Register new X App')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://developer.x.com/en/portal/dashboard" target="_blank">X Developer Dashboard</x-filament::link> and create or select the project app you want to use.</p><p>Go to the app\'s settings and set up <b>User authentication</b> if not yet. Make sure to select <b>Web App</b> as the type of app.</p><p>For the <b>Callback URI / Redirect URL</b> and <b>Website URL</b> set it using the value below.</p>'))),
TextInput::make('_noenv_origin')
->label('Website URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('')),
TextInput::make('_noenv_callback')
->label('Callback URI / Redirect URL')
->dehydrated()
->disabled()
->hintCopy()
->default(fn () => url('/auth/oauth/callback/x')),
TextEntry::make('register_application')
->hiddenLabel()
->state(new HtmlString('<p>If you have already set this up go to your app\'s <b>Keys and tokens</b> and obtain the Client ID and Secret there.</p>')),
]),
], parent::getSetupSteps());
}
public function getIcon(): string
{
return 'tabler-brand-x';
}
public function getHexColor(): string
{
return '#1da1f2';
}
}

View File

@ -181,7 +181,6 @@ class Settings extends Page implements HasSchemas
->schema([
Select::make('FILAMENT_AVATAR_PROVIDER')
->label(trans('admin/setting.general.avatar_provider'))
->native(false)
->options($this->avatarService->getMappings())
->selectablePlaceholder(false)
->default(env('FILAMENT_AVATAR_PROVIDER', config('panel.filament.avatar-provider'))),
@ -204,6 +203,15 @@ class Settings extends Page implements HasSchemas
])
->stateCast(new BooleanStateCast(false, true))
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
ToggleButtons::make('FILAMENT_DEFAULT_NAVIGATION')
->label(trans('admin/setting.general.default_navigation'))
->inline()
->options([
'sidebar' => trans('admin/setting.general.sidebar'),
'topbar' => trans('admin/setting.general.topbar'),
'mixed' => trans('admin/setting.general.mixed'),
])
->default(env('FILAMENT_DEFAULT_NAVIGATION', config('panel.filament.default-navigation'))),
ToggleButtons::make('APP_2FA_REQUIRED')
->label(trans('admin/setting.general.2fa_requirement'))
->inline()
@ -217,7 +225,6 @@ class Settings extends Page implements HasSchemas
->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))),
Select::make('FILAMENT_WIDTH')
->label(trans('admin/setting.general.display_width'))
->native(false)
->options(Width::class)
->selectablePlaceholder(false)
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),

View File

@ -257,7 +257,6 @@ class CreateEgg extends CreateRecord
->default('ghcr.io/pelican-eggs/installers:debian'),
Select::make('script_entry')
->label(trans('admin/egg.script_entry'))
->native(false)
->selectablePlaceholder(false)
->default('bash')
->options([

View File

@ -16,6 +16,7 @@ use Filament\Actions\ActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
@ -24,13 +25,19 @@ use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Flex;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Image;
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\IconSize;
use Illuminate\Validation\Rules\Unique;
class EditEgg extends EditRecord
@ -50,36 +57,215 @@ class EditEgg extends EditRecord
Tabs::make()->tabs([
Tab::make('configuration')
->label(trans('admin/egg.tabs.configuration'))
->columns(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 4])
->columns(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 6])
->icon('tabler-egg')
->schema([
Grid::make(2)
->columnSpan(1)
->schema([
Image::make('', '')
->hidden(fn ($record) => !$record->image)
->url(fn ($record) => $record->image)
->alt('')
->alignJustify()
->imageSize(150)
->columnSpanFull(),
Flex::make([
Action::make('uploadImage')
->iconButton()
->iconSize(IconSize::Large)
->icon('tabler-photo-up')
->modal()
->modalHeading('')
->modalSubmitActionLabel(trans('admin/egg.import.import_image'))
->schema([
Tabs::make()
->contained(false)
->tabs([
Tab::make(trans('admin/egg.import.url'))
->schema([
Hidden::make('base64Image'),
TextInput::make('image_url')
->label(trans('admin/egg.import.image_url'))
->reactive()
->autocomplete(false)
->debounce(500)
->afterStateUpdated(function ($state, Set $set) {
if (!$state) {
$set('image_url_error', null);
return;
}
try {
if (!filter_var($state, FILTER_VALIDATE_URL)) {
throw new \Exception(trans('admin/egg.import.invalid_url'));
}
$allowedExtensions = [
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
'svg' => 'image/svg+xml',
];
$extension = strtolower(pathinfo(parse_url($state, PHP_URL_PATH), PATHINFO_EXTENSION));
if (!array_key_exists($extension, $allowedExtensions)) {
throw new \Exception(trans('admin/egg.import.unsupported_format', ['format' => implode(', ', $allowedExtensions)]));
}
$host = parse_url($state, PHP_URL_HOST);
$ip = gethostbyname($host);
if (
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
) {
throw new \Exception(trans('admin/egg.import.no_local_ip'));
}
$context = stream_context_create([
'http' => ['timeout' => 3],
'https' => [
'timeout' => 3,
'verify_peer' => true,
'verify_peer_name' => true,
],
]);
$imageContent = @file_get_contents($state, false, $context, 0, 1048576); // 1024KB
if (!$imageContent) {
throw new \Exception(trans('admin/egg.import.image_error'));
}
if (strlen($imageContent) >= 1048576) {
throw new \Exception(trans('admin/egg.import.image_too_large'));
}
$mimeType = $allowedExtensions[$extension];
$base64 = 'data:' . $mimeType . ';base64,' . base64_encode($imageContent);
$set('base64Image', $base64);
$set('image_url_error', null);
} catch (\Exception $e) {
$set('image_url_error', $e->getMessage());
$set('base64Image', null);
}
}),
TextEntry::make('image_url_error')
->hiddenLabel()
->visible(fn ($get) => $get('image_url_error') !== null)
->afterStateHydrated(fn ($set, $get) => $get('image_url_error')),
Image::make(fn (Get $get) => $get('image_url'), '')
->imageSize(150)
->visible(fn ($get) => $get('image_url') && !$get('image_url_error'))
->alignCenter(),
]),
Tab::make(trans('admin/egg.import.file'))
->schema([
FileUpload::make('image')
->hiddenLabel()
->previewable()
->openable(false)
->downloadable(false)
->maxSize(1024)
->maxFiles(1)
->columnSpanFull()
->alignCenter()
->imageEditor()
->saveUploadedFileUsing(function ($file, Set $set) {
$base64 = "data:{$file->getMimeType()};base64,". base64_encode(file_get_contents($file->getRealPath()));
$set('base64Image', $base64);
return $base64;
}),
]),
]),
])
->action(function (array $data, $record): void {
$base64 = $data['base64Image'] ?? null;
if (empty($base64) && !empty($data['image'])) {
$base64 = $data['image'];
}
if (!empty($base64)) {
$record->update([
'image' => $base64,
]);
Notification::make()
->title(trans('admin/egg.import.image_updated'))
->success()
->send();
$record->refresh();
} else {
Notification::make()
->title(trans('admin/egg.import.no_image'))
->warning()
->send();
}
}),
Action::make('deleteImage')
->visible(fn ($record) => $record->image)
->label('')
->icon('tabler-trash')
->iconButton()
->iconSize(IconSize::Large)
->color('danger')
->action(function ($record) {
$record->update([
'image' => null,
]);
Notification::make()
->title(trans('admin/egg.import.image_deleted'))
->success()
->send();
$record->refresh();
}),
]),
]),
TextInput::make('name')
->label(trans('admin/egg.name'))
->required()
->maxLength(255)
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 1])
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 3, 'lg' => 2])
->helperText(trans('admin/egg.name_help')),
Textarea::make('description')
->label(trans('admin/egg.description'))
->rows(3)
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 4, 'lg' => 3])
->helperText(trans('admin/egg.description_help')),
TextInput::make('id')
->label(trans('admin/egg.egg_id'))
->columnSpan(1)
->disabled(),
TextInput::make('uuid')
->label(trans('admin/egg.egg_uuid'))
->disabled()
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
->helperText(trans('admin/egg.uuid_help')),
TextInput::make('id')
->label(trans('admin/egg.egg_id'))
->disabled(),
Textarea::make('description')
->label(trans('admin/egg.description'))
->rows(3)
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
->helperText(trans('admin/egg.description_help')),
TextInput::make('author')
->label(trans('admin/egg.author'))
->required()
->maxLength(255)
->email()
->disabled()
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 2])
->helperText(trans('admin/egg.author_help_edit')),
Toggle::make('force_outgoing_ip')
->inline(false)
->label(trans('admin/egg.force_ip'))
->columnSpan(1)
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
KeyValue::make('startup_commands')
->label(trans('admin/egg.startup_commands'))
->live()
@ -93,24 +279,20 @@ class EditEgg extends EditRecord
->label(trans('admin/egg.file_denylist'))
->placeholder('denied-file.txt')
->helperText(trans('admin/egg.file_denylist_help'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
TagsInput::make('features')
->label(trans('admin/egg.features'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
Toggle::make('force_outgoing_ip')
->inline(false)
->label(trans('admin/egg.force_ip'))
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
Hidden::make('script_is_privileged')
->helperText('The docker images available to servers using this egg.'),
TagsInput::make('tags')
->label(trans('admin/egg.tags'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
TextInput::make('update_url')
->label(trans('admin/egg.update_url'))
->url()
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
TagsInput::make('features')
->label(trans('admin/egg.features'))
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
Hidden::make('script_is_privileged')
->helperText('The docker images available to servers using this egg.'),
TagsInput::make('tags')
->label(trans('admin/egg.tags'))
->columnSpan(['default' => 2, 'sm' => 2, 'md' => 2, 'lg' => 3]),
KeyValue::make('docker_images')
->label(trans('admin/egg.docker_images'))
->live()
@ -248,7 +430,6 @@ class EditEgg extends EditRecord
->placeholder('ghcr.io/pelican-eggs/installers:debian'),
Select::make('script_entry')
->label(trans('admin/egg.script_entry'))
->native(false)
->selectablePlaceholder(false)
->options([
'bash' => 'bash',

View File

@ -19,6 +19,7 @@ use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ReplicateAction;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
@ -42,6 +43,13 @@ class ListEggs extends ListRecords
TextColumn::make('id')
->label('Id')
->hidden(),
ImageColumn::make('image')
->label('')
->alignCenter()
->circular()
->getStateUsing(fn ($record) => $record->image
? $record->image
: 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(public_path('pelican.svg')))),
TextColumn::make('name')
->label(trans('admin/egg.name'))
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)

View File

@ -24,6 +24,7 @@ use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
@ -125,6 +126,7 @@ class MountResource extends Resource
ToggleButtons::make('read_only')
->label(trans('admin/mount.read_only'))
->helperText(trans('admin/mount.read_only_help'))
->stateCast(new BooleanStateCast(false))
->options([
false => trans('admin/mount.toggles.writable'),
true => trans('admin/mount.toggles.read_only'),
@ -162,7 +164,8 @@ class MountResource extends Resource
Section::make()->schema([
Select::make('eggs')->multiple()
->label(trans('admin/mount.eggs'))
->relationship('eggs', 'name')
// Selecting only non-json fields to prevent Postgres from choking on DISTINCT JSON columns
->relationship('eggs', 'name', fn (Builder $query) => $query->select(['eggs.id', 'eggs.name']))
->preload(),
Select::make('nodes')->multiple()
->label(trans('admin/mount.nodes'))

View File

@ -16,6 +16,7 @@ use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class ListServers extends ListRecords
{
@ -47,7 +48,9 @@ class ListServers extends ListRecords
->searchable(),
TextColumn::make('name')
->label(trans('admin/server.name'))
->searchable()
->searchable(query: fn (Builder $query, string $search) => $query->where(
Server::query()->qualifyColumn('name'), 'like', "%{$search}%")
)
->sortable(),
TextColumn::make('node.name')
->label(trans('admin/server.node'))

View File

@ -188,7 +188,7 @@ class UserResource extends Resource
->hintAction(
Action::make('password_reset')
->label(trans('admin/user.password_reset'))
->hidden(fn () => config('mail.default', 'log') === 'log')
->hidden(fn (string $operation) => $operation === 'create' || config('mail.default', 'log') === 'log')
->icon('tabler-send')
->action(function (User $user) {
$status = Password::broker(Filament::getPanel('app')->getAuthPasswordBroker())->sendResetLink([
@ -236,8 +236,7 @@ class UserResource extends Resource
->default(fn () => config('app.timezone', 'UTC'))
->selectablePlaceholder(false)
->options(fn () => collect(DateTimeZone::listIdentifiers())->mapWithKeys(fn ($tz) => [$tz => $tz]))
->searchable()
->native(false),
->searchable(),
Select::make('language')
->label(trans('profile.language'))
->columnSpan([
@ -251,8 +250,7 @@ class UserResource extends Resource
->default('en')
->searchable()
->selectablePlaceholder(false)
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
->native(false),
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages()),
FileUpload::make('avatar')
->visible(fn (?User $user, FileUpload $fileUpload) => $user ? $fileUpload->getDisk()->exists($fileUpload->getDirectory() . '/' . $user->id . '.png') : false)
->avatar()
@ -414,7 +412,7 @@ class UserResource extends Resource
$sshKey->delete();
Activity::event('user:ssh-key.delete')
->actor(auth()->user())
->actor(user())
->subject($user)
->subject($sshKey)
->property('fingerprint', $sshKey->fingerprint)

View File

@ -21,8 +21,6 @@ class CopyFrom extends Select
$this->searchable();
$this->native(false);
$this->live();
}

View File

@ -34,7 +34,6 @@ use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
@ -132,8 +131,7 @@ class EditProfile extends BaseEditProfile
->default(config('app.timezone', 'UTC'))
->selectablePlaceholder(false)
->options(fn () => collect(DateTimeZone::listIdentifiers())->mapWithKeys(fn ($tz) => [$tz => $tz]))
->searchable()
->native(false),
->searchable(),
Select::make('language')
->label(trans('profile.language'))
->required()
@ -143,8 +141,7 @@ class EditProfile extends BaseEditProfile
->selectablePlaceholder(false)
->helperText(fn ($state, LanguageService $languageService) => new HtmlString($languageService->isLanguageTranslated($state) ? ''
: trans('profile.language_help', ['state' => $state]) . ' <u><a href="https://crowdin.com/project/pelican-dev/">Update On Crowdin</a></u>'))
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
->native(false),
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages()),
FileUpload::make('avatar')
->visible(fn () => config('panel.filament.uploadable-avatars'))
->avatar()
@ -442,10 +439,10 @@ class EditProfile extends BaseEditProfile
->label(trans('profile.navigation'))
->inline()
->options([
1 => trans('profile.top'),
0 => trans('profile.side'),
])
->stateCast(new BooleanStateCast(false, true)),
'sidebar' => trans('profile.sidebar'),
'topbar' => trans('profile.topbar'),
'mixed' => trans('profile.mixed'),
]),
]),
Section::make(trans('profile.console'))
->collapsible()
@ -555,6 +552,7 @@ class EditProfile extends BaseEditProfile
{
return [
$this->getSaveFormAction()->formId('form'),
$this->getCancelFormAction()->formId('form'),
];
}
@ -584,7 +582,14 @@ class EditProfile extends BaseEditProfile
$data['console_rows'] = (int) $this->getUser()->getCustomization(CustomizationKey::ConsoleRows);
$data['console_graph_period'] = (int) $this->getUser()->getCustomization(CustomizationKey::ConsoleGraphPeriod);
$data['dashboard_layout'] = $this->getUser()->getCustomization(CustomizationKey::DashboardLayout);
$data['top_navigation'] = (bool) $this->getUser()->getCustomization(CustomizationKey::TopNavigation);
// Handle migration from boolean to string navigation types
$topNavigation = $this->getUser()->getCustomization(CustomizationKey::TopNavigation);
if (is_bool($topNavigation)) {
$data['top_navigation'] = $topNavigation ? 'topbar' : 'sidebar';
} else {
$data['top_navigation'] = $topNavigation;
}
return $data;
}

View File

@ -51,7 +51,7 @@ class Startup extends ServerFormPage
->label(trans('server/startup.command'))
->live()
->visible(fn (Server $server) => in_array($server->startup, $server->egg->startup_commands))
->disabled(fn (Server $server) => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->disabled(fn (Server $server) => !user()?->can(Permission::ACTION_STARTUP_UPDATE, $server))
->formatStateUsing(fn (Server $server) => $server->startup)
->afterStateUpdated(function ($state, Server $server, Set $set) {
$original = $server->startup;

View File

@ -26,12 +26,14 @@ use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Panel;
use Filament\Resources\Pages\ListRecords;
use Filament\Resources\Pages\PageRegistration;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
@ -296,14 +298,27 @@ class ListFiles extends ListRecords
->authorize(fn () => user()?->can(Permission::ACTION_FILE_ARCHIVE, $server))
->label(trans('server/file.actions.archive.title'))
->icon('tabler-archive')->iconSize(IconSize::Large)
->schema([
Grid::make(3)
->schema([
TextInput::make('name')
->label(trans('server/file.actions.archive.archive_name'))
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
->suffix('.tar.gz'),
->columnSpan(2),
Select::make('extension')
->label(trans('server/file.actions.archive.extension'))
->selectablePlaceholder(false)
->options([
'tar.gz' => 'tar.gz',
'zip' => 'zip',
'tar.bz2' => 'tar.bz2',
'tar.xz' => 'tar.xz',
])
->columnSpan(1),
]),
])
->action(function ($data, File $file) {
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, [$file->name], $data['name']);
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, [$file->name], $data['name'], $data['extension']);
Activity::event('server:file.compress')
->property('name', $archive['name'])
@ -391,16 +406,29 @@ class ListFiles extends ListRecords
}),
BulkAction::make('archive')
->authorize(fn () => user()?->can(Permission::ACTION_FILE_ARCHIVE, $server))
->schema([
Grid::make(3)
->schema([
TextInput::make('name')
->label(trans('server/file.actions.archive.archive_name'))
->placeholder(fn () => 'archive-' . str(Carbon::now()->toRfc3339String())->replace(':', '')->before('+0000') . 'Z')
->suffix('.tar.gz'),
->columnSpan(2),
Select::make('extension')
->label(trans('server/file.actions.archive.extension'))
->selectablePlaceholder(false)
->options([
'tar.gz' => 'tar.gz',
'zip' => 'zip',
'tar.bz2' => 'tar.bz2',
'tar.xz' => 'tar.xz',
])
->columnSpan(1),
]),
])
->action(function ($data, Collection $files) {
$files = $files->map(fn ($file) => $file['name'])->toArray();
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, $files, $data['name']);
$archive = $this->getDaemonFileRepository()->compressFiles($this->path, $files, $data['name'], $data['extension']);
Activity::event('server:file.compress')
->property('name', $archive['name'])

View File

@ -255,8 +255,7 @@ class ScheduleResource extends Resource
'6' => trans('server/schedule.time.saturday'),
'0' => trans('server/schedule.time.sunday'),
])
->selectablePlaceholder(false)
->native(false),
->selectablePlaceholder(false),
])
->action(function (Set $set, $data) {
$set('cron_minute', '0');

View File

@ -121,11 +121,11 @@ class ServerConsole extends Widget
foreach ($data as $key => $value) {
$cacheKey = "servers.{$this->server->id}.$key";
$data = cache()->get($cacheKey, []);
$cachedStats = cache()->get($cacheKey, []);
$data[$timestamp] = $value;
$cachedStats[$timestamp] = $value;
cache()->put($cacheKey, $data, now()->addMinute());
cache()->put($cacheKey, array_slice($cachedStats, -120), now()->addMinute());
}
}

View File

@ -77,7 +77,7 @@ class DatabaseHostController extends ApplicationApiController
return $this->fractal->item($databaseHost)
->transformWith($this->getTransformer(DatabaseHostTransformer::class))
->addMeta([
'resource' => route('api.application.databases.view', [
'resource' => route('api.application.databasehosts.view', [
'database_host' => $databaseHost->id,
]),
])

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\Client;
use App\Facades\Activity;
use App\Http\Requests\Api\Client\Account\UpdateEmailRequest;
use App\Http\Requests\Api\Client\Account\UpdatePasswordRequest;
use App\Http\Requests\Api\Client\Account\UpdateUsernameRequest;
use App\Services\Users\UserUpdateService;
use App\Transformers\Api\Client\UserTransformer;
use Illuminate\Auth\AuthManager;
@ -36,6 +37,25 @@ class AccountController extends ClientApiController
->toArray();
}
/**
* Update username
*
* Update the authenticated user's username.
*/
public function updateUsername(UpdateUsernameRequest $request): JsonResponse
{
$original = $request->user()->username;
$this->updateService->handle($request->user(), $request->validated());
if ($original !== $request->input('username')) {
Activity::event('user:account.username-changed')
->property(['old' => $original, 'new' => $request->input('username')])
->log();
}
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
* Update email
*

View File

@ -212,7 +212,8 @@ class FileController extends ClientApiController
$file = $this->fileRepository->setServer($server)->compressFiles(
$request->input('root'),
$request->input('files'),
$request->input('name')
$request->input('name'),
$request->input('extension')
);
Activity::event('server:file.compress')

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Requests\Api\Client\Account;
use App\Exceptions\Http\Base\InvalidPasswordProvidedException;
use App\Http\Requests\Api\Client\ClientApiRequest;
use App\Models\User;
use Illuminate\Container\Container;
use Illuminate\Contracts\Hashing\Hasher;
class UpdateUsernameRequest extends ClientApiRequest
{
/**
* @throws InvalidPasswordProvidedException
*/
public function authorize(): bool
{
if (!parent::authorize()) {
return false;
}
$hasher = Container::getInstance()->make(Hasher::class);
// Verify password matches when changing password or email.
if (!$hasher->check($this->input('password'), $this->user()->password)) {
throw new InvalidPasswordProvidedException(trans('validation.internal.invalid_password'));
}
return true;
}
public function rules(): array
{
$rules = User::getRulesForUpdate($this->user());
return ['username' => $rules['username']];
}
}

View File

@ -22,6 +22,7 @@ class CompressFilesRequest extends ClientApiRequest
'files' => 'required|array',
'files.*' => 'string',
'name' => 'sometimes|nullable|string',
'extension' => 'sometimes|in:zip,tgz,tar.gz,txz,tar.xz,tbz2,tar.bz2',
];
}
}

View File

@ -117,7 +117,6 @@ class PanelInstaller extends SimplePage implements HasForms
->selectablePlaceholder(false)
->options(fn (LanguageService $languageService) => $languageService->getAvailableLanguages())
->afterStateUpdated(fn ($state, Application $app) => $app->setLocale($state ?? config('app.locale')))
->native(false)
->columnStart(4);
}

View File

@ -25,6 +25,18 @@ class ServerEntry extends Component
</div>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
@if($server->egg->image)
<div style="
position: absolute;
inset: 0;
background: url('{{ $server->egg->image }}') right no-repeat;
background-size: contain;
opacity: 0.20;
max-width: 680px;
max-height: 140px;
"></div>
@endif
<div class="flex items-center mb-5 gap-2">
<x-filament::loading-indicator class="h-6 w-6" />
<h2 class="text-xl font-bold">

View File

@ -165,7 +165,7 @@ class ActivityLog extends Model implements HasIcon, HasLabel
public function getIp(): ?string
{
return auth()->user()->can('seeIps activityLog') ? $this->ip : null;
return user()?->can('seeIps activityLog') ? $this->ip : null;
}
public function htmlable(): string
@ -185,7 +185,7 @@ class ActivityLog extends Model implements HasIcon, HasLabel
return "
<div style='display: flex; align-items: center;'>
<img width='50px' height='50px' src='{$avatarUrl}' style='margin-right: 15px' />
<img width='50px' height='50px' src='{$avatarUrl}' style='margin-right: 15px; border-radius: 50%;' />
<div>
<p>$username $this->event</p>

View File

@ -21,6 +21,7 @@ use Illuminate\Support\Str;
* @property string $author
* @property string $name
* @property string|null $description
* @property string|null $image
* @property string[]|null $features
* @property array<string, string> $docker_images
* @property string|null $update_url
@ -80,6 +81,7 @@ class Egg extends Model implements Validatable
'name',
'author',
'description',
'image',
'features',
'docker_images',
'force_outgoing_ip',
@ -104,6 +106,7 @@ class Egg extends Model implements Validatable
'uuid' => ['required', 'string', 'size:36'],
'name' => ['required', 'string', 'max:255'],
'description' => ['string', 'nullable'],
'image' => ['string', 'nullable'],
'features' => ['array', 'nullable'],
'author' => ['required', 'string', 'email'],
'file_denylist' => ['array', 'nullable'],

View File

@ -26,30 +26,18 @@ use App\Services\Helpers\SoftwareVersionService;
use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\TextInput\Actions\CopyAction;
use Filament\Support\Colors\Color;
use Filament\Support\Facades\FilamentColor;
use Filament\Support\Facades\FilamentView;
use Filament\View\PanelsRenderHook;
use Illuminate\Config\Repository;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use Livewire\Component;
use Livewire\Livewire;
use Spatie\Health\Facades\Health;
use function Livewire\on;
use function Livewire\store;
class AppServiceProvider extends ServiceProvider
{
/**
@ -104,57 +92,6 @@ class AppServiceProvider extends ServiceProvider
Scramble::registerApi('application', ['api_path' => 'api/application', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
Scramble::registerApi('client', ['api_path' => 'api/client', 'info' => ['version' => '1.0']])->afterOpenApiGenerated($bearerTokens);
FilamentColor::register([
'danger' => Color::Red,
'gray' => Color::Zinc,
'info' => Color::Sky,
'primary' => Color::Blue,
'success' => Color::Green,
'warning' => Color::Amber,
'blurple' => Color::hex('#5865F2'),
]);
FilamentView::registerRenderHook(
PanelsRenderHook::PAGE_START,
fn () => Blade::render('@livewire(\App\Livewire\AlertBannerContainer::class)'),
);
FilamentView::registerRenderHook(
PanelsRenderHook::FOOTER,
fn () => Blade::render('filament.layouts.footer'),
);
FilamentView::registerRenderHook(
PanelsRenderHook::STYLES_BEFORE,
fn () => Blade::render("@vite(['resources/css/app.css'])")
);
FilamentView::registerRenderHook(
PanelsRenderHook::SCRIPTS_AFTER,
fn () => Blade::render("@vite(['resources/js/app.js'])"),
);
on('dehydrate', function (Component $component) {
if (!Livewire::isLivewireRequest()) {
return;
}
if (store($component)->has('redirect')) {
return;
}
if (count(session()->get('alert-banners') ?? []) <= 0) {
return;
}
$component->dispatch('alertBannerSent');
});
Field::macro('hintCopy', function () {
/** @var Field $this */
return $this->hintAction(CopyAction::make()); // @phpstan-ignore varTag.nativeType
});
// Don't run any health checks during tests
if (!$app->runningUnitTests()) {
Health::checks([

View File

@ -4,11 +4,16 @@ namespace App\Providers\Extensions;
use App\Extensions\OAuth\OAuthService;
use App\Extensions\OAuth\Schemas\AuthentikSchema;
use App\Extensions\OAuth\Schemas\CommonSchema;
use App\Extensions\OAuth\Schemas\BitbucketSchema;
use App\Extensions\OAuth\Schemas\DiscordSchema;
use App\Extensions\OAuth\Schemas\FacebookSchema;
use App\Extensions\OAuth\Schemas\GithubSchema;
use App\Extensions\OAuth\Schemas\GitlabSchema;
use App\Extensions\OAuth\Schemas\GoogleSchema;
use App\Extensions\OAuth\Schemas\LinkedinSchema;
use App\Extensions\OAuth\Schemas\SlackSchema;
use App\Extensions\OAuth\Schemas\SteamSchema;
use App\Extensions\OAuth\Schemas\XSchema;
use Illuminate\Support\ServiceProvider;
class OAuthServiceProvider extends ServiceProvider
@ -19,14 +24,14 @@ class OAuthServiceProvider extends ServiceProvider
$service = new OAuthService();
// Default OAuth providers included with Socialite
$service->register(new CommonSchema('facebook', icon: 'tabler-brand-facebook-f', hexColor: '#1877f2'));
$service->register(new CommonSchema('x', icon: 'tabler-brand-x-f', hexColor: '#1da1f2'));
$service->register(new CommonSchema('linkedin', icon: 'tabler-brand-linkedin-f', hexColor: '#0a66c2'));
$service->register(new CommonSchema('google', icon: 'tabler-brand-google-f', hexColor: '#4285f4'));
$service->register(new FacebookSchema());
$service->register(new XSchema());
$service->register(new LinkedinSchema());
$service->register(new GoogleSchema());
$service->register(new GithubSchema());
$service->register(new GitlabSchema());
$service->register(new CommonSchema('bitbucket', icon: 'tabler-brand-bitbucket-f', hexColor: '#205081'));
$service->register(new CommonSchema('slack', icon: 'tabler-brand-slack', hexColor: '#6ecadc'));
$service->register(new BitbucketSchema());
$service->register(new SlackSchema());
// Additional OAuth providers from socialiteproviders.com
$service->register(new AuthentikSchema());

View File

@ -0,0 +1,82 @@
<?php
namespace App\Providers\Filament;
use Filament\Forms\Components\Field;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput\Actions\CopyAction;
use Filament\Support\Colors\Color;
use Filament\Support\Facades\FilamentColor;
use Filament\Support\Facades\FilamentView;
use Filament\View\PanelsRenderHook;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Livewire\Component;
use Livewire\Livewire;
use function Livewire\on;
use function Livewire\store;
class FilamentServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
FilamentColor::register([
'danger' => Color::Red,
'gray' => Color::Zinc,
'info' => Color::Sky,
'primary' => Color::Blue,
'success' => Color::Green,
'warning' => Color::Amber,
'blurple' => Color::hex('#5865F2'),
]);
FilamentView::registerRenderHook(
PanelsRenderHook::PAGE_START,
fn () => Blade::render('@livewire(\App\Livewire\AlertBannerContainer::class)'),
);
FilamentView::registerRenderHook(
PanelsRenderHook::FOOTER,
fn () => Blade::render('filament.layouts.footer'),
);
FilamentView::registerRenderHook(
PanelsRenderHook::STYLES_BEFORE,
fn () => Blade::render("@vite(['resources/css/app.css'])")
);
FilamentView::registerRenderHook(
PanelsRenderHook::SCRIPTS_AFTER,
fn () => Blade::render("@vite(['resources/js/app.js'])"),
);
on('dehydrate', function (Component $component) {
if (!Livewire::isLivewireRequest()) {
return;
}
if (store($component)->has('redirect')) {
return;
}
if (count(session()->get('alert-banners') ?? []) <= 0) {
return;
}
$component->dispatch('alertBannerSent');
});
Field::macro('hintCopy', function () {
/** @var Field $this */
return $this->hintAction(CopyAction::make()); // @phpstan-ignore varTag.nativeType
});
Select::configureUsing(fn (Select $select) => $select->native(false));
}
public function register(): void {}
}

View File

@ -34,8 +34,16 @@ abstract class PanelProvider extends BasePanelProvider
->brandLogo(config('app.logo'))
->brandLogoHeight('2rem')
->favicon(config('app.favicon', '/pelican.ico'))
->topNavigation(fn () => user()?->getCustomization(CustomizationKey::TopNavigation))
->topbar(fn () => user()?->getCustomization(CustomizationKey::TopNavigation))
->topNavigation(function () {
$navigationType = user()?->getCustomization(CustomizationKey::TopNavigation);
return $navigationType === 'topbar' || $navigationType === true;
})
->topbar(function () {
$navigationType = user()?->getCustomization(CustomizationKey::TopNavigation);
return $navigationType === 'topbar' || $navigationType === 'mixed' || $navigationType === true;
})
->maxContentWidth(config('panel.filament.display-width', 'screen-2xl'))
->profile(EditProfile::class, false)
->userMenuItems([

View File

@ -153,7 +153,7 @@ class DaemonFileRepository extends DaemonRepository
*
* @throws ConnectionException
*/
public function compressFiles(?string $root, array $files, ?string $name): array
public function compressFiles(?string $root, array $files, ?string $name, ?string $extension): array
{
return $this->getHttpClient()
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
@ -164,6 +164,7 @@ class DaemonFileRepository extends DaemonRepository
'root' => $root ?? '/',
'files' => $files,
'name' => $name ?? '',
'extension' => $extension ?? '',
]
)->json();
}

View File

@ -29,6 +29,7 @@ class EggExporterService
'author' => $egg->author,
'uuid' => $egg->uuid,
'description' => $egg->description,
'image' => $egg->image,
'tags' => $egg->tags,
'features' => $egg->features,
'docker_images' => $egg->docker_images,

View File

@ -198,6 +198,7 @@ class EggImporterService
return $model->forceFill([
'name' => Arr::get($parsed, 'name'),
'description' => Arr::get($parsed, 'description'),
'image' => Arr::get($parsed, 'image'),
'tags' => Arr::get($parsed, 'tags', []),
'features' => Arr::get($parsed, 'features'),
'docker_images' => Arr::get($parsed, 'docker_images'),

View File

@ -2,6 +2,7 @@
namespace App\Services\Servers;
use App\Models\Permission;
use App\Models\Server;
use App\Models\Subuser;
use App\Models\User;
@ -17,23 +18,26 @@ class GetUserPermissionsService
*/
public function handle(Server $server, User $user): array
{
if ($user->id === $server->owner_id) {
$isOwner = $user->id === $server->owner_id;
$isAdmin = $user->isAdmin() && ($user->can('view', $server) || $user->can('update', $server));
if ($isOwner && !$isAdmin) {
return ['*'];
}
if ($user->isAdmin() && ($user->can('view', $server) || $user->can('update', $server))) {
$permissions = $user->can('update', $server) ? ['*'] : ['websocket.connect', 'backup.read'];
$adminPermissions = [
'admin.websocket.errors',
'admin.websocket.install',
'admin.websocket.transfer',
];
$permissions[] = 'admin.websocket.errors';
$permissions[] = 'admin.websocket.install';
$permissions[] = 'admin.websocket.transfer';
return $permissions;
if ($isAdmin) {
return $isOwner || $user->can('update', $server) ? array_merge(['*'], $adminPermissions) : array_merge([Permission::ACTION_WEBSOCKET_CONNECT], $adminPermissions);
}
/** @var Subuser|null $subuserPermissions */
$subuserPermissions = $server->subusers()->where('user_id', $user->id)->first();
/** @var Subuser|null $subuser */
$subuser = $server->subusers()->where('user_id', $user->id)->first();
return $subuserPermissions ? $subuserPermissions->permissions : [];
return $subuser->permissions ?? [];
}
}

View File

@ -46,6 +46,7 @@ class EggTransformer extends BaseTransformer
'name' => $model->name,
'author' => $model->author,
'description' => $model->description,
'image' => $model->image,
'features' => $model->features,
'tags' => $model->tags,
'docker_image' => Arr::first($model->docker_images, default: ''), // docker_images, use startup_commands

View File

@ -5,13 +5,14 @@ return [
App\Providers\AppServiceProvider::class,
App\Providers\BackupsServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\Filament\AppPanelProvider::class,
App\Providers\Filament\ServerPanelProvider::class,
App\Providers\Extensions\AvatarServiceProvider::class,
App\Providers\Extensions\CaptchaServiceProvider::class,
App\Providers\Extensions\FeatureServiceProvider::class,
App\Providers\Extensions\OAuthServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\Filament\AppPanelProvider::class,
App\Providers\Filament\FilamentServiceProvider::class,
App\Providers\Filament\ServerPanelProvider::class,
App\Providers\RouteServiceProvider::class,
SocialiteProviders\Manager\ServiceProvider::class,
];

520
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,7 @@ return [
'display-width' => env('FILAMENT_WIDTH', 'screen-2xl'),
'avatar-provider' => env('FILAMENT_AVATAR_PROVIDER', 'gravatar'),
'uploadable-avatars' => env('FILAMENT_UPLOADABLE_AVATARS', false),
'default-navigation' => env('FILAMENT_DEFAULT_NAVIGATION', 'sidebar'),
],
'use_binary_prefix' => env('PANEL_USE_BINARY_PREFIX', true),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,11 +2,12 @@ _comment: 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL'
meta:
version: PLCN_v3
update_url: 'https://github.com/pelican-dev/panel/raw/main/database/Seeders/eggs/minecraft/egg-sponge.yaml'
exported_at: '2025-09-12T08:38:42+00:00'
exported_at: '2025-10-31T12:41:03+00:00'
name: Sponge
author: panel@example.com
uuid: f0d2f88f-1ff3-42a0-b03f-ac44c5571e6d
description: 'A community-driven open source Minecraft: Java Edition modding platform.'
image: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBmaWxsPSIjRjdDRjBEIj48cGF0aCBkPSJNMTkwIDBIMTBDNC41IDAgMCA0LjUgMCAxMHYxODBjMCA1LjUgNC41IDEwIDEwIDEwaDE2LjFjLTEuNy00NS43LS4xLTUyLjUgMy4xLTU3IDMuOS01LjYgNS41LTYuMyAxMS40LTExIDUtNCAzLjItMTAuNS0uNC0xNS4yLTIuMi0yLjktNS4zLTYuMy03LjctOS42LTEuNS0yLjIgMi4yLTE1LjEgMy42LTE5LjggMS40LTQuNyAzLjgtMjAgMjQuOC0yNC4xIDcuOS0xLjYgMjkuNi0yLjcgNDQuNS0xLjgtLjEtLjYtLjMtMS4zLS40LTItLjMtMS4yLS41LTIuNS0uOC0zLjktLjMtMS4zLS42LTIuNy0uOS00LjEtLjMtMS40LS43LTIuOC0xLTQuMy0uNC0xLjUtLjctMi45LTEuMi00LjQtLjgtMy0xLjgtNS45LTMtOC43LS42LTEuNC0xLjItMi43LTEuOS0zLjktLjctMS4xLTEuNC0yLjEtMi0yLjUtLjEtLjEtLjItLjItLjMtLjJoLS4xLjJzLjEgMCAwIDBsLS4zLS4xaC0uMmwtLjQtLjFoLS41Yy0xLjMtLjEtMi43LS4xLTQuMiAwLTIuOS4yLTYgLjgtOSAxLjVzLTUuOSAxLjYtOC43IDIuNGMtMS4yLjQtMi4zLjgtMy40IDEuMS4xLjkuMiAxLjcuMiAyLjYgMCAxMy0xMC41IDIzLjUtMjMuNSAyMy41UzIwLjYgNDcuOSAyMC42IDM0LjlzMTAuNS0yMy41IDIzLjUtMjMuNWM4LjcgMCAxNi4zIDQuNyAyMC40IDExLjggMS0uNCAyLjEtLjggMy4yLTEuMiAyLjgtMS4xIDUuOS0yLjIgOS4xLTMuMiAzLjMtMSA2LjctMiAxMC41LTIuNSAxLjktLjMgMy45LS40IDYuMS0uM2guOGMuMyAwIC42LjEuOC4xSDk1LjdsLjMuMWguMWwuMy4xcy4yIDAgLjMuMWwuNC4xYy42LjIuOS4zIDEuMy41cy43LjMgMS4xLjVjLjcuNCAxLjMuOCAxLjkgMS4yIDEuMS45IDIgMS44IDIuNyAyLjcuOC45IDEuNCAxLjggMiAyLjcgMS4yIDEuOCAyLjEgMy41IDIuOSA1LjIgMS42IDMuNCAyLjkgNi44IDMuOSAxMGwxLjUgNC44Yy41IDEuNi44IDMuMSAxLjIgNC42LjIuNy40IDEuNS41IDIuMi4yLjcuMyAxLjQuNSAyLjEuMyAxLjQuNiAyLjguOSA0LjEuNCAyIC43IDMuOSAxIDUuNiAyMi40IDIuMiAzOS41IDUuMSA0Ny4yIDEyLjggMTEuMyAxMSAyMCA2MSAxNC4zIDEyNC41aDEwYzUuNSAwIDEwLTQuNSAxMC0xMFYxMGMwLTUuNS00LjUtMTAtMTAtMTB6Ii8+PHBhdGggZD0iTTkxLjQgMTQwLjhjLTEuMyAzLjYtMi40IDQ1LjcgMTAgNDUuN3MxMi41LTQzLjIgMTIuMS00NS43Yy0uNC0yLjQtMjAuOC0zLjUtMjIuMSAwek03NSAxMDBjLTguNS0xLjItMTMuNiA0MC4yLTEuNyA0Mi42IDExLjIgMi4yIDEwLjEtNDEuNCAxLjctNDIuNnpNMTMwLjggMTAwYy04LjUtMS4yLTEzLjYgNDAuMi0xLjcgNDIuNiAxMS4yIDIuMiAxMC4yLTQxLjQgMS43LTQyLjZ6Ii8+PC9zdmc+'
tags:
- minecraft
features:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,13 +2,14 @@ _comment: 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL'
meta:
version: PLCN_v3
update_url: 'https://github.com/pelican-dev/panel/raw/main/database/Seeders/eggs/source-engine/egg-custom-source-engine-game.yaml'
exported_at: '2025-09-05T08:55:22+00:00'
exported_at: '2025-10-31T12:43:00+00:00'
name: 'Custom Source Engine Game'
author: panel@example.com
uuid: 2a42d0c2-c0ba-4067-9a0a-9b95d77a3490
description: |-
This option allows modifying the startup arguments and other details to run a custom SRCDS based
game on the panel.
image: 'data:image/webp;base64,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'
tags:
- source
- steamcmd

View File

@ -2,13 +2,14 @@ _comment: 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL'
meta:
version: PLCN_v3
update_url: 'https://github.com/pelican-dev/panel/raw/main/database/Seeders/eggs/source-engine/egg-garrys-mod.yaml'
exported_at: '2025-09-05T08:54:21+00:00'
exported_at: '2025-10-31T12:37:53+00:00'
name: 'Garrys Mod'
author: panel@example.com
uuid: 60ef81d4-30a2-4d98-ab64-f59c69e2f915
description: |-
Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company,
Facepunch Studios.
image: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyNC4zLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAzODQgMzg0IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAzODQgMzg0OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDojMDA4MUZGO30NCgkuc3Qxe2ZpbGw6I0ZGRkZGRjt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTM0My45MiwzODRjLTEwMS42LDAtMjAzLjIsMC0zMDQuOCwwYy0wLjg5LTAuNDQtMS44NC0wLjMxLTIuNzktMC4zOGMtNC41MS0wLjM2LTguNjEtMi4wNy0xMi41Ni00LjEzDQoJYy01LjktMy4wNy0xMC44NC03LjMxLTE0LjkyLTEyLjU0Yy0zLjkzLTUuMDQtNi43My0xMC42NS04LjE3LTE2LjljLTAuMzktMS42OC0wLjMxLTMuNDEtMC40MS01LjEyYy0wLjAyLTAuMzQsMC4wOS0wLjc0LTAuMjctMQ0KCUMwLDI0Mi42NCwwLDE0MS4zNiwwLDQwLjA4YzAuNDMtMC42NywwLjI1LTEuNDQsMC4yNy0yLjE1YzAuMTEtNC42NCwxLjU3LTguOTIsMy42My0xMi45N0M5LjIyLDE0LjQ4LDE3LjI1LDYuODgsMjguMjUsMi40Ng0KCWMzLjM2LTEuMzUsNi44MS0yLjA5LDEwLjQtMi4yN0MzOS4xMywwLjE2LDM5LjY3LDAuNiw0MC4wOCwwQzE0MS45MiwwLDI0My43NiwwLDM0NS42LDBjMC43MSwwLjc3LDEuNjgsMC4zNiwyLjQ5LDAuNDINCgljMS45MiwwLjE1LDMuNzQsMC42NCw1LjUzLDEuMjVjNy4zMSwyLjQ2LDEzLjU4LDYuNTYsMTguODIsMTIuMmM1LjM5LDUuOCw5LjEyLDEyLjUsMTAuOSwyMC4yNWMwLjM4LDEuNjYsMC4yOSwzLjM3LDAuMzksNS4wNg0KCWMwLjAyLDAuMzItMC4wNywwLjY3LDAuMjYsMC45YzAsMTAxLjI4LDAsMjAyLjU2LDAsMzAzLjg0Yy0wLjU4LDAuOC0wLjM1LDEuNzYtMC4zNCwyLjU5YzAuMDMsMi43LTAuNjMsNS4yNC0xLjUzLDcuNzENCgljLTQuMjMsMTEuNTUtMTEuODgsMjAuMTItMjIuOCwyNS43M2MtMy4xMywxLjYxLTYuNDIsMi45MS05Ljk2LDMuNDNjLTEuNTIsMC4yMi0zLjA0LDAuMjYtNC41NiwwLjMzDQoJQzM0NC40OCwzODMuNzMsMzQ0LjE1LDM4My42OCwzNDMuOTIsMzg0eiIvPg0KPHBhdGggY2xhc3M9InN0MSIgZD0iTTIzNS41NywyMjEuOTFjLTIuODQsMy40LTUuNjgsNy41MS05LjIyLDEwLjg4Yy03LjIsNi44NC0xNi4yLDEwLjQxLTI1Ljc3LDEyLjQ5DQoJYy0xMS41MywyLjUxLTIzLjEzLDIuMTgtMzQuNzYsMC42MWMtMTQuNTItMS45NS0yNy4yNy03LjgzLTM4LjM4LTE3LjIyYy05LjM1LTcuOTEtMTYuMDYtMTcuOC0yMC44NC0yOS4wNA0KCWMtNC44OC0xMS40Ni03LjQtMjMuNTEtOC42OC0zNS44MWMtMS44NS0xNy43OC0wLjk5LTM1LjQyLDQuMzMtNTIuNmM0LjM3LTE0LjExLDExLjAxLTI3LDIxLjA4LTM4LjAzDQoJQzEzNSw2MC40LDE0OS4xOCw1Mi4wOCwxNjYuMSw0OC41OWM5LjQ2LTEuOTUsMTkuMDEtMS45NSwyOC41Ny0wLjRjMTcsMi43NywzMC4xMywxMS40OSwzOS43NSwyNS42OGMwLjI1LDAuMzIsMS4xOSwxLjcyLDEuMTksMS43Mg0KCXMwLTQuNzgsMC02LjgzYy0wLjAxLTUuMTYsMC0xNi41MSwwLTE2LjUxczEuMDksMCwxLjM1LDBjMTUuMjgtMC4wNSw0OS45Ny0wLjAxLDQ5Ljk3LTAuMDFzLTAuMDEsMS45Ni0wLjAxLDQuMjgNCgljMCw2NC40LDAuMDksMTI4LjgtMC4wNSwxOTMuMTljLTAuMDQsMjAuMzYtNS43OSwzOS0xOC44NCw1NC45NWMtMTAuMTUsMTIuNC0yMy4yNSwyMC41Ny0zOC4yNSwyNS44NQ0KCWMtMTQuNiw1LjE0LTI5LjcsNi45MS00NS4wOCw2LjI2Yy0xNi44MS0wLjcxLTMyLjk5LTQuMzUtNDcuNzUtMTIuNjRjLTIyLjIyLTEyLjQ5LTM1LjcyLTMxLjIyLTM5LjA3LTU2Ljc4DQoJYy0wLjQ1LTMuMzktMC41Ni02Ljg0LTAuNjktMTAuMjZjLTAuMDEtMC4yMS0wLjA3LTEuMzEtMC4wNy0xLjMxczEuMDQsMCwxLjI2LDBjMTUuMjQtMC4wNywzMC40OC0wLjA4LDQ1LjcyLTAuMDMNCgljMC4xOCwwLDAuOSwwLjAxLDAuOSwwLjAxcy0wLjAxLDAuOTYtMC4wMSwxLjEyYzAuMTYsOC44NiwyLjYyLDE2Ljg2LDguNzcsMjMuNDRjNC41OSw0LjkxLDEwLjI2LDguMTYsMTYuNyw5Ljg5DQoJYzE0Ljk2LDQuMDMsMjkuNTksMy4xOSw0My40OS0zLjk4YzEyLjUtNi40NSwyMC40LTE2LjU0LDIxLjMtMzAuODVjMC42Ny0xMC43OSwwLjI3LTIxLjY2LDAuMzQtMzIuNDkNCglDMjM1LjU3LDIyMi4zNSwyMzUuNTcsMjIxLjc5LDIzNS41NywyMjEuOTF6Ii8+DQo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMTUwLjc3LDE0OS44MmMtMC4zLTExLjc5LDAuOTMtMjMuMzcsNS41NS0zNC4zNWM1LjQzLTEyLjg5LDE0LjkxLTIwLjc0LDI4LjktMjIuODgNCgljOS42Ny0xLjQ4LDE5LjA4LTAuODUsMjcuOTksMy40N2M4LjksNC4zMiwxNC4zOSwxMS43LDE3Ljk5LDIwLjY0YzMuNTMsOC43NSw1LjMzLDE3Ljk0LDUuNDYsMjcuMzFjMC4xMiw4LjM4LDAuMDksMTYuODktMS4yLDI1LjE0DQoJYy0yLjU5LDE2LjU3LTE0LjA5LDMyLjA4LTMxLjczLDM2LjRjLTE2LjA5LDMuOTQtMzIuNjktMi4yMy00Mi4yOC0xNS44MWMtNi43NC05LjUzLTkuODUtMjAuMjEtMTAuNjYtMzEuNjgNCglDMTUwLjYxLDE1NS4zMywxNTAuNzcsMTUyLjU3LDE1MC43NywxNDkuODJ6Ii8+DQo8L3N2Zz4NCg=='
tags:
- source
- steamcmd

File diff suppressed because one or more lines are too long

View File

@ -2,13 +2,14 @@ _comment: 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PANEL'
meta:
version: PLCN_v3
update_url: 'https://github.com/pelican-dev/panel/raw/main/database/Seeders/eggs/source-engine/egg-team-fortress2.yaml'
exported_at: '2025-09-05T08:55:44+00:00'
exported_at: '2025-10-31T12:31:09+00:00'
name: 'Team Fortress 2'
author: panel@example.com
uuid: 7f8eb681-b2c8-4bf8-b9f4-d79ff70b6e5d
description: |-
Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published
by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.
image: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNTAwcHgiIGhlaWdodD0iNTAwLjAwOXB4IiB2aWV3Qm94PSItNTAgLTUwLjAwNSA1MDAgNTAwLjAwOSIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAtNTAgLTUwLjAwNSA1MDAgNTAwLjAwOSINCgkgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzFfIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjI1NS4zOTk0IiB5MT0iLTQzMy44NDM4IiB4Mj0iMTQ0LjU5ODkiIHkyPSI1My44NDUxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDAgMTApIj4NCgk8c3RvcCAgb2Zmc2V0PSIwIiBzdHlsZT0ic3RvcC1jb2xvcjojNUQxRjBFIi8+DQoJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6I0QwOTczNyIvPg0KPC9saW5lYXJHcmFkaWVudD4NCjxwYXRoIGZpbGw9InVybCgjU1ZHSURfMV8pIiBkPSJNMjYyLjA0NC00Mi4yMzlDMzcwLjA5Ny0xNC42NDQsNDUwLDgzLjM0NCw0NTAsMTk5Ljk5OGMwLDIuNjYxLTAuMDU3LDUuMzExLTAuMTQzLDcuOTUxDQoJbC0xNjguMTUxLTIzLjkxNmMtNC45MjItMjUuMzM4LTIxLjMzMy00Ni41NTYtNDMuNTk0LTU4LjA0NUwyNjIuMDQ0LTQyLjIzOXogTTEyNS45OSwxNjEuODgNCgljMTEuNDg4LTIyLjI2MSwzMi43MDctMzguNjcsNTguMDQzLTQzLjU5M2wyMy45Mi0xNjguMTUzYy0yLjY0My0wLjA4My01LjI5LTAuMTM5LTcuOTUzLTAuMTM5DQoJYy0xMTYuNjUyLDAtMjE0LjYzOSw3OS44OTgtMjQyLjIzNSwxODcuOTUzTDEyNS45OSwxNjEuODh6IE0xNjEuODgzLDI3NC4wMDhjLTIyLjI1OS0xMS40ODktMzguNjctMzIuNzEtNDMuNTkyLTU4LjA0NQ0KCWwtMTY4LjE1Mi0yMy45MmMtMC4wODMsMi42NDMtMC4xMzksNS4yOTMtMC4xMzksNy45NTVjMCwxMTYuNjQ4LDc5Ljg5OCwyMTQuNjM3LDE4Ny45NTIsMjQyLjIzM0wxNjEuODgzLDI3NC4wMDh6IE0yNzQuMDEsMjM4LjExMw0KCWMtMTEuNDksMjIuMjYxLTMyLjcwNywzOC42NjktNTguMDQ2LDQzLjU5M2wtMjMuOTE5LDE2OC4xNThjMi42NDMsMC4wODMsNS4yOTIsMC4xNCw3Ljk1NCwwLjE0DQoJYzExNi42NTMsMCwyMTQuNjQtNzkuOTAxLDI0Mi4yMzItMTg3Ljk1NUwyNzQuMDEsMjM4LjExM3oiLz4NCjxyYWRpYWxHcmFkaWVudCBpZD0iU1ZHSURfMl8iIGN4PSI5OC4xOTE5IiBjeT0iLTE5OC4zNTYiIHI9IjQyNS45ODcxIiBmeD0iOTIuNzU2NSIgZnk9Ii0xOTkuNDgzNCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwLjk3NzQgMC4yMTE0IDAuMTI2NiAtMC41ODUxIC03Ny4wMzAxIC0xNjcuMzcwNykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4NCgk8c3RvcCAgb2Zmc2V0PSIwIiBzdHlsZT0ic3RvcC1jb2xvcjojRkZGRkZGO3N0b3Atb3BhY2l0eTowIi8+DQoJPHN0b3AgIG9mZnNldD0iMC40NjU3IiBzdHlsZT0ic3RvcC1jb2xvcjojRkZGRkZGO3N0b3Atb3BhY2l0eTowLjA4MDQiLz4NCgk8c3RvcCAgb2Zmc2V0PSIwLjk4NjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNGRkZGRkY7c3RvcC1vcGFjaXR5OjAuMjE0MyIvPg0KCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNGRkZGRkY7c3RvcC1vcGFjaXR5OjAiLz4NCjwvcmFkaWFsR3JhZGllbnQ+DQo8cGF0aCBmaWxsPSJ1cmwoI1NWR0lEXzJfKSIgZD0iTTI2Mi4wNDQtNDIuMjM5QzM3MC4wOTctMTQuNjQ0LDQ1MCw4My4zNDQsNDUwLDE5OS45OThjMCwyLjY2MS0wLjA1Nyw1LjMxMS0wLjE0Myw3Ljk1MQ0KCWwtMTY4LjE1MS0yMy45MTZjLTQuOTIyLTI1LjMzOC0yMS4zMzMtNDYuNTU2LTQzLjU5NC01OC4wNDVMMjYyLjA0NC00Mi4yMzl6IE0xMjUuOTksMTYxLjg4DQoJYzExLjQ4OC0yMi4yNjEsMzIuNzA3LTM4LjY3LDU4LjA0My00My41OTNsMjMuOTItMTY4LjE1M2MtMi42NDMtMC4wODMtNS4yOS0wLjEzOS03Ljk1My0wLjEzOQ0KCWMtMTE2LjY1MiwwLTIxNC42MzksNzkuODk4LTI0Mi4yMzUsMTg3Ljk1M0wxMjUuOTksMTYxLjg4eiBNMTYxLjg4MywyNzQuMDA4Yy0yMi4yNTktMTEuNDg5LTM4LjY3LTMyLjcxLTQzLjU5Mi01OC4wNDUNCglsLTE2OC4xNTItMjMuOTJjLTAuMDgzLDIuNjQzLTAuMTM5LDUuMjkzLTAuMTM5LDcuOTU1YzAsMTE2LjY0OCw3OS44OTgsMjE0LjYzNywxODcuOTUyLDI0Mi4yMzNMMTYxLjg4MywyNzQuMDA4eiBNMjc0LjAxLDIzOC4xMTMNCgljLTExLjQ5LDIyLjI2MS0zMi43MDcsMzguNjY5LTU4LjA0Niw0My41OTNsLTIzLjkxOSwxNjguMTU4YzIuNjQzLDAuMDgzLDUuMjkyLDAuMTQsNy45NTQsMC4xNA0KCWMxMTYuNjUzLDAsMjE0LjY0LTc5LjkwMSwyNDIuMjMyLTE4Ny45NTVMMjc0LjAxLDIzOC4xMTN6Ii8+DQo8L3N2Zz4NCg=='
tags:
- source
- steamcmd

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('eggs', function (Blueprint $table) {
$table->longText('image')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('eggs', function (Blueprint $table) {
$table->dropColumn('image');
});
}
};

View File

@ -21,6 +21,7 @@ return [
],
'user' => [
'account' => [
'username-changed' => 'Changed username from <b>:old</b> to <b>:new</b>',
'email-changed' => 'Changed email from <b>:old</b> to <b>:new</b>',
'password-changed' => 'Changed password',
],

View File

@ -13,6 +13,9 @@ return [
'import' => [
'file' => 'File',
'url' => 'URL',
'image_url' => 'Image URL',
'image_error' => 'Could not fetch image',
'image_too_large' => 'Image too large. Limit is 1024KB',
'egg_help' => 'This should be the raw .json/.yaml file',
'url_help' => 'URLs must point directly to the raw .json/.yaml file',
'add_url' => 'New URL',
@ -20,6 +23,13 @@ return [
'import_success' => 'Import Success',
'github' => 'Add from Github',
'refresh' => 'Refresh',
'import_image' => 'Import Image',
'no_local_ip' => 'Local IP Addresses are not allowed',
'unsupported_format' => 'Unsupported Format. Supported Formats: :formats',
'invalid_url' => 'The provided URL is invalid',
'image_deleted' => 'Image Deleted',
'no_image' => 'No Image Provided',
'image_updated' => 'Image Updated',
],
'export' => [
'modal' => 'How would you like to export :egg ?',

View File

@ -20,8 +20,10 @@ return [
'app_favicon_help' => 'Favicon should be placed in the public folder located in the root panel directory.',
'debug_mode' => 'Debug Mode',
'navigation' => 'Navigation',
'default_navigation' => 'Default Navigation Type',
'sidebar' => 'Sidebar',
'topbar' => 'Topbar',
'mixed' => 'Mixed',
'unit_prefix' => 'Unit Prefix',
'decimal_prefix' => 'Decimal Prefix (MB/GB)',
'binary_prefix' => 'Binary Prefix (MiB/GiB)',

View File

@ -6,7 +6,6 @@ return [
'model_label_plural' => 'Webhooks',
'endpoint' => 'Endpoint',
'description' => 'Description',
'events' => 'Events',
'no_webhooks' => 'No Webhooks',
'help' => 'Help',
'help_text' => 'You have to wrap variable name in between {{ }} for example if you want to get the name from the api you can use {{name}}.',
@ -38,7 +37,6 @@ return [
'thumbnail' => 'Thumbnail URL',
'embeds' => 'Embeds',
'thread_name' => 'Forum Thread Name',
'flags' => 'Flags',
'allowed_mentions' => 'Allowed Mentions',
'roles' => 'Roles',
'users' => 'Users',

View File

@ -61,8 +61,9 @@ return [
'graph_period' => 'Graph Period',
'graph_period_helper' => 'The amount of data points, seconds, shown on the console graphs.',
'navigation' => 'Navigation Type',
'top' => 'Topbar',
'side' => 'Sidebar',
'sidebar' => 'Sidebar',
'topbar' => 'Topbar',
'mixed' => 'Mixed',
'no_oauth' => 'No Accounts Linked',
'no_api_keys' => 'No API Keys',
'no_ssh_keys' => 'No SSH Keys',

View File

@ -46,6 +46,7 @@ return [
'title' => 'Archive',
'archive_name' => 'Archive Name',
'notification' => 'Archive Created',
'extension' => 'Extension',
],
'unarchive' => [
'title' => 'Unarchive',

View File

@ -17,10 +17,10 @@
"vite": "6.2.6"
},
"dependencies": {
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-search": "^0.15.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"glob": "^11.0.3",
"xterm-addon-search-bar": "^0.2.0"

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
var i=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});export{i as default};
var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};

View File

@ -1 +1 @@
(()=>{var d=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});var m=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},u=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i})=>({handleFormValidationError(e){e.detail.livewireId===i&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let t=n;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,o)=>n.$get(m(e,t,o)),$set:(t,o,l,a=!1)=>n.$set(m(e,t,l),o,a),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:i,commit:e,respond:n,succeed:t,fail:o})=>{t(({snapshot:l,effects:a})=>{a.dispatches?.forEach(r=>{if(!r.params?.awaitSchemaComponent)return;let s=Array.from(i.el.querySelectorAll(`[wire\\:partial="schema-component::${r.params.awaitSchemaComponent}"]`)).filter(c=>u(c)===i);if(s.length!==1){if(s.length>1)throw`Multiple schema components found with key [${r.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${i.id}-${r.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(r.name,{detail:r.params}))},{once:!0})}})})})});})();
(()=>{var d=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let t=this.$el.parentElement;t&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(t),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let t=this.$el.parentElement;if(!t)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=t.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var u=function(t,e,n){let i=t;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)i=i.includes(".")?i.slice(0,i.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(i)?e:["",null,void 0].includes(e)?i:`${i}.${e}`},c=t=>{let e=Alpine.findClosest(t,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:t})=>({handleFormValidationError(e){e.detail.livewireId===t&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let i=n;for(;i;)i.dispatchEvent(new CustomEvent("expand")),i=i.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:t,containerPath:e,$wire:n})=>({$statePath:t,$get:(i,s)=>n.$get(u(e,i,s)),$set:(i,s,a,o=!1)=>n.$set(u(e,i,a),s,o),get $state(){return n.$get(t)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:t,commit:e,respond:n,succeed:i,fail:s})=>{i(({snapshot:a,effects:o})=>{o.dispatches?.forEach(r=>{if(!r.params?.awaitSchemaComponent)return;let l=Array.from(t.el.querySelectorAll(`[wire\\:partial="schema-component::${r.params.awaitSchemaComponent}"]`)).filter(h=>c(h)===t);if(l.length!==1){if(l.length>1)throw`Multiple schema components found with key [${r.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${t.id}-${r.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(r.name,{detail:r.params}))},{once:!0})}})})})});})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,11 +3,11 @@ import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { SearchAddon } from '@xterm/addon-search';
import { SearchBarAddon } from 'xterm-addon-search-bar';
import { CanvasAddon } from '@xterm/addon-canvas';
import { WebglAddon } from '@xterm/addon-webgl';
window.Xterm = {
Terminal,
CanvasAddon,
WebglAddon,
FitAddon,
WebLinksAddon,
SearchAddon,

View File

@ -76,19 +76,19 @@
theme: theme
};
const { Terminal, FitAddon, WebLinksAddon, SearchAddon, SearchBarAddon, CanvasAddon } = window.Xterm;
const { Terminal, FitAddon, WebLinksAddon, SearchAddon, SearchBarAddon, WebglAddon } = window.Xterm;
const terminal = new Terminal(options);
const fitAddon = new FitAddon();
const webLinksAddon = new WebLinksAddon();
const searchAddon = new SearchAddon();
const searchAddonBar = new SearchBarAddon({ searchAddon });
const canvasAddon = new CanvasAddon();
const webglAddon = new WebglAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(webLinksAddon);
terminal.loadAddon(searchAddon);
terminal.loadAddon(searchAddonBar);
terminal.loadAddon(canvasAddon);
terminal.loadAddon(webglAddon);
terminal.open(document.getElementById('terminal'));

View File

@ -1,7 +1,6 @@
@php
$actiongroup = \App\Filament\App\Resources\Servers\Pages\ListServers::getPowerActionGroup()->record($server);
@endphp
<div wire:poll.15s
class="relative cursor-pointer"
x-on:click="window.location.href = '{{ \App\Filament\Server\Pages\Console::getUrl(panel: 'server', tenant: $server) }}'">
@ -11,6 +10,18 @@
</div>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-lg overflow-hidden p-3">
@if($server->egg->image)
<div style="
position: absolute;
inset: 0;
background: url('{{ $server->egg->image }}') right no-repeat;
background-size: contain;
opacity: 0.20;
max-width: 680px;
max-height: 140px;
"></div>
@endif
<div class="flex items-center mb-5 gap-2">
<x-filament::icon-button
:icon="$server->condition->getIcon()"
@ -26,7 +37,8 @@
</h2>
@if ($actiongroup->isVisible())
<div class="end-0">
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-b-lg overflow-hidden p-1" x-on:click.stop>
<div class="flex-1 dark:bg-gray-800 dark:text-white rounded-b-lg overflow-hidden p-1"
x-on:click.stop>
{{ $actiongroup }}
</div>
</div>

View File

@ -21,6 +21,7 @@ Route::get('/permissions', [Client\ClientController::class, 'permissions']);
Route::prefix('/account')->middleware(AccountSubject::class)->group(function () {
Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account');
Route::put('/username', [Client\AccountController::class, 'updateUsername'])->name('api:client.account.update-username');
Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email');
Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password');

View File

@ -415,11 +415,6 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
"@xterm/addon-canvas@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.7.0.tgz#d3a3835f3634e0106e108661315ae14da23e8dd7"
integrity sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==
"@xterm/addon-fit@^0.10.0":
version "0.10.0"
resolved "https://registry.yarnpkg.com/@xterm/addon-fit/-/addon-fit-0.10.0.tgz#bebf87fadd74e3af30fdcdeef47030e2592c6f55"
@ -435,6 +430,11 @@
resolved "https://registry.yarnpkg.com/@xterm/addon-web-links/-/addon-web-links-0.11.0.tgz#f283513b8c713757bad8e3bf04b6becc3b4e585f"
integrity sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==
"@xterm/addon-webgl@^0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz#9e927cee10af971595fb2a72fd4c3bc2819f0096"
integrity sha512-xCnfMBTI+/HKPdRnSOHaJDRqEpq2Ugy8LEj9GiY4J3zJObo3joylIFaMvzBwbYRg8zLtkO0KQaStCeSfoaI2/w==
"@xterm/xterm@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.5.0.tgz#275fb8f6e14afa6e8a0c05d4ebc94523ff775396"