mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-20 02:54:45 +02:00
Merge branch 'main' into vehikl/singleton
This commit is contained in:
commit
6818ee8ee6
4
.github/workflows/lint.yaml
vendored
4
.github/workflows/lint.yaml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.2, 8.3, 8.4]
|
||||
php: [ 8.2, 8.3, 8.4 ]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -68,4 +68,4 @@ jobs:
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: PHPStan
|
||||
run: vendor/bin/phpstan --memory-limit=-1
|
||||
run: vendor/bin/phpstan --memory-limit=-1 --error-format=github
|
||||
|
@ -79,14 +79,15 @@ RUN chown root:www-data ./ \
|
||||
&& chmod 750 ./ \
|
||||
# Files should not have execute set, but directories need it
|
||||
&& find ./ -type d -exec chmod 750 {} \; \
|
||||
# Create necessary directories
|
||||
# Create necessary directories
|
||||
&& mkdir -p /pelican-data/storage /var/www/html/storage/app/public /var/run/supervisord /etc/supercronic \
|
||||
# Symlinks for env, database, and avatars
|
||||
&& ln -s /pelican-data/.env ./.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -sf /var/www/html/storage/app/public /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage/avatars /var/www/html/storage/app/public/avatars \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& ln -s /pelican-data/storage/fonts /var/www/html/storage/app/public/fonts \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord
|
||||
|
||||
|
@ -31,8 +31,11 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||
if (config('cache.default') === 'redis') {
|
||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||
// This only needs to run when using redis. anything else throws an error.
|
||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||
}
|
||||
|
||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||
|
@ -9,12 +9,17 @@ use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class GSLTokenSchema implements FeatureSchemaInterface
|
||||
{
|
||||
@ -22,14 +27,14 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
public function getListeners(): array
|
||||
{
|
||||
return [
|
||||
'gsl token expired',
|
||||
'account not found',
|
||||
'(gsl token expired)',
|
||||
'(account not found)',
|
||||
];
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return 'gsltoken';
|
||||
return 'gsl_token';
|
||||
}
|
||||
|
||||
public function getAction(): Action
|
||||
@ -38,7 +43,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
$server = Filament::getTenant();
|
||||
|
||||
/** @var ServerVariable $serverVariable */
|
||||
$serverVariable = $server->serverVariables()->where('env_variable', 'STEAM_ACC')->first();
|
||||
$serverVariable = $server->serverVariables()->whereHas('variable', function (Builder $query) {
|
||||
$query->where('env_variable', 'STEAM_ACC');
|
||||
})->first();
|
||||
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
@ -47,9 +54,8 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
->modalSubmitActionLabel('Update GSL Token')
|
||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->form([
|
||||
Placeholder::make('java')
|
||||
->label('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it
|
||||
completely.'),
|
||||
Placeholder::make('info')
|
||||
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
|
||||
TextInput::make('gsltoken')
|
||||
->label('GSL Token')
|
||||
->rules([
|
||||
@ -97,13 +103,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
|
||||
Notification::make()
|
||||
->title('GSL Token updated')
|
||||
->body('Restart the server to use the new token.')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body($e->getMessage())
|
||||
->title('Could not update GSL Token')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use App\Facades\Activity;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
@ -20,10 +21,11 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return [
|
||||
'java.lang.UnsupportedClassVersionError',
|
||||
'minecraft 1.17 requires running the server with java 16 or above',
|
||||
'minecraft 1.18 requires running the server with java 17 or above',
|
||||
'unsupported major.minor version',
|
||||
'has been compiled by a more recent version of the java runtime',
|
||||
'minecraft 1.17 requires running the server with java 16 or above',
|
||||
'minecraft 1.18 requires running the server with java 17 or above',
|
||||
'minecraft 1.19 requires running the server with java 17 or above',
|
||||
];
|
||||
}
|
||||
|
||||
@ -68,17 +70,18 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
->property(['old' => $original, 'new' => $new])
|
||||
->log();
|
||||
}
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
->body('Restart the server to use the new image.')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body($e->getMessage())
|
||||
->title('Could not update docker image')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
public function getListeners(): array
|
||||
{
|
||||
return [
|
||||
'You need to agree to the EULA in order to run the server',
|
||||
'you need to agree to the eula in order to run the server',
|
||||
];
|
||||
}
|
||||
|
||||
@ -33,30 +33,29 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Minecraft EULA')
|
||||
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>')))
|
||||
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
|
||||
->modalSubmitActionLabel('I Accept')
|
||||
->action(function (DaemonFileRepository $fileRepository, DaemonPowerRepository $powerRepository) {
|
||||
try {
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
$content = $fileRepository->setServer($server)->getContent('eula.txt');
|
||||
$content = preg_replace('/(eula=)false/', '\1true', $content);
|
||||
$fileRepository->setServer($server)->putContent('eula.txt', $content);
|
||||
|
||||
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
|
||||
|
||||
$powerRepository->setServer($server)->send('restart');
|
||||
|
||||
Notification::make()
|
||||
->title('Docker image updated')
|
||||
->body('Restart the server.')
|
||||
->title('Minecraft EULA accepted')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body($e->getMessage())
|
||||
->title('Could not accept Minecraft EULA')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ use Filament\Pages\Concerns\InteractsWithHeaderActions;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Illuminate\Http\Client\Factory;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\Str;
|
||||
@ -144,8 +145,7 @@ class Settings extends Page implements HasForms
|
||||
->placeholder('/pelican.ico'),
|
||||
]),
|
||||
Group::make()
|
||||
->columnSpan(2)
|
||||
->columns(4)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
@ -167,6 +167,10 @@ class Settings extends Page implements HasForms
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_TOP_NAVIGATION', (bool) $state))
|
||||
->default(env('FILAMENT_TOP_NAVIGATION', config('panel.filament.top-navigation'))),
|
||||
]),
|
||||
Group::make()
|
||||
->columns(2)
|
||||
->schema([
|
||||
Select::make('FILAMENT_AVATAR_PROVIDER')
|
||||
->label(trans('admin/setting.general.avatar_provider'))
|
||||
->native(false)
|
||||
@ -205,12 +209,18 @@ class Settings extends Page implements HasForms
|
||||
->formatStateUsing(fn ($state): int => (int) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('APP_2FA_REQUIRED', (int) $state))
|
||||
->default(env('APP_2FA_REQUIRED', config('panel.auth.2fa_required'))),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label(trans('admin/setting.general.display_width'))
|
||||
->native(false)
|
||||
->options(MaxWidth::class)
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
TagsInput::make('TRUSTED_PROXIES')
|
||||
->label(trans('admin/setting.general.trusted_proxies'))
|
||||
->separator()
|
||||
->splitKeys(['Tab', ' '])
|
||||
->placeholder(trans('admin/setting.general.trusted_proxies_help'))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', config('trustedproxy.proxies'))))
|
||||
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
|
||||
->hintActions([
|
||||
FormAction::make('clear')
|
||||
->label(trans('admin/setting.general.clear'))
|
||||
@ -245,12 +255,6 @@ class Settings extends Page implements HasForms
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
]),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label(trans('admin/setting.general.display_width'))
|
||||
->native(false)
|
||||
->options(MaxWidth::class)
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DatabaseHostResource extends Resource
|
||||
{
|
||||
@ -27,7 +28,7 @@ class DatabaseHostResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
@ -144,7 +145,7 @@ class DatabaseHostResource extends Resource
|
||||
->preload()
|
||||
->helperText(trans('admin/databasehost.linked_nodes_help'))
|
||||
->label(trans('admin/databasehost.linked_nodes'))
|
||||
->relationship('nodes', 'name'),
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'))),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@ -158,4 +159,13 @@ class DatabaseHostResource extends Resource
|
||||
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Support\Str;
|
||||
@ -145,7 +146,7 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->preload()
|
||||
->helperText(trans('admin/databasehost.linked_nodes_help'))
|
||||
->label(trans('admin/databasehost.linked_nodes'))
|
||||
->relationship('nodes', 'name'),
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class MountResource extends Resource
|
||||
{
|
||||
@ -44,7 +45,7 @@ class MountResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
@ -147,7 +148,7 @@ class MountResource extends Resource
|
||||
->preload(),
|
||||
Select::make('nodes')->multiple()
|
||||
->label(trans('admin/mount.nodes'))
|
||||
->relationship('nodes', 'name')
|
||||
->relationship('nodes', 'name', fn (Builder $query) => $query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id')))
|
||||
->searchable(['name', 'fqdn'])
|
||||
->preload(),
|
||||
]),
|
||||
@ -170,4 +171,13 @@ class MountResource extends Resource
|
||||
'edit' => Pages\EditMount::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
use App\Filament\Admin\Resources\NodeResource\RelationManagers;
|
||||
use App\Models\Node;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class NodeResource extends Resource
|
||||
{
|
||||
@ -37,7 +38,7 @@ class NodeResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
@ -56,4 +57,11 @@ class NodeResource extends Resource
|
||||
'edit' => Pages\EditNode::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use App\Models\Node;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
@ -149,14 +150,15 @@ class CreateNode extends CreateRecord
|
||||
->required()
|
||||
->maxLength(100),
|
||||
|
||||
ToggleButtons::make('scheme')
|
||||
Hidden::make('scheme')
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
|
||||
Hidden::make('behind_proxy')
|
||||
->default(false),
|
||||
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(1)
|
||||
->inline()
|
||||
->helperText(function (Get $get) {
|
||||
if (request()->isSecure()) {
|
||||
@ -169,20 +171,29 @@ class CreateNode extends CreateRecord
|
||||
|
||||
return '';
|
||||
})
|
||||
->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
'http' => 'HTTP',
|
||||
'https' => 'HTTPS (SSL)',
|
||||
'https_proxy' => 'HTTPS with (reverse) proxy',
|
||||
])
|
||||
->colors([
|
||||
'http' => 'warning',
|
||||
'https' => 'success',
|
||||
'https_proxy' => 'success',
|
||||
])
|
||||
->icons([
|
||||
'http' => 'tabler-lock-open-off',
|
||||
'https' => 'tabler-lock',
|
||||
'https_proxy' => 'tabler-shield-lock',
|
||||
])
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'),
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http')
|
||||
->live()
|
||||
->dehydrated(false)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$set('scheme', $state === 'http' ? 'http' : 'https');
|
||||
$set('behind_proxy', $state === 'https_proxy');
|
||||
}),
|
||||
]),
|
||||
Step::make('advanced')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
|
@ -14,6 +14,7 @@ use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions as FormActions;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
@ -199,7 +200,9 @@ class EditNode extends EditRecord
|
||||
])
|
||||
->required()
|
||||
->maxLength(100),
|
||||
ToggleButtons::make('scheme')
|
||||
Hidden::make('scheme'),
|
||||
Hidden::make('behind_proxy'),
|
||||
ToggleButtons::make('connection')
|
||||
->label(trans('admin/node.ssl'))
|
||||
->columnSpan(1)
|
||||
->inline()
|
||||
@ -214,20 +217,30 @@ class EditNode extends EditRecord
|
||||
|
||||
return '';
|
||||
})
|
||||
->disableOptionWhen(fn (string $value): bool => $value === 'http' && request()->isSecure())
|
||||
->disableOptionWhen(fn (string $value) => $value === 'http' && request()->isSecure())
|
||||
->options([
|
||||
'http' => 'HTTP',
|
||||
'https' => 'HTTPS (SSL)',
|
||||
'https_proxy' => 'HTTPS with (reverse) proxy',
|
||||
])
|
||||
->colors([
|
||||
'http' => 'warning',
|
||||
'https' => 'success',
|
||||
'https_proxy' => 'success',
|
||||
])
|
||||
->icons([
|
||||
'http' => 'tabler-lock-open-off',
|
||||
'https' => 'tabler-lock',
|
||||
'https_proxy' => 'tabler-shield-lock',
|
||||
])
|
||||
->default(fn () => request()->isSecure() ? 'https' : 'http'), ]),
|
||||
->formatStateUsing(fn (Get $get) => $get('scheme') === 'http' ? 'http' : ($get('behind_proxy') ? 'https_proxy' : 'https'))
|
||||
->live()
|
||||
->dehydrated(false)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$set('scheme', $state === 'http' ? 'http' : 'https');
|
||||
$set('behind_proxy', $state === 'https_proxy');
|
||||
}),
|
||||
]),
|
||||
Tab::make('adv')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
->columns([
|
||||
|
@ -12,8 +12,7 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@ -32,18 +31,12 @@ class AllocationsRelationManager extends RelationManager
|
||||
public function setTitle(): string
|
||||
{
|
||||
return trans('admin/server.allocations');
|
||||
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('ip')
|
||||
|
||||
// Non Primary Allocations
|
||||
// ->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->id !== $allocation->server?->allocation_id)
|
||||
|
||||
// All assigned allocations
|
||||
->recordTitleAttribute('address')
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
|
||||
->paginationPageOptions(['10', '20', '50', '100', '200', '500'])
|
||||
->searchable()
|
||||
@ -72,14 +65,14 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label(trans('admin/node.table.ip')),
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\Action::make('create new allocation')
|
||||
Action::make('create new allocation')
|
||||
->label(trans('admin/node.create_allocation'))
|
||||
->form(fn () => [
|
||||
Select::make('allocation_ip')
|
||||
->options(collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->label(trans('admin/node.ip_address'))
|
||||
->inlineLabel()
|
||||
->ipv4()
|
||||
->ip()
|
||||
->helperText(trans('admin/node.ip_help'))
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->live()
|
||||
@ -96,19 +89,15 @@ class AllocationsRelationManager extends RelationManager
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->disabled(fn (Get $get) => empty($get('allocation_ip')))
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports',
|
||||
CreateServer::retrieveValidPorts($this->getOwnerRecord(), $state, $get('allocation_ip')))
|
||||
)
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', CreateServer::retrieveValidPorts($this->getOwnerRecord(), $state, $get('allocation_ip'))))
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord(), $data)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('update node')),
|
||||
]),
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('update node')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
@ -69,6 +70,11 @@ class RoleResource extends Resource
|
||||
->badge()
|
||||
->counts('permissions')
|
||||
->formatStateUsing(fn (Role $role, $state) => $role->isRootAdmin() ? trans('admin/role.all') : $state),
|
||||
TextColumn::make('nodes.name')
|
||||
->icon('tabler-server-2')
|
||||
->label(trans('admin/role.nodes'))
|
||||
->badge()
|
||||
->placeholder(trans('admin/role.all')),
|
||||
TextColumn::make('users_count')
|
||||
->label(trans('admin/role.users'))
|
||||
->counts('users')
|
||||
@ -125,6 +131,14 @@ class RoleResource extends Resource
|
||||
->label(trans('admin/role.permissions'))
|
||||
->content(trans('admin/role.root_admin', ['role' => Role::ROOT_ADMIN]))
|
||||
->visible(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
|
||||
Select::make('nodes')
|
||||
->label(trans('admin/role.nodes'))
|
||||
->multiple()
|
||||
->relationship('nodes', 'name')
|
||||
->searchable(['name', 'fqdn'])
|
||||
->preload()
|
||||
->hint(trans('admin/role.nodes_hint'))
|
||||
->hidden(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,12 @@
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\ServerResource\Pages;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ServerResource extends Resource
|
||||
{
|
||||
@ -36,7 +40,30 @@ class ServerResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getMountCheckboxList(Get $get): CheckboxList
|
||||
{
|
||||
$allowedMounts = Mount::all();
|
||||
$node = $get('node_id');
|
||||
$egg = $get('egg_id');
|
||||
|
||||
if ($node && $egg) {
|
||||
$allowedMounts = $allowedMounts->filter(fn (Mount $mount) => ($mount->nodes->isEmpty() || $mount->nodes->contains($node)) &&
|
||||
($mount->eggs->isEmpty() || $mount->eggs->contains($egg))
|
||||
);
|
||||
}
|
||||
|
||||
return CheckboxList::make('mounts')
|
||||
->label('')
|
||||
->relationship('mounts')
|
||||
->live()
|
||||
->options(fn () => $allowedMounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn () => $allowedMounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]))
|
||||
->helperText(fn () => $allowedMounts->isEmpty() ? trans('admin/server.no_mounts') : null)
|
||||
->bulkToggleable()
|
||||
->columnSpanFull();
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
@ -47,4 +74,13 @@ class ServerResource extends Resource
|
||||
'edit' => Pages\EditServer::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereHas('node', function (Builder $query) {
|
||||
$query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ use Closure;
|
||||
use Exception;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
@ -109,14 +108,20 @@ class CreateServer extends CreateRecord
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-server-2')
|
||||
->selectablePlaceholder(false)
|
||||
->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
|
||||
->default(function () {
|
||||
/** @var ?Node $latestNode */
|
||||
$latestNode = auth()->user()->accessibleNodes()->latest()->first();
|
||||
$this->node = $latestNode;
|
||||
|
||||
return $this->node?->id;
|
||||
})
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
])
|
||||
->live()
|
||||
->relationship('node', 'name')
|
||||
->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id')))
|
||||
->searchable()
|
||||
->preload()
|
||||
->afterStateUpdated(function (Set $set, $state) {
|
||||
@ -183,10 +188,7 @@ class CreateServer extends CreateRecord
|
||||
$set('allocation_additional', null);
|
||||
$set('allocation_additional.needstobeastringhere.extra_allocations', null);
|
||||
})
|
||||
->getOptionLabelFromRecordUsing(
|
||||
fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
|
||||
($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
|
||||
)
|
||||
->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address)
|
||||
->placeholder(function (Get $get) {
|
||||
$node = Node::find($get('node_id'));
|
||||
|
||||
@ -212,7 +214,7 @@ class CreateServer extends CreateRecord
|
||||
->label(trans('admin/server.ip_address'))->inlineLabel()
|
||||
->helperText(trans('admin/server.ip_address_helper'))
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->ipv4()
|
||||
->ip()
|
||||
->live()
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
@ -263,10 +265,7 @@ class CreateServer extends CreateRecord
|
||||
->columnSpan(2)
|
||||
->disabled(fn (Get $get) => $get('../../node_id') === null)
|
||||
->searchable(['ip', 'port', 'ip_alias'])
|
||||
->getOptionLabelFromRecordUsing(
|
||||
fn (Allocation $allocation) => "$allocation->ip:$allocation->port" .
|
||||
($allocation->ip_alias ? " ($allocation->ip_alias)" : '')
|
||||
)
|
||||
->getOptionLabelFromRecordUsing(fn (Allocation $allocation) => $allocation->address)
|
||||
->placeholder(trans('admin/server.select_additional'))
|
||||
->disableOptionsWhenSelectedInSiblingRepeaterItems()
|
||||
->relationship(
|
||||
@ -744,7 +743,7 @@ class CreateServer extends CreateRecord
|
||||
'lg' => 4,
|
||||
])
|
||||
->columnSpan(6)
|
||||
->schema([
|
||||
->schema(fn (Get $get) => [
|
||||
Select::make('select_image')
|
||||
->label(trans('admin/server.image_name'))
|
||||
->live()
|
||||
@ -798,14 +797,7 @@ class CreateServer extends CreateRecord
|
||||
->valueLabel(trans('admin/server.description'))
|
||||
->columnSpanFull(),
|
||||
|
||||
CheckboxList::make('mounts')
|
||||
->label('Mounts')
|
||||
->live()
|
||||
->relationship('mounts')
|
||||
->options(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]) ?? [])
|
||||
->descriptions(fn () => $this->node?->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]) ?? [])
|
||||
->helperText(fn () => $this->node?->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
|
||||
->columnSpanFull(),
|
||||
ServerResource::getMountCheckboxList($get),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ServerResource\Pages;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
|
||||
use App\Enums\SuspendAction;
|
||||
use App\Filament\Admin\Resources\ServerResource;
|
||||
use App\Filament\Admin\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
|
||||
@ -13,7 +13,6 @@ use App\Models\Allocation;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
@ -33,7 +32,6 @@ use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\Actions as FormActions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
@ -53,6 +51,7 @@ use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -137,7 +136,39 @@ class EditServer extends EditRecord
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
])
|
||||
->hintAction(
|
||||
Action::make('view_install_log')
|
||||
->label(trans('admin/server.view_install_log'))
|
||||
//->visible(fn (Server $server) => $server->isFailedInstall())
|
||||
->modalHeading('')
|
||||
->modalSubmitAction(false)
|
||||
->modalFooterActionsAlignment(Alignment::Right)
|
||||
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
|
||||
->form([
|
||||
MonacoEditor::make('logs')
|
||||
->hiddenLabel()
|
||||
->placeholderText(trans('admin/server.no_log'))
|
||||
->formatStateUsing(function (Server $server, DaemonServerRepository $serverRepository) {
|
||||
try {
|
||||
return $serverRepository->setServer($server)->getInstallLogs();
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
->body(trans('admin/server.notifications.log_failed'))
|
||||
->color('warning')
|
||||
->warning()
|
||||
->send();
|
||||
} catch (Exception) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '';
|
||||
})
|
||||
->language('shell')
|
||||
->view('filament.plugins.monaco-editor-logs'),
|
||||
])
|
||||
),
|
||||
|
||||
Textarea::make('description')
|
||||
->label(trans('admin/server.description'))
|
||||
@ -177,7 +208,7 @@ class EditServer extends EditRecord
|
||||
->maxLength(255),
|
||||
Select::make('node_id')
|
||||
->label(trans('admin/server.node'))
|
||||
->relationship('node', 'name')
|
||||
->relationship('node', 'name', fn (Builder $query) => $query->whereIn('id', auth()->user()->accessibleNodes()->pluck('id')))
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 1,
|
||||
@ -651,14 +682,8 @@ class EditServer extends EditRecord
|
||||
]),
|
||||
Tab::make(trans('admin/server.mounts'))
|
||||
->icon('tabler-layers-linked')
|
||||
->schema([
|
||||
CheckboxList::make('mounts')
|
||||
->label('')
|
||||
->relationship('mounts')
|
||||
->options(fn (Server $server) => $server->node->mounts->filter(fn (Mount $mount) => $mount->eggs->contains($server->egg))->mapWithKeys(fn (Mount $mount) => [$mount->id => $mount->name]))
|
||||
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn (Mount $mount) => [$mount->id => "$mount->source -> $mount->target"]))
|
||||
->helperText(fn (Server $server) => $server->node->mounts->isNotEmpty() ? '' : trans('admin/server.no_mounts'))
|
||||
->columnSpanFull(),
|
||||
->schema(fn (Get $get) => [
|
||||
ServerResource::getMountCheckboxList($get),
|
||||
]),
|
||||
Tab::make(trans('admin/server.databases'))
|
||||
->hidden(fn () => !auth()->user()->can('viewList database'))
|
||||
@ -691,8 +716,8 @@ class EditServer extends EditRecord
|
||||
->requiresConfirmation()
|
||||
->modalIcon('tabler-database-x')
|
||||
->modalHeading(trans('admin/server.delete_db_heading'))
|
||||
->modalSubmitActionLabel(fn (Get $get) => 'Delete ' . $get('database') . '?')
|
||||
->modalDescription(fn (Get $get) => trans('admin/server.delete_db') . $get('database') . '?')
|
||||
->modalSubmitActionLabel(trans('filament-actions::delete.single.label'))
|
||||
->modalDescription(fn (Get $get) => trans('admin/server.delete_db', ['name' => $get('database')]))
|
||||
->action(function (DatabaseManagementService $databaseManagementService, $record) {
|
||||
$databaseManagementService->delete($record);
|
||||
$this->fillForm();
|
||||
@ -808,12 +833,12 @@ class EditServer extends EditRecord
|
||||
Action::make('toggleInstall')
|
||||
->label(trans('admin/server.toggle_install'))
|
||||
->disabled(fn (Server $server) => $server->isSuspended())
|
||||
->modal(fn (Server $server) => $server->status === ServerState::InstallFailed)
|
||||
->modal(fn (Server $server) => $server->isFailedInstall())
|
||||
->modalHeading(trans('admin/server.toggle_install_failed_header'))
|
||||
->modalDescription(trans('admin/server.toggle_install_failed_desc'))
|
||||
->modalSubmitActionLabel(trans('admin/server.reinstall'))
|
||||
->action(function (ToggleInstallService $toggleService, ReinstallServerService $reinstallService, Server $server) {
|
||||
if ($server->status === ServerState::InstallFailed) {
|
||||
if ($server->isFailedInstall()) {
|
||||
try {
|
||||
$reinstallService->handle($server);
|
||||
|
||||
@ -826,7 +851,7 @@ class EditServer extends EditRecord
|
||||
} catch (Exception) {
|
||||
Notification::make()
|
||||
->title(trans('admin/server.notifications.reinstall_failed'))
|
||||
->body(trans('admin/server.error_connecting', ['node' => $server->node->name]))
|
||||
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
@ -875,7 +900,7 @@ class EditServer extends EditRecord
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title(trans('admin/server.notifications.server_suspension'))
|
||||
->body(trans('admin/server.error_connecting', ['node' => $server->node->name]))
|
||||
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
@ -897,7 +922,7 @@ class EditServer extends EditRecord
|
||||
Notification::make()
|
||||
->warning()
|
||||
->title(trans('admin/server.notifications.server_suspension'))
|
||||
->body(trans('admin/server.error_connecting', ['node' => $server->node->name]))
|
||||
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
@ -962,7 +987,7 @@ class EditServer extends EditRecord
|
||||
} catch (Exception) {
|
||||
Notification::make()
|
||||
->title(trans('admin/server.notifications.reinstall_failed'))
|
||||
->body(trans('admin/server.error_connecting', ['node' => $server->node->name]))
|
||||
->body(trans('admin/server.notifications.error_connecting', ['node' => $server->node->name]))
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
@ -1078,7 +1103,7 @@ class EditServer extends EditRecord
|
||||
$data['description'] = '';
|
||||
}
|
||||
|
||||
unset($data['docker'], $data['status']);
|
||||
unset($data['docker'], $data['status'], $data['allocation_id']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\AssociateAction;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DissociateAction;
|
||||
use Filament\Tables\Actions\DissociateBulkAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@ -34,15 +35,18 @@ class AllocationsRelationManager extends RelationManager
|
||||
{
|
||||
return $table
|
||||
->selectCurrentPageOnly()
|
||||
->recordTitleAttribute('ip')
|
||||
->recordTitle(fn (Allocation $allocation) => "$allocation->ip:$allocation->port")
|
||||
->recordTitleAttribute('address')
|
||||
->recordTitle(fn (Allocation $allocation) => $allocation->address)
|
||||
->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id)
|
||||
->inverseRelationship('server')
|
||||
->heading(trans('admin/server.allocations'))
|
||||
->columns([
|
||||
TextColumn::make('ip')->label(trans('admin/server.ip_address')),
|
||||
TextColumn::make('port')->label(trans('admin/server.port')),
|
||||
TextInputColumn::make('ip_alias')->label(trans('admin/server.alias')),
|
||||
TextColumn::make('ip')
|
||||
->label(trans('admin/server.ip_address')),
|
||||
TextColumn::make('port')
|
||||
->label(trans('admin/server.port')),
|
||||
TextInputColumn::make('ip_alias')
|
||||
->label(trans('admin/server.alias')),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
@ -58,8 +62,11 @@ class AllocationsRelationManager extends RelationManager
|
||||
])
|
||||
->actions([
|
||||
Action::make('make-primary')
|
||||
->label(trans('admin/server.make_primary'))
|
||||
->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords())
|
||||
->label(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id ? '' : trans('admin/server.make_primary')),
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id),
|
||||
DissociateAction::make()
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id),
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()->label(trans('admin/server.create_allocation'))
|
||||
@ -69,7 +76,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->options(collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->label(trans('admin/server.ip_address'))
|
||||
->inlineLabel()
|
||||
->ipv4()
|
||||
->ip()
|
||||
->live()
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->required(),
|
||||
@ -85,9 +92,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->disabled(fn (Get $get) => empty($get('allocation_ip')))
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports',
|
||||
CreateServer::retrieveValidPorts($this->getOwnerRecord()->node, $state, $get('allocation_ip')))
|
||||
)
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', CreateServer::retrieveValidPorts($this->getOwnerRecord()->node, $state, $get('allocation_ip'))))
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
|
@ -6,15 +6,22 @@ use App\Enums\ServerResourceType;
|
||||
use App\Filament\App\Resources\ServerResource;
|
||||
use App\Filament\Components\Tables\Columns\ServerEntryColumn;
|
||||
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\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\ColumnGroup;
|
||||
use Filament\Tables\Columns\Layout\Stack;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class ListServers extends ListRecords
|
||||
{
|
||||
@ -24,12 +31,51 @@ class ListServers extends ListRecords
|
||||
|
||||
public const WARNING_THRESHOLD = 0.7;
|
||||
|
||||
private DaemonPowerRepository $daemonPowerRepository;
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->daemonPowerRepository = new DaemonPowerRepository();
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$baseQuery = auth()->user()->accessibleServers();
|
||||
|
||||
$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 = [
|
||||
TextColumn::make('condition')
|
||||
ContextMenuTextColumn::make('condition')
|
||||
->label('')
|
||||
->default('unknown')
|
||||
->wrap()
|
||||
@ -37,20 +83,24 @@ class ListServers extends ListRecords
|
||||
->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()),
|
||||
->color(fn (Server $server) => $server->condition->getColor())
|
||||
->contextMenuActions($menuOptions)
|
||||
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
|
||||
];
|
||||
|
||||
$viewTwo = [
|
||||
TextColumn::make('name')
|
||||
ContextMenuTextColumn::make('name')
|
||||
->label('')
|
||||
->size('md')
|
||||
->searchable(),
|
||||
TextColumn::make('')
|
||||
->searchable()
|
||||
->contextMenuActions($menuOptions)
|
||||
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
|
||||
ContextMenuTextColumn::make('allocation.address')
|
||||
->label('')
|
||||
->badge()
|
||||
->copyable(request()->isSecure())
|
||||
->copyMessage(fn (Server $server, string $state) => 'Copied ' . $server->allocation->address)
|
||||
->state(fn (Server $server) => $server->allocation->address),
|
||||
->contextMenuActions($menuOptions)
|
||||
->enableContextMenu(fn (Server $server) => !$server->isInConflictState()),
|
||||
];
|
||||
|
||||
$viewThree = [
|
||||
@ -190,4 +240,25 @@ class ListServers extends ListRecords
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
#[On('powerAction')]
|
||||
public function powerAction(Server $server, string $action): void
|
||||
{
|
||||
try {
|
||||
$this->daemonPowerRepository->setServer($server)->send($action);
|
||||
|
||||
Notification::make()
|
||||
->title('Power Action')
|
||||
->body($action . ' sent to ' . $server->name)
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$this->redirect(self::getUrl(['activeTab' => $this->activeTab]));
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title(trans('exceptions.node.error_connecting', ['node' => $server->node->name]))
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
@ -368,26 +369,48 @@ class EditProfile extends BaseEditProfile
|
||||
Section::make(trans('profile.console'))
|
||||
->collapsible()
|
||||
->icon('tabler-brand-tabler')
|
||||
->columns(4)
|
||||
->schema([
|
||||
TextInput::make('console_rows')
|
||||
->label(trans('profile.rows'))
|
||||
TextInput::make('console_font_size')
|
||||
->label(trans('profile.font_size'))
|
||||
->columnSpan(1)
|
||||
->minValue(1)
|
||||
->numeric()
|
||||
->required()
|
||||
->columnSpan(1)
|
||||
->default(30),
|
||||
->default(14),
|
||||
Select::make('console_font')
|
||||
->label(trans('profile.font'))
|
||||
->options(fn () => get_fonts(storage_path('app\public\fonts')))
|
||||
->required()
|
||||
->options(function () {
|
||||
$fonts = [
|
||||
'monospace' => 'monospace', //default
|
||||
];
|
||||
|
||||
if (!Storage::disk('public')->exists('fonts')) {
|
||||
Storage::disk('public')->makeDirectory('fonts');
|
||||
$this->fillForm();
|
||||
}
|
||||
|
||||
foreach (Storage::disk('public')->allFiles('fonts') as $file) {
|
||||
$fileInfo = pathinfo($file);
|
||||
|
||||
if ($fileInfo['extension'] === 'ttf') {
|
||||
$fonts[$fileInfo['filename']] = $fileInfo['filename'];
|
||||
}
|
||||
}
|
||||
|
||||
return $fonts;
|
||||
})
|
||||
->reactive()
|
||||
->default('monospace')
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('font_preview', $state)),
|
||||
Placeholder::make('font_preview')
|
||||
->label('Preview')
|
||||
->label(trans('profile.font_preview'))
|
||||
->columnSpan(2)
|
||||
->content(function (Get $get) {
|
||||
$fontName = $get('console_font') ?? 'No font selected.';
|
||||
|
||||
$fontUrl = asset("fonts/{$fontName}.ttf");
|
||||
$fontName = $get('console_font') ?? 'monospace';
|
||||
$fontSize = $get('console_font_size') . 'px';
|
||||
$fontUrl = asset("storage/fonts/{$fontName}.ttf");
|
||||
|
||||
return new HtmlString(<<<HTML
|
||||
<style>
|
||||
@ -405,13 +428,24 @@ class EditProfile extends BaseEditProfile
|
||||
<span class="preview-text">The quick blue pelican jumps over the lazy pterodactyl. :)</span>
|
||||
HTML);
|
||||
}),
|
||||
TextInput::make('console_font_size')
|
||||
->label(trans('profile.font_size'))
|
||||
->columnSpan(1)
|
||||
TextInput::make('console_graph_period')
|
||||
->label(trans('profile.graph_period'))
|
||||
->suffix(trans('profile.seconds'))
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('profile.graph_period_helper'))
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->default(30)
|
||||
->minValue(10)
|
||||
->maxValue(120)
|
||||
->required(),
|
||||
TextInput::make('console_rows')
|
||||
->label(trans('profile.rows'))
|
||||
->minValue(1)
|
||||
->numeric()
|
||||
->required()
|
||||
->default(14),
|
||||
->columnSpan(2)
|
||||
->default(30),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
@ -477,6 +511,7 @@ class EditProfile extends BaseEditProfile
|
||||
'console_font' => $data['console_font'],
|
||||
'console_font_size' => $data['console_font_size'],
|
||||
'console_rows' => $data['console_rows'],
|
||||
'console_graph_period' => $data['console_graph_period'],
|
||||
'dashboard_layout' => $data['dashboard_layout'],
|
||||
];
|
||||
|
||||
@ -490,9 +525,10 @@ class EditProfile extends BaseEditProfile
|
||||
{
|
||||
$moarbetterdata = json_decode($data['customization'], true);
|
||||
|
||||
$data['console_font'] = $moarbetterdata['console_font'] ?? 'ComicMono';
|
||||
$data['console_font'] = $moarbetterdata['console_font'] ?? 'monospace';
|
||||
$data['console_font_size'] = $moarbetterdata['console_font_size'] ?? 14;
|
||||
$data['console_rows'] = $moarbetterdata['console_rows'] ?? 30;
|
||||
$data['console_graph_period'] = $moarbetterdata['console_graph_period'] ?? 30;
|
||||
$data['dashboard_layout'] = $moarbetterdata['dashboard_layout'] ?? 'grid';
|
||||
|
||||
return $data;
|
||||
|
@ -60,11 +60,22 @@ class Login extends BaseLogin
|
||||
return null;
|
||||
}
|
||||
|
||||
$isValidToken = $this->google2FA->verifyKey(
|
||||
$user->totp_secret,
|
||||
$token,
|
||||
Config::integer('panel.auth.2fa.window'),
|
||||
);
|
||||
$isValidToken = false;
|
||||
if (strlen($token) === $this->google2FA->getOneTimePasswordLength()) {
|
||||
$isValidToken = $this->google2FA->verifyKey(
|
||||
$user->totp_secret,
|
||||
$token,
|
||||
Config::integer('panel.auth.2fa.window'),
|
||||
);
|
||||
} else {
|
||||
foreach ($user->recoveryTokens as $recoveryToken) {
|
||||
if (password_verify($token, $recoveryToken->token)) {
|
||||
$isValidToken = true;
|
||||
$recoveryToken->delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isValidToken) {
|
||||
// Buffer to prevent bruteforce
|
||||
@ -111,7 +122,9 @@ class Login extends BaseLogin
|
||||
{
|
||||
return TextInput::make('2fa')
|
||||
->label(trans('auth.two-factor-code'))
|
||||
->hidden(fn () => !$this->verifyTwoFactor)
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip(trans('auth.two-factor-hint'))
|
||||
->visible(fn () => $this->verifyTwoFactor)
|
||||
->required()
|
||||
->live();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use App\Filament\Server\Widgets\ServerConsole;
|
||||
use App\Filament\Server\Widgets\ServerCpuChart;
|
||||
use App\Filament\Server\Widgets\ServerMemoryChart;
|
||||
use App\Filament\Server\Widgets\ServerNetworkChart;
|
||||
use App\Filament\Server\Widgets\ServerOverview;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\Permission;
|
||||
@ -116,7 +117,7 @@ class Console extends Page
|
||||
$allWidgets = array_merge($allWidgets, [
|
||||
ServerCpuChart::class,
|
||||
ServerMemoryChart::class,
|
||||
//ServerNetworkChart::class, TODO: convert units.
|
||||
ServerNetworkChart::class,
|
||||
]);
|
||||
|
||||
$allWidgets = array_merge($allWidgets, static::$customWidgets[ConsoleWidgetPosition::Bottom->value] ?? []);
|
||||
|
@ -46,17 +46,10 @@ class ListFiles extends ListRecords
|
||||
protected static string $resource = FileResource::class;
|
||||
|
||||
#[Locked]
|
||||
public string $path;
|
||||
public string $path = '/';
|
||||
|
||||
private DaemonFileRepository $fileRepository;
|
||||
|
||||
public function mount(?string $path = null): void
|
||||
{
|
||||
parent::mount();
|
||||
|
||||
$this->path = $path ?? '/';
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
$resource = static::getResource();
|
||||
@ -515,8 +508,9 @@ class ListFiles extends ListRecords
|
||||
->form([
|
||||
TextInput::make('searchTerm')
|
||||
->placeholder('Enter a search term, e.g. *.txt')
|
||||
->required()
|
||||
->regex('/^[^*]*\*?[^*]*$/')
|
||||
->minLength(3),
|
||||
->minValue(3),
|
||||
])
|
||||
->action(fn ($data) => redirect(SearchFiles::getUrl([
|
||||
'searchTerm' => $data['searchTerm'],
|
||||
|
@ -12,6 +12,7 @@ use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Url;
|
||||
|
||||
class SearchFiles extends ListRecords
|
||||
{
|
||||
@ -22,15 +23,8 @@ class SearchFiles extends ListRecords
|
||||
#[Locked]
|
||||
public string $searchTerm;
|
||||
|
||||
#[Locked]
|
||||
public string $path;
|
||||
|
||||
public function mount(?string $searchTerm = null, ?string $path = null): void
|
||||
{
|
||||
parent::mount();
|
||||
$this->searchTerm = $searchTerm;
|
||||
$this->path = $path ?? '/';
|
||||
}
|
||||
#[Url]
|
||||
public string $path = '/';
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
|
@ -19,8 +19,8 @@ use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
@ -83,6 +83,35 @@ class UserResource extends Resource
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$tabs = [];
|
||||
$permissionsArray = [];
|
||||
|
||||
foreach (Permission::permissionData() as $data) {
|
||||
$options = [];
|
||||
$descriptions = [];
|
||||
|
||||
foreach ($data['permissions'] as $permission) {
|
||||
$options[$permission] = str($permission)->headline();
|
||||
$descriptions[$permission] = trans('server/users.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$permissionsArray[$data['name']][] = $permission;
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make(str($data['name'])->headline())
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.' . $data['name'] . '_desc'))
|
||||
->icon($data['icon'])
|
||||
->schema([
|
||||
CheckboxList::make($data['name'])
|
||||
->label('')
|
||||
->bulkToggleable()
|
||||
->columns(2)
|
||||
->options($options)
|
||||
->descriptions($descriptions),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return $table
|
||||
->paginated(false)
|
||||
->searchable(false)
|
||||
@ -158,69 +187,8 @@ class UserResource extends Resource
|
||||
Actions::make([
|
||||
Action::make('assignAll')
|
||||
->label('Assign All')
|
||||
->action(function (Set $set) {
|
||||
$permissions = [
|
||||
'control' => [
|
||||
'console',
|
||||
'start',
|
||||
'stop',
|
||||
'restart',
|
||||
],
|
||||
'user' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'file' => [
|
||||
'read',
|
||||
'read-content',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'archive',
|
||||
'sftp',
|
||||
],
|
||||
'backup' => [
|
||||
'read',
|
||||
'create',
|
||||
'delete',
|
||||
'download',
|
||||
'restore',
|
||||
],
|
||||
'allocation' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'startup' => [
|
||||
'read',
|
||||
'update',
|
||||
'docker-image',
|
||||
],
|
||||
'database' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'view_password',
|
||||
],
|
||||
'schedule' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'settings' => [
|
||||
'rename',
|
||||
'reinstall',
|
||||
],
|
||||
'activity' => [
|
||||
'read',
|
||||
],
|
||||
];
|
||||
|
||||
->action(function (Set $set) use ($permissionsArray) {
|
||||
$permissions = $permissionsArray;
|
||||
foreach ($permissions as $key => $value) {
|
||||
$allValues = array_unique($value);
|
||||
$set($key, $allValues);
|
||||
@ -235,264 +203,25 @@ class UserResource extends Resource
|
||||
]),
|
||||
Tabs::make()
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Tab::make('Console')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.control_desc'))
|
||||
->icon('tabler-terminal-2')
|
||||
->schema([
|
||||
CheckboxList::make('control')
|
||||
->formatStateUsing(function (User $user, Set $set) use ($server) {
|
||||
$permissionsArray = $server->subusers->where('user_id', $user->id)->first()->permissions;
|
||||
|
||||
$transformedPermissions = [];
|
||||
|
||||
foreach ($permissionsArray as $permission) {
|
||||
[$group, $action] = explode('.', $permission, 2);
|
||||
$transformedPermissions[$group][] = $action;
|
||||
}
|
||||
|
||||
foreach ($transformedPermissions as $key => $value) {
|
||||
$set($key, $value);
|
||||
}
|
||||
|
||||
return $transformedPermissions['control'] ?? [];
|
||||
})
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'console' => 'Console',
|
||||
'start' => 'Start',
|
||||
'stop' => 'Stop',
|
||||
'restart' => 'Restart',
|
||||
])
|
||||
->descriptions([
|
||||
'console' => trans('server/users.permissions.control_console'),
|
||||
'start' => trans('server/users.permissions.control_start'),
|
||||
'stop' => trans('server/users.permissions.control_stop'),
|
||||
'restart' => trans('server/users.permissions.control_restart'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('User')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.user_desc'))
|
||||
->icon('tabler-users')
|
||||
->schema([
|
||||
CheckboxList::make('user')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.user_create'),
|
||||
'read' => trans('server/users.permissions.user_read'),
|
||||
'update' => trans('server/users.permissions.user_update'),
|
||||
'delete' => trans('server/users.permissions.user_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('File')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.file_desc'))
|
||||
->icon('tabler-folders')
|
||||
->schema([
|
||||
CheckboxList::make('file')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'read-content' => 'Read Content',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'archive' => 'Archive',
|
||||
'sftp' => 'SFTP',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.file_create'),
|
||||
'read' => trans('server/users.permissions.file_read'),
|
||||
'read-content' => trans('server/users.permissions.file_read_content'),
|
||||
'update' => trans('server/users.permissions.file_update'),
|
||||
'delete' => trans('server/users.permissions.file_delete'),
|
||||
'archive' => trans('server/users.permissions.file_archive'),
|
||||
'sftp' => trans('server/users.permissions.file_sftp'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Backup')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.backup_desc'))
|
||||
->icon('tabler-download')
|
||||
->schema([
|
||||
CheckboxList::make('backup')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'delete' => 'Delete',
|
||||
'download' => 'Download',
|
||||
'restore' => 'Restore',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.backup_create'),
|
||||
'read' => trans('server/users.permissions.backup_read'),
|
||||
'delete' => trans('server/users.permissions.backup_delete'),
|
||||
'download' => trans('server/users.permissions.backup_download'),
|
||||
'restore' => trans('server/users.permissions.backup_restore'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Allocation')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.allocation_desc'))
|
||||
->icon('tabler-network')
|
||||
->schema([
|
||||
CheckboxList::make('allocation')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.allocation_read'),
|
||||
'create' => trans('server/users.permissions.allocation_create'),
|
||||
'update' => trans('server/users.permissions.allocation_update'),
|
||||
'delete' => trans('server/users.permissions.allocation_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Startup')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.startup_desc'))
|
||||
->icon('tabler-question-mark')
|
||||
->schema([
|
||||
CheckboxList::make('startup')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'update' => 'Update',
|
||||
'docker-image' => 'Docker Image',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.startup_read'),
|
||||
'update' => trans('server/users.permissions.startup_update'),
|
||||
'docker-image' => trans('server/users.permissions.startup_docker_image'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Database')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.database_desc'))
|
||||
->icon('tabler-database')
|
||||
->schema([
|
||||
CheckboxList::make('database')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'view_password' => 'View Password',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.database_read'),
|
||||
'create' => trans('server/users.permissions.database_create'),
|
||||
'update' => trans('server/users.permissions.database_update'),
|
||||
'delete' => trans('server/users.permissions.database_delete'),
|
||||
'view_password' => trans('server/users.permissions.database_view_password'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Schedule')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.schedule_desc'))
|
||||
->icon('tabler-clock')
|
||||
->schema([
|
||||
CheckboxList::make('schedule')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.schedule_read'),
|
||||
'create' => trans('server/users.permissions.schedule_create'),
|
||||
'update' => trans('server/users.permissions.schedule_update'),
|
||||
'delete' => trans('server/users.permissions.schedule_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Settings')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.settings_desc'))
|
||||
->icon('tabler-settings')
|
||||
->schema([
|
||||
CheckboxList::make('settings')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'rename' => 'Rename',
|
||||
'reinstall' => 'Reinstall',
|
||||
])
|
||||
->descriptions([
|
||||
'rename' => trans('server/users.permissions.setting_rename'),
|
||||
'reinstall' => trans('server/users.permissions.setting_reinstall'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Activity')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.activity_desc'))
|
||||
->icon('tabler-stack')
|
||||
->schema([
|
||||
CheckboxList::make('activity')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.activity_read'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
->schema($tabs),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->mutateRecordDataUsing(function ($data, User $user) use ($server) {
|
||||
$permissionsArray = $server->subusers->where('user_id', $user->id)->first()->permissions;
|
||||
|
||||
$transformedPermissions = [];
|
||||
|
||||
foreach ($permissionsArray as $permission) {
|
||||
[$group, $action] = explode('.', $permission, 2);
|
||||
$transformedPermissions[$group][] = $action;
|
||||
}
|
||||
|
||||
foreach ($transformedPermissions as $key => $value) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@ use App\Services\Subusers\SubuserCreationService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions as assignAll;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Actions as assignAll;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Section;
|
||||
@ -32,6 +32,35 @@ class ListUsers extends ListRecords
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$tabs = [];
|
||||
$permissionsArray = [];
|
||||
|
||||
foreach (Permission::permissionData() as $data) {
|
||||
$options = [];
|
||||
$descriptions = [];
|
||||
|
||||
foreach ($data['permissions'] as $permission) {
|
||||
$options[$permission] = str($permission)->headline();
|
||||
$descriptions[$permission] = trans('server/users.permissions.' . $data['name'] . '_' . str($permission)->replace('-', '_'));
|
||||
$permissionsArray[$data['name']][] = $permission;
|
||||
}
|
||||
|
||||
$tabs[] = Tab::make(str($data['name'])->headline())
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.' . $data['name'] . '_desc'))
|
||||
->icon($data['icon'])
|
||||
->schema([
|
||||
CheckboxList::make($data['name'])
|
||||
->label('')
|
||||
->bulkToggleable()
|
||||
->columns(2)
|
||||
->options($options)
|
||||
->descriptions($descriptions),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
Actions\CreateAction::make('invite')
|
||||
->label('Invite User')
|
||||
@ -60,72 +89,10 @@ class ListUsers extends ListRecords
|
||||
assignAll::make([
|
||||
Action::make('assignAll')
|
||||
->label('Assign All')
|
||||
->action(function (Set $set, Get $get) {
|
||||
$permissions = [
|
||||
'control' => [
|
||||
'console',
|
||||
'start',
|
||||
'stop',
|
||||
'restart',
|
||||
],
|
||||
'user' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'file' => [
|
||||
'read',
|
||||
'read-content',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'archive',
|
||||
'sftp',
|
||||
],
|
||||
'backup' => [
|
||||
'read',
|
||||
'create',
|
||||
'delete',
|
||||
'download',
|
||||
'restore',
|
||||
],
|
||||
'allocation' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'startup' => [
|
||||
'read',
|
||||
'update',
|
||||
'docker-image',
|
||||
],
|
||||
'database' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'view_password',
|
||||
],
|
||||
'schedule' => [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'settings' => [
|
||||
'rename',
|
||||
'reinstall',
|
||||
],
|
||||
'activity' => [
|
||||
'read',
|
||||
],
|
||||
];
|
||||
|
||||
->action(function (Set $set, Get $get) use ($permissionsArray) {
|
||||
$permissions = $permissionsArray;
|
||||
foreach ($permissions as $key => $value) {
|
||||
$currentValues = $get($key) ?? [];
|
||||
$allValues = array_unique(array_merge($currentValues, $value));
|
||||
$allValues = array_unique($value);
|
||||
$set($key, $allValues);
|
||||
}
|
||||
}),
|
||||
@ -138,247 +105,7 @@ class ListUsers extends ListRecords
|
||||
]),
|
||||
Tabs::make()
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Tab::make('Console')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.control_desc'))
|
||||
->icon('tabler-terminal-2')
|
||||
->schema([
|
||||
CheckboxList::make('control')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'console' => 'Console',
|
||||
'start' => 'Start',
|
||||
'stop' => 'Stop',
|
||||
'restart' => 'Restart',
|
||||
])
|
||||
->descriptions([
|
||||
'console' => trans('server/users.permissions.control_console'),
|
||||
'start' => trans('server/users.permissions.control_start'),
|
||||
'stop' => trans('server/users.permissions.control_stop'),
|
||||
'restart' => trans('server/users.permissions.control_restart'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('User')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.user_desc'))
|
||||
->icon('tabler-users')
|
||||
->schema([
|
||||
CheckboxList::make('user')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.user_create'),
|
||||
'read' => trans('server/users.permissions.user_read'),
|
||||
'update' => trans('server/users.permissions.user_update'),
|
||||
'delete' => trans('server/users.permissions.user_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('File')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.file_desc'))
|
||||
->icon('tabler-folders')
|
||||
->schema([
|
||||
CheckboxList::make('file')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'read-content' => 'Read Content',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'archive' => 'Archive',
|
||||
'sftp' => 'SFTP',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.file_create'),
|
||||
'read' => trans('server/users.permissions.file_read'),
|
||||
'read-content' => trans('server/users.permissions.file_read_content'),
|
||||
'update' => trans('server/users.permissions.file_update'),
|
||||
'delete' => trans('server/users.permissions.file_delete'),
|
||||
'archive' => trans('server/users.permissions.file_archive'),
|
||||
'sftp' => trans('server/users.permissions.file_sftp'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Backup')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.backup_desc'))
|
||||
->icon('tabler-download')
|
||||
->schema([
|
||||
CheckboxList::make('backup')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'delete' => 'Delete',
|
||||
'download' => 'Download',
|
||||
'restore' => 'Restore',
|
||||
])
|
||||
->descriptions([
|
||||
'create' => trans('server/users.permissions.backup_create'),
|
||||
'read' => trans('server/users.permissions.backup_read'),
|
||||
'delete' => trans('server/users.permissions.backup_delete'),
|
||||
'download' => trans('server/users.permissions.backup_download'),
|
||||
'restore' => trans('server/users.permissions.backup_restore'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Allocation')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.allocation_desc'))
|
||||
->icon('tabler-network')
|
||||
->schema([
|
||||
CheckboxList::make('allocation')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.allocation_read'),
|
||||
'create' => trans('server/users.permissions.allocation_create'),
|
||||
'update' => trans('server/users.permissions.allocation_update'),
|
||||
'delete' => trans('server/users.permissions.allocation_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Startup')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.startup_desc'))
|
||||
->icon('tabler-question-mark')
|
||||
->schema([
|
||||
CheckboxList::make('startup')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'update' => 'Update',
|
||||
'docker-image' => 'Docker Image',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.startup_read'),
|
||||
'update' => trans('server/users.permissions.startup_update'),
|
||||
'docker-image' => trans('server/users.permissions.startup_docker_image'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Database')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.database_desc'))
|
||||
->icon('tabler-database')
|
||||
->schema([
|
||||
CheckboxList::make('database')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'view_password' => 'View Password',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.database_read'),
|
||||
'create' => trans('server/users.permissions.database_create'),
|
||||
'update' => trans('server/users.permissions.database_update'),
|
||||
'delete' => trans('server/users.permissions.database_delete'),
|
||||
'view_password' => trans('server/users.permissions.database_view_password'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Schedule')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.schedule_desc'))
|
||||
->icon('tabler-clock')
|
||||
->schema([
|
||||
CheckboxList::make('schedule')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.schedule_read'),
|
||||
'create' => trans('server/users.permissions.schedule_create'),
|
||||
'update' => trans('server/users.permissions.schedule_update'),
|
||||
'delete' => trans('server/users.permissions.schedule_delete'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Settings')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.settings_desc'))
|
||||
->icon('tabler-settings')
|
||||
->schema([
|
||||
CheckboxList::make('settings')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'rename' => 'Rename',
|
||||
'reinstall' => 'Reinstall',
|
||||
])
|
||||
->descriptions([
|
||||
'rename' => trans('server/users.permissions.setting_rename'),
|
||||
'reinstall' => trans('server/users.permissions.setting_reinstall'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Activity')
|
||||
->schema([
|
||||
Section::make()
|
||||
->description(trans('server/users.permissions.activity_desc'))
|
||||
->icon('tabler-stack')
|
||||
->schema([
|
||||
CheckboxList::make('activity')
|
||||
->bulkToggleable()
|
||||
->label('')
|
||||
->columns(2)
|
||||
->options([
|
||||
'read' => 'Read',
|
||||
])
|
||||
->descriptions([
|
||||
'read' => trans('server/users.permissions.activity_read'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
->schema($tabs),
|
||||
]),
|
||||
])
|
||||
->modalHeading('Invite User')
|
||||
|
@ -4,6 +4,7 @@ namespace App\Filament\Server\Widgets;
|
||||
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Number;
|
||||
@ -16,10 +17,19 @@ class ServerCpuChart extends ChartWidget
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return !$server->isInConflictState() && !$server->retrieveStatus()->isOffline();
|
||||
}
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$period = auth()->user()->getCustomization()['console_graph_period'] ?? 30;
|
||||
$cpu = collect(cache()->get("servers.{$this->server->id}.cpu_absolute"))
|
||||
->slice(-10)
|
||||
->slice(-$period)
|
||||
->map(fn ($value, $key) => [
|
||||
'cpu' => Number::format($value, maxPrecision: 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
|
@ -4,6 +4,7 @@ namespace App\Filament\Server\Widgets;
|
||||
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Number;
|
||||
@ -16,9 +17,19 @@ class ServerMemoryChart extends ChartWidget
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return !$server->isInConflictState() && !$server->retrieveStatus()->isOffline();
|
||||
}
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$memUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))->slice(-10)
|
||||
$period = auth()->user()->getCustomization()['console_graph_period'] ?? 30;
|
||||
$memUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))
|
||||
->slice(-$period)
|
||||
->map(fn ($value, $key) => [
|
||||
'memory' => Number::format(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, maxPrecision: 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
|
@ -4,61 +4,72 @@ namespace App\Filament\Server\Widgets;
|
||||
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
|
||||
class ServerNetworkChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Network';
|
||||
|
||||
protected static ?string $pollingInterval = '1s';
|
||||
|
||||
protected static ?string $maxHeight = '300px';
|
||||
protected static ?string $maxHeight = '200px';
|
||||
|
||||
public ?Server $server = null;
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return !$server->isInConflictState() && !$server->retrieveStatus()->isOffline();
|
||||
}
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$data = cache()->get("servers.{$this->server->id}.network");
|
||||
$previous = null;
|
||||
|
||||
$rx = collect($data)
|
||||
->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'rx' => $value->rx_bytes,
|
||||
'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'),
|
||||
])
|
||||
->all();
|
||||
$period = auth()->user()->getCustomization()['console_graph_period'] ?? 30;
|
||||
$net = collect(cache()->get("servers.{$this->server->id}.network"))
|
||||
->slice(-$period)
|
||||
->map(function ($current, $timestamp) use (&$previous) {
|
||||
$net = null;
|
||||
|
||||
$tx = collect($data)
|
||||
->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'tx' => $value->rx_bytes,
|
||||
'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'),
|
||||
])
|
||||
if ($previous !== null) {
|
||||
$net = [
|
||||
'rx' => max(0, $current->rx_bytes - $previous->rx_bytes),
|
||||
'tx' => max(0, $current->tx_bytes - $previous->tx_bytes),
|
||||
'timestamp' => Carbon::createFromTimestamp($timestamp, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
$previous = $current;
|
||||
|
||||
return $net;
|
||||
})
|
||||
->all();
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Inbound',
|
||||
'data' => array_column($rx, 'rx'),
|
||||
'data' => array_column($net, 'rx'),
|
||||
'backgroundColor' => [
|
||||
'rgba(96, 165, 250, 0.3)',
|
||||
'rgba(100, 255, 105, 0.5)',
|
||||
],
|
||||
'tension' => '0.3',
|
||||
'fill' => true,
|
||||
],
|
||||
[
|
||||
'label' => 'Outbound',
|
||||
'data' => array_column($tx, 'tx'),
|
||||
'data' => array_column($net, 'tx'),
|
||||
'backgroundColor' => [
|
||||
'rgba(165, 96, 250, 0.3)',
|
||||
'rgba(96, 165, 250, 0.3)',
|
||||
],
|
||||
'tension' => '0.3',
|
||||
'fill' => true,
|
||||
],
|
||||
],
|
||||
'labels' => array_column($rx, 'timestamp'),
|
||||
'labels' => array_column($net, 'timestamp'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -69,25 +80,38 @@ class ServerNetworkChart extends ChartWidget
|
||||
|
||||
protected function getOptions(): RawJs
|
||||
{
|
||||
// TODO: use "panel.use_binary_prefix" config value
|
||||
return RawJs::make(<<<'JS'
|
||||
{
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
display: false, //debug
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
ticks: {
|
||||
display: true,
|
||||
callback(value) {
|
||||
const bytes = typeof value === 'string' ? parseInt(value, 10) : value;
|
||||
|
||||
if (bytes < 1) return '0 Bytes';
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const number = Number((bytes / Math.pow(1024, i)).toFixed(2));
|
||||
|
||||
return `${number} ${['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'][i]}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
JS);
|
||||
}
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
$lastData = collect(cache()->get("servers.{$this->server->id}.network"))->last();
|
||||
|
||||
return 'Network - ↓' . convert_bytes_to_readable($lastData->rx_bytes ?? 0) . ' - ↑' . convert_bytes_to_readable($lastData->tx_bytes ?? 0);
|
||||
}
|
||||
}
|
||||
|
@ -18,26 +18,7 @@ class StoreNodeRequest extends ApplicationApiRequest
|
||||
*/
|
||||
public function rules(?array $rules = null): array
|
||||
{
|
||||
return collect($rules ?? Node::getRules())->only([
|
||||
'public',
|
||||
'name',
|
||||
'description',
|
||||
'fqdn',
|
||||
'scheme',
|
||||
'behind_proxy',
|
||||
'maintenance_mode',
|
||||
'memory',
|
||||
'memory_overallocate',
|
||||
'disk',
|
||||
'disk_overallocate',
|
||||
'cpu',
|
||||
'cpu_overallocate',
|
||||
'upload_size',
|
||||
'daemon_listen',
|
||||
'daemon_sftp',
|
||||
'daemon_sftp_alias',
|
||||
'daemon_base',
|
||||
])->mapWithKeys(function ($value, $key) {
|
||||
return collect($rules ?? Node::getRules())->mapWithKeys(function ($value, $key) {
|
||||
return [snake_case($key) => $value];
|
||||
})->toArray();
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ class Allocation extends Model
|
||||
protected function address(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => "$this->alias:$this->port",
|
||||
get: fn () => (is_ipv6($this->alias) ? "[$this->alias]" : $this->alias) . ":$this->port",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ use App\Traits\HasValidation;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@ -276,6 +277,11 @@ class Egg extends Model implements Validatable
|
||||
return $this->configFrom->file_denylist;
|
||||
}
|
||||
|
||||
public function mounts(): MorphToMany
|
||||
{
|
||||
return $this->morphToMany(Mount::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all servers associated with this egg.
|
||||
*/
|
||||
|
@ -5,7 +5,7 @@ namespace App\Models;
|
||||
use App\Contracts\Validatable;
|
||||
use App\Traits\HasValidation;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -102,24 +102,24 @@ class Mount extends Model implements Validatable
|
||||
/**
|
||||
* Returns all eggs that have this mount assigned.
|
||||
*/
|
||||
public function eggs(): BelongsToMany
|
||||
public function eggs(): MorphToMany
|
||||
{
|
||||
return $this->belongsToMany(Egg::class);
|
||||
return $this->morphedByMany(Egg::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all nodes that have this mount assigned.
|
||||
*/
|
||||
public function nodes(): BelongsToMany
|
||||
public function nodes(): MorphToMany
|
||||
{
|
||||
return $this->belongsToMany(Node::class);
|
||||
return $this->morphedByMany(Node::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all servers that have this mount assigned.
|
||||
*/
|
||||
public function servers(): BelongsToMany
|
||||
public function servers(): MorphToMany
|
||||
{
|
||||
return $this->belongsToMany(Server::class);
|
||||
return $this->morphedByMany(Server::class, 'mountable');
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MountNode extends Model
|
||||
{
|
||||
protected $table = 'mount_node';
|
||||
|
||||
protected $primaryKey = null;
|
||||
|
||||
public $incrementing = false;
|
||||
}
|
@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
@ -49,6 +50,8 @@ use Symfony\Component\Yaml\Yaml;
|
||||
* @property int|null $servers_count
|
||||
* @property \App\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
|
||||
* @property int|null $allocations_count
|
||||
* @property \App\Models\Role[]|\Illuminate\Database\Eloquent\Collection $roles
|
||||
* @property int|null $roles_count
|
||||
*/
|
||||
class Node extends Model implements Validatable
|
||||
{
|
||||
@ -214,7 +217,7 @@ class Node extends Model implements Validatable
|
||||
],
|
||||
],
|
||||
'allowed_mounts' => $this->mounts->pluck('source')->toArray(),
|
||||
'remote' => route('filament.app.resources...index'),
|
||||
'remote' => config('app.url'),
|
||||
];
|
||||
}
|
||||
|
||||
@ -239,9 +242,9 @@ class Node extends Model implements Validatable
|
||||
return $this->maintenance_mode;
|
||||
}
|
||||
|
||||
public function mounts(): HasManyThrough
|
||||
public function mounts(): MorphToMany
|
||||
{
|
||||
return $this->hasManyThrough(Mount::class, MountNode::class, 'node_id', 'id', 'id', 'mount_id');
|
||||
return $this->morphToMany(Mount::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -268,6 +271,11 @@ class Node extends Model implements Validatable
|
||||
return $this->belongsToMany(DatabaseHost::class);
|
||||
}
|
||||
|
||||
public function roles(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Role::class, NodeRole::class, 'node_id', 'id', 'id', 'role_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean if the node is viable for an additional server to be placed on it.
|
||||
*/
|
||||
@ -396,10 +404,11 @@ class Node extends Model implements Validatable
|
||||
}
|
||||
}
|
||||
|
||||
// Only IPV4
|
||||
$ips = $ips->filter(fn (string $ip) => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false);
|
||||
$ips = $ips->filter(fn (string $ip) => is_ip($ip));
|
||||
|
||||
// TODO: remove later
|
||||
$ips->push('0.0.0.0');
|
||||
$ips->push('::');
|
||||
|
||||
return $ips->unique()->all();
|
||||
});
|
||||
|
12
app/Models/NodeRole.php
Normal file
12
app/Models/NodeRole.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
class NodeRole extends Pivot
|
||||
{
|
||||
protected $table = 'node_role';
|
||||
|
||||
protected $primaryKey = null;
|
||||
}
|
@ -39,7 +39,7 @@ class Permission extends Model implements Validatable
|
||||
|
||||
public const ACTION_DATABASE_DELETE = 'database.delete';
|
||||
|
||||
public const ACTION_DATABASE_VIEW_PASSWORD = 'database.view_password';
|
||||
public const ACTION_DATABASE_VIEW_PASSWORD = 'database.view-password';
|
||||
|
||||
public const ACTION_SCHEDULE_READ = 'schedule.read';
|
||||
|
||||
@ -114,127 +114,6 @@ class Permission extends Model implements Validatable
|
||||
'permission' => ['required', 'string'],
|
||||
];
|
||||
|
||||
/**
|
||||
* All the permissions available on the system. You should use self::permissions()
|
||||
* to retrieve them, and not directly access this array as it is subject to change.
|
||||
*
|
||||
* @see Permission::permissions()
|
||||
*
|
||||
* @var array<array-key, array{
|
||||
* description: string,
|
||||
* keys: array<array-key, string>,
|
||||
* }>
|
||||
*/
|
||||
protected static array $permissions = [
|
||||
'websocket' => [
|
||||
'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.',
|
||||
'keys' => [
|
||||
'connect' => 'Allows a user to connect to the websocket instance for a server to stream the console.',
|
||||
],
|
||||
],
|
||||
|
||||
'control' => [
|
||||
'description' => 'Permissions that control a user\'s ability to control the power state of a server, or send commands.',
|
||||
'keys' => [
|
||||
'console' => 'Allows a user to send commands to the server instance via the console.',
|
||||
'start' => 'Allows a user to start the server if it is stopped.',
|
||||
'stop' => 'Allows a user to stop a server if it is running.',
|
||||
'restart' => 'Allows a user to perform a server restart. This allows them to start the server if it is offline, but not put the server in a completely stopped state.',
|
||||
],
|
||||
],
|
||||
|
||||
'user' => [
|
||||
'description' => 'Permissions that allow a user to manage other subusers on a server. They will never be able to edit their own account, or assign permissions they do not have themselves.',
|
||||
'keys' => [
|
||||
'create' => 'Allows a user to create new subusers for the server.',
|
||||
'read' => 'Allows the user to view subusers and their permissions for the server.',
|
||||
'update' => 'Allows a user to modify other subusers.',
|
||||
'delete' => 'Allows a user to delete a subuser from the server.',
|
||||
],
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'description' => 'Permissions that control a user\'s ability to modify the filesystem for this server.',
|
||||
'keys' => [
|
||||
'create' => 'Allows a user to create additional files and folders via the Panel or direct upload.',
|
||||
'read' => 'Allows a user to view the contents of a directory, but not view the contents of or download files.',
|
||||
'read-content' => 'Allows a user to view the contents of a given file. This will also allow the user to download files.',
|
||||
'update' => 'Allows a user to update the contents of an existing file or directory.',
|
||||
'delete' => 'Allows a user to delete files or directories.',
|
||||
'archive' => 'Allows a user to archive the contents of a directory as well as decompress existing archives on the system.',
|
||||
'sftp' => 'Allows a user to connect to SFTP and manage server files using the other assigned file permissions.',
|
||||
],
|
||||
],
|
||||
|
||||
'backup' => [
|
||||
'description' => 'Permissions that control a user\'s ability to generate and manage server backups.',
|
||||
'keys' => [
|
||||
'create' => 'Allows a user to create new backups for this server.',
|
||||
'read' => 'Allows a user to view all backups that exist for this server.',
|
||||
'delete' => 'Allows a user to remove backups from the system.',
|
||||
'download' => 'Allows a user to download a backup for the server. Danger: this allows a user to access all files for the server in the backup.',
|
||||
'restore' => 'Allows a user to restore a backup for the server. Danger: this allows the user to delete all the server files in the process.',
|
||||
],
|
||||
],
|
||||
|
||||
// Controls permissions for editing or viewing a server's allocations.
|
||||
'allocation' => [
|
||||
'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.',
|
||||
'keys' => [
|
||||
'read' => 'Allows a user to view all allocations currently assigned to this server. Users with any level of access to this server can always view the primary allocation.',
|
||||
'create' => 'Allows a user to assign additional allocations to the server.',
|
||||
'update' => 'Allows a user to change the primary server allocation and attach notes to each allocation.',
|
||||
'delete' => 'Allows a user to delete an allocation from the server.',
|
||||
],
|
||||
],
|
||||
|
||||
// Controls permissions for editing or viewing a server's startup parameters.
|
||||
'startup' => [
|
||||
'description' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.',
|
||||
'keys' => [
|
||||
'read' => 'Allows a user to view the startup variables for a server.',
|
||||
'update' => 'Allows a user to modify the startup variables for the server.',
|
||||
'docker-image' => 'Allows a user to modify the Docker image used when running the server.',
|
||||
],
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'description' => 'Permissions that control a user\'s access to the database management for this server.',
|
||||
'keys' => [
|
||||
'create' => 'Allows a user to create a new database for this server.',
|
||||
'read' => 'Allows a user to view the database associated with this server.',
|
||||
'update' => 'Allows a user to rotate the password on a database instance. If the user does not have the view_password permission they will not see the updated password.',
|
||||
'delete' => 'Allows a user to remove a database instance from this server.',
|
||||
'view_password' => 'Allows a user to view the password associated with a database instance for this server.',
|
||||
],
|
||||
],
|
||||
|
||||
'schedule' => [
|
||||
'description' => 'Permissions that control a user\'s access to the schedule management for this server.',
|
||||
'keys' => [
|
||||
'create' => 'Allows a user to create new schedules for this server.', // task.create-schedule
|
||||
'read' => 'Allows a user to view schedules and the tasks associated with them for this server.', // task.view-schedule, task.list-schedules
|
||||
'update' => 'Allows a user to update schedules and schedule tasks for this server.', // task.edit-schedule, task.queue-schedule, task.toggle-schedule
|
||||
'delete' => 'Allows a user to delete schedules for this server.', // task.delete-schedule
|
||||
],
|
||||
],
|
||||
|
||||
'settings' => [
|
||||
'description' => 'Permissions that control a user\'s access to the settings for this server.',
|
||||
'keys' => [
|
||||
'rename' => 'Allows a user to rename this server and change the description of it.',
|
||||
'reinstall' => 'Allows a user to trigger a reinstall of this server.',
|
||||
],
|
||||
],
|
||||
|
||||
'activity' => [
|
||||
'description' => 'Permissions that control a user\'s access to the server activity logs.',
|
||||
'keys' => [
|
||||
'read' => 'Allows a user to view the activity logs for the server.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@ -242,11 +121,92 @@ class Permission extends Model implements Validatable
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* All the permissions available on the system.
|
||||
*
|
||||
* @return array<int, array{
|
||||
* name: string,
|
||||
* icon: string,
|
||||
* permissions: string[]
|
||||
* }>
|
||||
*/
|
||||
public static function permissionData(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'control',
|
||||
'icon' => 'tabler-terminal-2',
|
||||
'permissions' => ['console', 'start', 'stop', 'restart'],
|
||||
],
|
||||
[
|
||||
'name' => 'user',
|
||||
'icon' => 'tabler-users',
|
||||
'permissions' => ['read', 'create', 'update', 'delete'],
|
||||
],
|
||||
[
|
||||
'name' => 'file',
|
||||
'icon' => 'tabler-files',
|
||||
'permissions' => ['read', 'read-content', 'create', 'update', 'delete', 'archive', 'sftp'],
|
||||
],
|
||||
[
|
||||
'name' => 'backup',
|
||||
'icon' => 'tabler-file-zip',
|
||||
'permissions' => ['read', 'create', 'delete', 'download', 'restore'],
|
||||
],
|
||||
[
|
||||
'name' => 'allocation',
|
||||
'icon' => 'tabler-network',
|
||||
'permissions' => ['read', 'create', 'update', 'delete'],
|
||||
],
|
||||
[
|
||||
'name' => 'startup',
|
||||
'icon' => 'tabler-player-play',
|
||||
'permissions' => ['read', 'update', 'docker-image'],
|
||||
],
|
||||
[
|
||||
'name' => 'database',
|
||||
'icon' => 'tabler-database',
|
||||
'permissions' => ['read', 'create', 'update', 'delete', 'view-password'],
|
||||
],
|
||||
[
|
||||
'name' => 'schedule',
|
||||
'icon' => 'tabler-clock',
|
||||
'permissions' => ['read', 'create', 'update', 'delete'],
|
||||
],
|
||||
[
|
||||
'name' => 'settings',
|
||||
'icon' => 'tabler-settings',
|
||||
'permissions' => ['rename', 'reinstall'],
|
||||
],
|
||||
[
|
||||
'name' => 'activity',
|
||||
'icon' => 'tabler-stack',
|
||||
'permissions' => ['read'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the permissions available on the system for a user to have when controlling a server.
|
||||
*/
|
||||
public static function permissions(): Collection
|
||||
{
|
||||
return Collection::make(self::$permissions);
|
||||
$permissions = [
|
||||
'websocket' => [
|
||||
'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.',
|
||||
'keys' => [
|
||||
'connect' => 'Allows a user to connect to the websocket instance for a server to stream the console.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach (static::permissionData() as $data) {
|
||||
$permissions[$data['name']] = [
|
||||
'description' => trans('server/users.permissions.' . $data['name'] . '_desc'),
|
||||
'keys' => collect($data['permissions'])->mapWithKeys(fn ($key) => [$key => trans('server/users.permissions.' . $data['name'] . '_' . str($key)->replace('-', '_'))])->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
return collect($permissions);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Enums\RolePermissionModels;
|
||||
use App\Enums\RolePermissionPrefixes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Spatie\Permission\Models\Role as BaseRole;
|
||||
|
||||
/**
|
||||
@ -15,6 +16,8 @@ use Spatie\Permission\Models\Role as BaseRole;
|
||||
* @property int|null $permissions_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $users
|
||||
* @property int|null $users_count
|
||||
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\Node[] $nodes
|
||||
* @property int|null $nodes_count
|
||||
*/
|
||||
class Role extends BaseRole
|
||||
{
|
||||
@ -128,4 +131,9 @@ class Role extends BaseRole
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
public function nodes(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Node::class, NodeRole::class);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ use Carbon\CarbonInterface;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
@ -232,7 +231,12 @@ class Server extends Model implements Validatable
|
||||
|
||||
public function isInstalled(): bool
|
||||
{
|
||||
return $this->status !== ServerState::Installing && $this->status !== ServerState::InstallFailed;
|
||||
return $this->status !== ServerState::Installing && !$this->isFailedInstall();
|
||||
}
|
||||
|
||||
public function isFailedInstall(): bool
|
||||
{
|
||||
return $this->status === ServerState::InstallFailed || $this->status === ServerState::ReinstallFailed;
|
||||
}
|
||||
|
||||
public function isSuspended(): bool
|
||||
@ -350,9 +354,9 @@ class Server extends Model implements Validatable
|
||||
return $this->hasMany(Backup::class);
|
||||
}
|
||||
|
||||
public function mounts(): BelongsToMany
|
||||
public function mounts(): MorphToMany
|
||||
{
|
||||
return $this->belongsToMany(Mount::class);
|
||||
return $this->morphToMany(Mount::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,6 +286,22 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
});
|
||||
}
|
||||
|
||||
public function accessibleNodes(): Builder
|
||||
{
|
||||
// Root admins can access all nodes
|
||||
if ($this->isRootAdmin()) {
|
||||
return Node::query();
|
||||
}
|
||||
|
||||
// Check if there are no restrictions from any role
|
||||
$roleIds = $this->roles()->pluck('id');
|
||||
if (!NodeRole::whereIn('role_id', $roleIds)->exists()) {
|
||||
return Node::query();
|
||||
}
|
||||
|
||||
return Node::whereHas('roles', fn (Builder $builder) => $builder->whereIn('roles.id', $roleIds));
|
||||
}
|
||||
|
||||
public function subusers(): HasMany
|
||||
{
|
||||
return $this->hasMany(Subuser::class);
|
||||
@ -390,13 +406,24 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
||||
return $provider?->get($this);
|
||||
}
|
||||
|
||||
public function canTarget(Model $user): bool
|
||||
public function canTarget(Model $model): bool
|
||||
{
|
||||
// Root admins can target everyone and everything
|
||||
if ($this->isRootAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user instanceof User && !$user->isRootAdmin();
|
||||
// Make sure normal admins can't target root admins
|
||||
if ($model instanceof User) {
|
||||
return !$model->isRootAdmin();
|
||||
}
|
||||
|
||||
// Make sure the user can only target accessible nodes
|
||||
if ($model instanceof Node) {
|
||||
return $this->accessibleNodes()->where('id', $model->id)->exists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getTenants(Panel $panel): array|Collection
|
||||
|
@ -2,9 +2,28 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\User;
|
||||
|
||||
class DatabaseHostPolicy
|
||||
{
|
||||
use DefaultPolicies;
|
||||
|
||||
protected string $modelName = 'databasehost';
|
||||
|
||||
public function before(User $user, string $ability, string|DatabaseHost $databaseHost): ?bool
|
||||
{
|
||||
// For "viewAny" the $databaseHost param is the class name
|
||||
if (is_string($databaseHost)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($databaseHost->nodes as $node) {
|
||||
if (!$user->canTarget($node)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,28 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Mount;
|
||||
use App\Models\User;
|
||||
|
||||
class MountPolicy
|
||||
{
|
||||
use DefaultPolicies;
|
||||
|
||||
protected string $modelName = 'mount';
|
||||
|
||||
public function before(User $user, string $ability, string|Mount $mount): ?bool
|
||||
{
|
||||
// For "viewAny" the $mount param is the class name
|
||||
if (is_string($mount)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($mount->nodes as $node) {
|
||||
if (!$user->canTarget($node)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,26 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Models\User;
|
||||
|
||||
class NodePolicy
|
||||
{
|
||||
use DefaultPolicies;
|
||||
|
||||
protected string $modelName = 'node';
|
||||
|
||||
public function before(User $user, string $ability, string|Node $node): ?bool
|
||||
{
|
||||
// For "viewAny" the $node param is the class name
|
||||
if (is_string($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$user->canTarget($node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,11 @@ class ServerPolicy
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure user can target node of the server
|
||||
if (!$user->canTarget($server->node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Owner has full server permissions
|
||||
if ($server->owner_id === $user->id) {
|
||||
return true;
|
||||
|
@ -71,6 +71,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
'ssh_key' => Models\UserSSHKey::class,
|
||||
'task' => Models\Task::class,
|
||||
'user' => Models\User::class,
|
||||
'node' => Models\Node::class,
|
||||
]);
|
||||
|
||||
Http::macro(
|
||||
|
@ -141,4 +141,12 @@ class DaemonServerRepository extends DaemonRepository
|
||||
'jtis' => [md5($id . $this->server->uuid)],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getInstallLogs(): string
|
||||
{
|
||||
return $this->getHttpClient()
|
||||
->get("/api/servers/{$this->server->uuid}/install-logs")
|
||||
->throw()
|
||||
->json('data');
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,7 @@ class AssignmentService
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: how should we approach supporting IPv6 with this?
|
||||
// gethostbyname only supports IPv4, but the alternative (dns_get_record) returns
|
||||
// an array of records, which is not ideal for this use case, we need a SINGLE
|
||||
// IP to use, not multiple.
|
||||
$underlying = gethostbyname($data['allocation_ip']);
|
||||
$parsed = Network::parse($underlying);
|
||||
$parsed = Network::parse($data['allocation_ip']);
|
||||
} catch (\Exception $exception) {
|
||||
throw new DisplayException("Could not parse provided allocation IP address ({$data['allocation_ip']}): {$exception->getMessage()}", $exception);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class ToggleInstallService
|
||||
{
|
||||
public function handle(Server $server): void
|
||||
{
|
||||
if ($server->status === ServerState::InstallFailed) {
|
||||
if ($server->isFailedInstall()) {
|
||||
abort(500, trans('exceptions.server.marked_as_failed'));
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,20 @@ if (!function_exists('is_ip')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('is_ipv4')) {
|
||||
function is_ipv4(?string $address): bool
|
||||
{
|
||||
return $address !== null && filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('is_ipv6')) {
|
||||
function is_ipv6(?string $address): bool
|
||||
{
|
||||
return $address !== null && filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('convert_bytes_to_readable')) {
|
||||
function convert_bytes_to_readable(int $bytes, int $decimals = 2, ?int $base = null): string
|
||||
{
|
||||
@ -67,17 +81,3 @@ if (!function_exists('resolve_path')) {
|
||||
return implode('/', $absolutes);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_fonts')) {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function get_fonts(?string $directory = null): array
|
||||
{
|
||||
$directory ??= public_path('fonts');
|
||||
|
||||
return collect(glob($directory . '/*.ttf', GLOB_BRACE) ?: [])
|
||||
->mapWithKeys(fn ($file) => [$name = pathinfo($file, PATHINFO_FILENAME) => $name])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
@ -9,17 +9,18 @@
|
||||
"ext-pdo": "*",
|
||||
"ext-zip": "*",
|
||||
"abdelhamiderrahmouni/filament-monaco-editor": "^0.2.5",
|
||||
"aws/aws-sdk-php": "^3.342",
|
||||
"aws/aws-sdk-php": "^3.343",
|
||||
"aymanalhattami/filament-context-menu": "^1.0",
|
||||
"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.11",
|
||||
"laravel/framework": "^12.13",
|
||||
"laravel/helpers": "^1.7",
|
||||
"laravel/sanctum": "^4.0.2",
|
||||
"laravel/socialite": "^5.19",
|
||||
"laravel/sanctum": "^4.1",
|
||||
"laravel/socialite": "^5.20",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"laravel/ui": "^4.6",
|
||||
"lcobucci/jwt": "~4.3.0",
|
||||
@ -33,10 +34,10 @@
|
||||
"socialiteproviders/authentik": "^5.2",
|
||||
"socialiteproviders/discord": "^4.2",
|
||||
"socialiteproviders/steam": "^4.3",
|
||||
"spatie/laravel-data": "^4.14",
|
||||
"spatie/laravel-data": "^4.15",
|
||||
"spatie/laravel-fractal": "^6.3",
|
||||
"spatie/laravel-health": "^1.34",
|
||||
"spatie/laravel-permission": "^6.16",
|
||||
"spatie/laravel-permission": "^6.17",
|
||||
"spatie/laravel-query-builder": "^6.3",
|
||||
"spatie/temporary-directory": "^2.3",
|
||||
"symfony/http-client": "^7.2",
|
||||
|
353
composer.lock
generated
353
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b1bd5923489f35e846cb208d660d758e",
|
||||
"content-hash": "3819408e46a86ddb3019bf36a8f36ee9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "abdelhamiderrahmouni/filament-monaco-editor",
|
||||
@ -1020,16 +1020,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.343.0",
|
||||
"version": "3.343.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "8750298282f7f6f3fc65e43ae37a64bfc055100a"
|
||||
"reference": "3746aca8cbed5f46beba850e0a480ef58e71b197"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8750298282f7f6f3fc65e43ae37a64bfc055100a",
|
||||
"reference": "8750298282f7f6f3fc65e43ae37a64bfc055100a",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3746aca8cbed5f46beba850e0a480ef58e71b197",
|
||||
"reference": "3746aca8cbed5f46beba850e0a480ef58e71b197",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1111,9 +1111,85 @@
|
||||
"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.0"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.343.6"
|
||||
},
|
||||
"time": "2025-04-29T18:04:08+00:00"
|
||||
"time": "2025-05-07T18:10:08+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",
|
||||
@ -3714,16 +3790,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.11.1",
|
||||
"version": "v12.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "bd0d62bd9c5196728e428cd695d89ec8640daac1"
|
||||
"reference": "52b588bcd8efc6d01bc1493d2d67848f8065f269"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/bd0d62bd9c5196728e428cd695d89ec8640daac1",
|
||||
"reference": "bd0d62bd9c5196728e428cd695d89ec8640daac1",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/52b588bcd8efc6d01bc1493d2d67848f8065f269",
|
||||
"reference": "52b588bcd8efc6d01bc1493d2d67848f8065f269",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3744,7 +3820,7 @@
|
||||
"guzzlehttp/uri-template": "^1.0",
|
||||
"laravel/prompts": "^0.3.0",
|
||||
"laravel/serializable-closure": "^1.3|^2.0",
|
||||
"league/commonmark": "^2.6",
|
||||
"league/commonmark": "^2.7",
|
||||
"league/flysystem": "^3.25.1",
|
||||
"league/flysystem-local": "^3.25.1",
|
||||
"league/uri": "^7.5.1",
|
||||
@ -3836,7 +3912,7 @@
|
||||
"php-http/discovery": "^1.15",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1",
|
||||
"predis/predis": "^2.3",
|
||||
"predis/predis": "^2.3|^3.0",
|
||||
"resend/resend-php": "^0.10.0",
|
||||
"symfony/cache": "^7.2.0",
|
||||
"symfony/http-client": "^7.2.0",
|
||||
@ -3868,7 +3944,7 @@
|
||||
"pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
|
||||
"php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
|
||||
"phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).",
|
||||
"predis/predis": "Required to use the predis connector (^2.3).",
|
||||
"predis/predis": "Required to use the predis connector (^2.3|^3.0).",
|
||||
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
|
||||
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
|
||||
"resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
|
||||
@ -3925,7 +4001,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-04-30T13:48:39+00:00"
|
||||
"time": "2025-05-07T17:29:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/helpers",
|
||||
@ -4509,16 +4585,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.6.2",
|
||||
"version": "2.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94"
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/06c3b0bf2540338094575612f4a1778d0d2d5e94",
|
||||
"reference": "06c3b0bf2540338094575612f4a1778d0d2d5e94",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4555,7 +4631,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.7-dev"
|
||||
"dev-main": "2.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -4612,7 +4688,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-18T21:09:27+00:00"
|
||||
"time": "2025-05-05T12:20:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
@ -5772,16 +5848,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.9.0",
|
||||
"version": "3.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d"
|
||||
"reference": "ced71f79398ece168e24f7f7710462f462310d4d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6d16a8a015166fe54e22c042e0805c5363aef50d",
|
||||
"reference": "6d16a8a015166fe54e22c042e0805c5363aef50d",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d",
|
||||
"reference": "ced71f79398ece168e24f7f7710462f462310d4d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5874,7 +5950,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-27T12:57:33+00:00"
|
||||
"time": "2025-05-01T19:51:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
@ -9216,16 +9292,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.2.5",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
|
||||
"reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218",
|
||||
"reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -9289,7 +9365,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.5"
|
||||
"source": "https://github.com/symfony/console/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -9305,7 +9381,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-12T08:11:12+00:00"
|
||||
"time": "2025-04-07T19:09:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
@ -9736,16 +9812,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/html-sanitizer",
|
||||
"version": "v7.2.3",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/html-sanitizer.git",
|
||||
"reference": "91443febe34cfa5e8e00425f892e6316db95bc23"
|
||||
"reference": "1bd0c8fd5938d9af3f081a7c43d360ddefd494ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/91443febe34cfa5e8e00425f892e6316db95bc23",
|
||||
"reference": "91443febe34cfa5e8e00425f892e6316db95bc23",
|
||||
"url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/1bd0c8fd5938d9af3f081a7c43d360ddefd494ca",
|
||||
"reference": "1bd0c8fd5938d9af3f081a7c43d360ddefd494ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -9785,7 +9861,7 @@
|
||||
"sanitizer"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/html-sanitizer/tree/v7.2.3"
|
||||
"source": "https://github.com/symfony/html-sanitizer/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -9801,7 +9877,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-27T11:08:17+00:00"
|
||||
"time": "2025-03-31T08:29:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
@ -9978,16 +10054,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v7.2.5",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126"
|
||||
"reference": "6023ec7607254c87c5e69fb3558255aca440d72b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126",
|
||||
"reference": "371272aeb6286f8135e028ca535f8e4d6f114126",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b",
|
||||
"reference": "6023ec7607254c87c5e69fb3558255aca440d72b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10036,7 +10112,7 @@
|
||||
"description": "Defines an object-oriented layer for the HTTP specification",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v7.2.5"
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10052,20 +10128,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-25T15:54:33+00:00"
|
||||
"time": "2025-04-09T08:14:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v7.2.5",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54"
|
||||
"reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
|
||||
"reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec",
|
||||
"reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10150,7 +10226,7 @@
|
||||
"description": "Provides a structured process for converting a Request into a Response",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v7.2.5"
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10166,20 +10242,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-28T13:32:50+00:00"
|
||||
"time": "2025-05-02T09:04:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mailer",
|
||||
"version": "v7.2.3",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mailer.git",
|
||||
"reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3"
|
||||
"reference": "998692469d6e698c6eadc7ef37a6530a9eabb356"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/f3871b182c44997cf039f3b462af4a48fb85f9d3",
|
||||
"reference": "f3871b182c44997cf039f3b462af4a48fb85f9d3",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/998692469d6e698c6eadc7ef37a6530a9eabb356",
|
||||
"reference": "998692469d6e698c6eadc7ef37a6530a9eabb356",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10230,7 +10306,7 @@
|
||||
"description": "Helps sending emails",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mailer/tree/v7.2.3"
|
||||
"source": "https://github.com/symfony/mailer/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10246,7 +10322,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-27T11:08:17+00:00"
|
||||
"time": "2025-04-04T09:50:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mailgun-mailer",
|
||||
@ -10319,16 +10395,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
|
||||
"reference": "706e65c72d402539a072d0d6ad105fff6c161ef1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/706e65c72d402539a072d0d6ad105fff6c161ef1",
|
||||
"reference": "706e65c72d402539a072d0d6ad105fff6c161ef1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10383,7 +10459,7 @@
|
||||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/mime/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10399,11 +10475,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-19T08:51:20+00:00"
|
||||
"time": "2025-04-27T13:34:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
@ -10462,7 +10538,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10482,7 +10558,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
@ -10540,7 +10616,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10560,16 +10636,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-idn",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
|
||||
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773",
|
||||
"reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
|
||||
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10623,7 +10699,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10639,11 +10715,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2024-09-10T14:38:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
@ -10704,7 +10780,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10724,19 +10800,20 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
@ -10784,7 +10861,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10800,20 +10877,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2024-12-23T08:48:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -10864,7 +10941,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10880,11 +10957,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2025-01-02T08:10:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php83.git",
|
||||
@ -10940,7 +11017,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -10960,7 +11037,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-uuid",
|
||||
"version": "v1.31.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-uuid.git",
|
||||
@ -11019,7 +11096,7 @@
|
||||
"uuid"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0"
|
||||
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11039,16 +11116,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/postmark-mailer",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/postmark-mailer.git",
|
||||
"reference": "d11c8ce0ff5974a2ee4a9a3297ba89ecdf6d1952"
|
||||
"reference": "71ac001f8bc2ac36cc0bbea3fd6f4a4087d0cec0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/d11c8ce0ff5974a2ee4a9a3297ba89ecdf6d1952",
|
||||
"reference": "d11c8ce0ff5974a2ee4a9a3297ba89ecdf6d1952",
|
||||
"url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/71ac001f8bc2ac36cc0bbea3fd6f4a4087d0cec0",
|
||||
"reference": "71ac001f8bc2ac36cc0bbea3fd6f4a4087d0cec0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11089,7 +11166,7 @@
|
||||
"description": "Symfony Postmark Mailer Bridge",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/postmark-mailer/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/postmark-mailer/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11105,7 +11182,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-26T08:19:39+00:00"
|
||||
"time": "2025-04-30T07:52:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
@ -11334,16 +11411,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.2.0",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
|
||||
"reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
|
||||
"reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931",
|
||||
"reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11401,7 +11478,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.2.0"
|
||||
"source": "https://github.com/symfony/string/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11417,20 +11494,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-13T13:31:26+00:00"
|
||||
"time": "2025-04-20T20:18:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v7.2.4",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a"
|
||||
"reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6",
|
||||
"reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11496,7 +11573,7 @@
|
||||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v7.2.4"
|
||||
"source": "https://github.com/symfony/translation/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11512,7 +11589,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-13T10:27:23+00:00"
|
||||
"time": "2025-04-07T19:09:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
@ -11668,16 +11745,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v7.2.3",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "82b478c69745d8878eb60f9a049a4d584996f73a"
|
||||
"reference": "9c46038cd4ed68952166cf7001b54eb539184ccb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a",
|
||||
"reference": "82b478c69745d8878eb60f9a049a4d584996f73a",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/9c46038cd4ed68952166cf7001b54eb539184ccb",
|
||||
"reference": "9c46038cd4ed68952166cf7001b54eb539184ccb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11731,7 +11808,7 @@
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.2.3"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11747,20 +11824,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-17T11:39:41+00:00"
|
||||
"time": "2025-04-09T08:14:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.2.5",
|
||||
"version": "v7.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912"
|
||||
"reference": "0feafffb843860624ddfd13478f481f4c3cd8b23"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912",
|
||||
"reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/0feafffb843860624ddfd13478f481f4c3cd8b23",
|
||||
"reference": "0feafffb843860624ddfd13478f481f4c3cd8b23",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11803,7 +11880,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.2.5"
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.2.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11819,7 +11896,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-03T07:12:39+00:00"
|
||||
"time": "2025-04-04T10:10:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
@ -11878,16 +11955,16 @@
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.1",
|
||||
"version": "v5.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -11946,7 +12023,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -11958,7 +12035,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:52:34+00:00"
|
||||
"time": "2025-04-30T23:37:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
@ -13940,16 +14017,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.13",
|
||||
"version": "2.1.14",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "e55e03e6d4ac49cd1240907e5b08e5cd378572a9"
|
||||
"reference": "8f2e03099cac24ff3b379864d171c5acbfc6b9a2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e55e03e6d4ac49cd1240907e5b08e5cd378572a9",
|
||||
"reference": "e55e03e6d4ac49cd1240907e5b08e5cd378572a9",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f2e03099cac24ff3b379864d171c5acbfc6b9a2",
|
||||
"reference": "8f2e03099cac24ff3b379864d171c5acbfc6b9a2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -13994,7 +14071,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-27T12:28:25+00:00"
|
||||
"time": "2025-05-02T15:32:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -15348,16 +15425,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spatie/backtrace",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/backtrace.git",
|
||||
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2"
|
||||
"reference": "80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/backtrace/zipball/9807de6b8fecfaa5b3d10650985f0348b02862b2",
|
||||
"reference": "9807de6b8fecfaa5b3d10650985f0348b02862b2",
|
||||
"url": "https://api.github.com/repos/spatie/backtrace/zipball/80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20",
|
||||
"reference": "80ae5fabe2a1e3bc7df5c7ca5d91b094dc314b20",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -15395,7 +15472,7 @@
|
||||
"spatie"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/spatie/backtrace/tree/1.7.2"
|
||||
"source": "https://github.com/spatie/backtrace/tree/1.7.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -15407,7 +15484,7 @@
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-28T14:55:53+00:00"
|
||||
"time": "2025-05-07T07:20:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/error-solutions",
|
||||
@ -15907,5 +15984,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.2"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
<?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::create('node_role', function (Blueprint $table) {
|
||||
$table->unsignedInteger('node_id');
|
||||
$table->unsignedBigInteger('role_id');
|
||||
|
||||
$table->unique(['node_id', 'role_id']);
|
||||
|
||||
$table->foreign('node_id')->references('id')->on('nodes')->cascadeOnDelete();
|
||||
$table->foreign('role_id')->references('id')->on('roles')->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('node_role');
|
||||
}
|
||||
};
|
93
database/migrations/2025_05_01_193002_move_to_mountables.php
Normal file
93
database/migrations/2025_05_01_193002_move_to_mountables.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('mountables', function (Blueprint $table) {
|
||||
$table->unsignedInteger('mount_id');
|
||||
|
||||
$table->string('mountable_type');
|
||||
$table->unsignedBigInteger('mountable_id');
|
||||
$table->index(['mountable_id', 'mountable_type'], 'mountables_mountable_id_mountable_type_index');
|
||||
|
||||
$table->foreign('mount_id')
|
||||
->references('id') // mount id
|
||||
->on('mounts')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary(['mount_id', 'mountable_id', 'mountable_type'],
|
||||
'mountables_mountable_type_primary');
|
||||
});
|
||||
|
||||
Schema::table('mount_node', function (Blueprint $table) {
|
||||
$table->dropForeign(['node_id']);
|
||||
$table->dropForeign(['mount_id']);
|
||||
$table->dropUnique(['node_id', 'mount_id']);
|
||||
});
|
||||
|
||||
$inserts = [];
|
||||
$nodeMounts = DB::table('mount_node')->get();
|
||||
$nodeMounts->each(function ($mount) use (&$inserts) {
|
||||
$inserts[] = [
|
||||
'mount_id' => $mount->mount_id,
|
||||
'mountable_type' => 'node',
|
||||
'mountable_id' => $mount->node_id,
|
||||
];
|
||||
});
|
||||
|
||||
Schema::table('mount_server', function (Blueprint $table) {
|
||||
$table->dropForeign(['server_id']);
|
||||
$table->dropForeign(['mount_id']);
|
||||
$table->dropUnique(['server_id', 'mount_id']);
|
||||
});
|
||||
|
||||
$serverMounts = DB::table('mount_server')->get();
|
||||
$serverMounts->each(function ($mount) use (&$inserts) {
|
||||
$inserts[] = [
|
||||
'mount_id' => $mount->mount_id,
|
||||
'mountable_type' => 'server',
|
||||
'mountable_id' => $mount->server_id,
|
||||
];
|
||||
});
|
||||
|
||||
Schema::table('egg_mount', function (Blueprint $table) {
|
||||
$table->dropForeign(['egg_id']);
|
||||
$table->dropForeign(['mount_id']);
|
||||
$table->dropUnique(['egg_id', 'mount_id']);
|
||||
});
|
||||
|
||||
$eggMounts = DB::table('egg_mount')->get();
|
||||
$eggMounts->each(function ($mount) use (&$inserts) {
|
||||
$inserts[] = [
|
||||
'mount_id' => $mount->mount_id,
|
||||
'mountable_type' => 'egg',
|
||||
'mountable_id' => $mount->egg_id,
|
||||
];
|
||||
});
|
||||
|
||||
DB::transaction(function () use ($inserts) {
|
||||
DB::table('mountables')->insert($inserts);
|
||||
});
|
||||
|
||||
Schema::drop('mount_node');
|
||||
Schema::drop('mount_server');
|
||||
Schema::drop('egg_mount');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Not needed
|
||||
}
|
||||
};
|
@ -12,4 +12,6 @@ return [
|
||||
'root_admin' => 'The :role has all permissions.',
|
||||
'root_admin_delete' => 'Can\'t delete Root Admin',
|
||||
'users' => 'Users',
|
||||
'nodes' => 'Nodes',
|
||||
'nodes_hint' => 'Leave empty to allow access to all nodes.',
|
||||
];
|
||||
|
@ -64,6 +64,7 @@ return [
|
||||
'reinstall_modal_heading' => 'Are you sure you want to reinstall this server?',
|
||||
'reinstall_modal_description' => '!! This can result in unrecoverable data loss !!',
|
||||
'server_status' => 'Server Status',
|
||||
'view_install_log' => 'View install log',
|
||||
'uuid' => 'UUID',
|
||||
'node' => 'Node',
|
||||
'short_uuid' => 'Short UUID',
|
||||
@ -85,7 +86,7 @@ return [
|
||||
'allocations' => 'Allocations',
|
||||
'databases' => 'Databases',
|
||||
'no_databases' => 'No Databases exist for this Server',
|
||||
'delete_db' => 'Are you sure you want to delete',
|
||||
'delete_db' => 'Are you sure you want to delete :name ?',
|
||||
'delete_db_heading' => 'Delete Database?',
|
||||
'backups' => 'Backups',
|
||||
'egg' => 'Egg',
|
||||
@ -100,6 +101,7 @@ return [
|
||||
'create_allocation' => 'Create Allocation',
|
||||
'add_allocation' => 'Add Allocation',
|
||||
'view' => 'View',
|
||||
'no_log' => 'No Log Available',
|
||||
'tabs' => [
|
||||
'information' => 'Information',
|
||||
'egg_configuration' => 'Egg Configuration',
|
||||
@ -129,5 +131,6 @@ return [
|
||||
'install_toggle_failed' => 'Could not toggle install status',
|
||||
'reinstall_started' => 'Reinstall started',
|
||||
'reinstall_failed' => 'Could not start reinstall',
|
||||
'log_failed' => 'Could not connect to Wings to retrieve server install log.',
|
||||
],
|
||||
];
|
||||
|
@ -16,6 +16,7 @@ return [
|
||||
'failed' => 'These credentials do not match our records.',
|
||||
'failed-two-factor' => 'Incorrect 2FA Code',
|
||||
'two-factor-code' => 'Two Factor Code',
|
||||
'two-factor-hint' => 'You may use backup codes if you lost access to your device.',
|
||||
'password' => 'The provided password is incorrect.',
|
||||
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
||||
'2fa_must_be_enabled' => 'The administrator has required that 2-Factor Authentication must be enabled for your account in order to use the Panel.',
|
||||
|
@ -4,6 +4,7 @@ return [
|
||||
'daemon_connection_failed' => 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/:code response code. This exception has been logged.',
|
||||
'node' => [
|
||||
'servers_attached' => 'A node must have no servers linked to it in order to be deleted.',
|
||||
'error_connecting' => 'Error connecting to :node',
|
||||
'daemon_off_config_updated' => 'The daemon configuration <strong>has been updated</strong>, however there was an error encountered while attempting to automatically update the configuration file on the Daemon. You will need to manually update the configuration file (config.yml) for the daemon to apply these changes.',
|
||||
],
|
||||
'allocations' => [
|
||||
|
@ -47,4 +47,8 @@ return [
|
||||
'rows' => 'Rows',
|
||||
'font_size' => 'Font Size',
|
||||
'font' => 'Font',
|
||||
'font_preview' => 'Font Preview',
|
||||
'seconds' => 'Seconds',
|
||||
'graph_period' => 'Graph Period',
|
||||
'graph_period_helper' => 'The amount of data points, seconds, shown on the console graphs.',
|
||||
];
|
||||
|
@ -15,8 +15,8 @@ return [
|
||||
'startup_read' => 'Allows a user to view the startup variables for a server.',
|
||||
'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.',
|
||||
'setting_reinstall' => 'Allows a user to trigger a reinstall of this server.',
|
||||
'setting_rename' => 'Allows a user to rename this server and change the description of it.',
|
||||
'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.',
|
||||
'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
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
@ -1,10 +1,19 @@
|
||||
<x-filament::widget>
|
||||
@assets
|
||||
@php
|
||||
$userFont = auth()->user()->getCustomization()['console_font'] ?? 'ComicMono';
|
||||
$userFont = auth()->user()->getCustomization()['console_font'] ?? 'monospace';
|
||||
$userFontSize = auth()->user()->getCustomization()['console_font_size'] ?? 14;
|
||||
$userRows = auth()->user()->getCustomization()['console_rows'] ?? 30;
|
||||
@endphp
|
||||
@if($userFont)
|
||||
<link rel="preload" href="{{ asset("storage/fonts/{$userFont}.ttf") }}" as="font" crossorigin>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: '{{ $userFont }}';
|
||||
src: url('{{ asset("storage/fonts/{$userFont}.ttf") }}');
|
||||
}
|
||||
</style>
|
||||
@endif
|
||||
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm/lib/xterm.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit/lib/addon-fit.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links/lib/addon-web-links.min.js"></script>
|
||||
@ -12,14 +21,6 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search-bar/lib/xterm-addon-search-bar.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm/css/xterm.min.css">
|
||||
<link rel="stylesheet" href="{{ asset('/css/filament/server/console.css') }}">
|
||||
<link rel="preload" href="{{ asset("storage/fonts/{$userFont}.ttf") }}" as="font" crossorigin>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: '{{ $userFont }}';
|
||||
src: url('{{ asset("storage/fonts/{$userFont}.ttf") }}');
|
||||
}
|
||||
</style>
|
||||
@endassets
|
||||
|
||||
<div id="terminal" wire:ignore></div>
|
||||
@ -71,7 +72,7 @@
|
||||
|
||||
let options = {
|
||||
fontSize: {{ $userFontSize }},
|
||||
fontFamily: '{{ $userFont }}',
|
||||
fontFamily: '{{ $userFont }}, monospace',
|
||||
lineHeight: 1.2,
|
||||
disableStdin: true,
|
||||
cursorStyle: 'underline',
|
||||
|
184
resources/views/filament/plugins/monaco-editor-logs.blade.php
Normal file
184
resources/views/filament/plugins/monaco-editor-logs.blade.php
Normal file
@ -0,0 +1,184 @@
|
||||
@script
|
||||
<script>
|
||||
$wire.on('setContent', ({ content }) => {
|
||||
document.getElementById('{{ $getId() }}').editor.getModel().setValue(content);
|
||||
});
|
||||
|
||||
$wire.on('setLanguage', ({ lang }) => {
|
||||
monaco.editor.setModelLanguage(document.getElementById('{{ $getId() }}').editor.getModel(), lang);
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
<x-dynamic-component :component="$getFieldWrapperView()" :field="$field" class="overflow-hidden">
|
||||
|
||||
<div x-data="{
|
||||
monacoContent: $wire.$entangle('{{ $getStatePath() }}'),
|
||||
previewContent: '',
|
||||
fullScreenModeEnabled: false,
|
||||
showPreview: false,
|
||||
monacoLanguage: '{{ $getLanguage() }}',
|
||||
monacoPlaceholder: {{ (int) $getShowPlaceholder() }},
|
||||
monacoPlaceholderText: '{{ $getPlaceholderText() }}',
|
||||
monacoLoader: {{ (int) $getShowLoader() }},
|
||||
monacoFontSize: '{{ $getFontSize() }}',
|
||||
lineNumbersMinChars: {{ $getLineNumbersMinChars() }},
|
||||
automaticLayout: {{ (int) $getAutomaticLayout() }},
|
||||
monacoId: '{{ $getId() }}',
|
||||
|
||||
toggleFullScreenMode() {
|
||||
this.fullScreenModeEnabled = !this.fullScreenModeEnabled;
|
||||
this.fullScreenModeEnabled ? document.body.classList.add('overflow-hidden')
|
||||
: document.body.classList.remove('overflow-hidden');
|
||||
$el.style.width = this.fullScreenModeEnabled ? '100vw'
|
||||
: $el.parentElement.clientWidth + 'px';
|
||||
},
|
||||
|
||||
monacoEditor(editor){
|
||||
editor.onDidChangeModelContent((e) => {
|
||||
this.monacoContent = editor.getValue();
|
||||
this.updatePlaceholder(editor.getValue());
|
||||
});
|
||||
|
||||
editor.onDidBlurEditorWidget(() => {
|
||||
this.updatePlaceholder(editor.getValue());
|
||||
});
|
||||
|
||||
editor.onDidFocusEditorWidget(() => {
|
||||
this.updatePlaceholder(editor.getValue());
|
||||
});
|
||||
},
|
||||
|
||||
updatePlaceholder: function(value) {
|
||||
if (value == '') {
|
||||
this.monacoPlaceholder = true;
|
||||
return;
|
||||
}
|
||||
this.monacoPlaceholder = false;
|
||||
},
|
||||
|
||||
monacoEditorFocus(){
|
||||
document.getElementById(this.monacoId).dispatchEvent(
|
||||
new CustomEvent('monaco-editor-focused', { monacoId: this.monacoId })
|
||||
);
|
||||
},
|
||||
|
||||
monacoEditorAddLoaderScriptToHead() {
|
||||
script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/loader.min.js';
|
||||
document.head.appendChild(script);
|
||||
},
|
||||
|
||||
wrapPreview(value){
|
||||
return `<head>{{ $getPreviewHeadEndContent() }}</head>` +
|
||||
`<body {{ $getPreviewBodyAttributes() }}>` +
|
||||
`{{ $getPreviewBodyStartContent() }}` +
|
||||
`${value}` +
|
||||
`{{ $getPreviewBodyEndContent() }}` +
|
||||
`</body>`;
|
||||
},
|
||||
|
||||
}" x-init="
|
||||
previewContent = wrapPreview(monacoContent);
|
||||
$el.style.height = '500px';
|
||||
$watch('fullScreenModeEnabled', value => {
|
||||
if (value) {
|
||||
$el.style.height = '100vh';
|
||||
} else {
|
||||
$el.style.height = '500px';
|
||||
}
|
||||
});
|
||||
|
||||
if(typeof _amdLoaderGlobal == 'undefined'){
|
||||
monacoEditorAddLoaderScriptToHead();
|
||||
}
|
||||
|
||||
monacoLoaderInterval = setInterval(() => {
|
||||
if(typeof _amdLoaderGlobal !== 'undefined'){
|
||||
|
||||
// Based on https://jsfiddle.net/developit/bwgkr6uq/ which works without needing service worker. Provided by loader.min.js.
|
||||
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs' }});
|
||||
let proxy = URL.createObjectURL(new Blob([` self.MonacoEnvironment = { baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min' }; importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.49.0/min/vs/base/worker/workerMain.min.js');`], { type: 'text/javascript' }));
|
||||
window.MonacoEnvironment = { getWorkerUrl: () => proxy };
|
||||
|
||||
require(['vs/editor/editor.main'], () => {
|
||||
|
||||
monaco.editor.defineTheme('custom', {{ $editorTheme() }});
|
||||
document.getElementById(monacoId).editor = monaco.editor.create($refs.monacoEditorElement, {
|
||||
value: monacoContent,
|
||||
theme: localStorage.getItem('theme') === 'light' ? 'iPlastic' : 'custom',
|
||||
fontSize: monacoFontSize,
|
||||
lineNumbersMinChars: lineNumbersMinChars,
|
||||
automaticLayout: automaticLayout,
|
||||
language: monacoLanguage,
|
||||
scrollbar: {
|
||||
horizontal: 'auto',
|
||||
horizontalScrollbarSize: 15,
|
||||
vertical: 'auto',
|
||||
verticalScrollbarSize: 15
|
||||
},
|
||||
wordWrap: 'on',
|
||||
WrappingIndent: 'same',
|
||||
readOnly: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
$el.style.zIndex = '1';
|
||||
monacoEditor(document.getElementById(monacoId).editor);
|
||||
document.getElementById(monacoId).addEventListener('monaco-editor-focused', (event) => {
|
||||
document.getElementById(monacoId).editor.focus();
|
||||
});
|
||||
updatePlaceholder(document.getElementById(monacoId).editor.getValue());
|
||||
});
|
||||
|
||||
clearInterval(monacoLoaderInterval);
|
||||
monacoLoader = false;
|
||||
}
|
||||
}, 5); " :id="monacoId"
|
||||
class="fme-wrapper"
|
||||
:class="{ 'fme-full-screen': fullScreenModeEnabled }" x-cloak>
|
||||
<div class="flex items-center ml-auto">
|
||||
@if($getShowFullScreenToggle())
|
||||
<button type="button" aria-label="{{ __("full_screen_btn_label") }}" class="fme-full-screen-btn"
|
||||
@click="toggleFullScreenMode()">
|
||||
<svg class="fme-full-screen-btn-icon" x-show="!fullScreenModeEnabled"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M16 4l4 0l0 4" />
|
||||
<path d="M14 10l6 -6" />
|
||||
<path d="M8 20l-4 0l0 -4" />
|
||||
<path d="M4 20l6 -6" />
|
||||
<path d="M16 20l4 0l0 -4" />
|
||||
<path d="M14 14l6 6" />
|
||||
<path d="M8 4l-4 0l0 4" />
|
||||
<path d="M4 4l6 6" />
|
||||
</svg>
|
||||
<svg class="fme-full-screen-btn-icon" x-show="fullScreenModeEnabled"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M5 9l4 0l0 -4" />
|
||||
<path d="M3 3l6 6" />
|
||||
<path d="M5 15l4 0l0 4" />
|
||||
<path d="M3 21l6 -6" />
|
||||
<path d="M19 9l-4 0l0 -4" />
|
||||
<path d="M15 9l6 -6" />
|
||||
<path d="M19 15l-4 0l0 4" />
|
||||
<path d="M15 15l6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="fme-container" x-show="!showPreview">
|
||||
<!-- Editor -->
|
||||
<div x-show="!monacoLoader" class="fme-element-wrapper">
|
||||
<div x-ref="monacoEditorElement" class="fme-element" wire:ignore style="height: 100%"></div>
|
||||
<div x-ref="monacoPlaceholderElement" x-show="monacoPlaceholder" @click="monacoEditorFocus()"
|
||||
:style="'font-size: ' + monacoFontSize" class="fme-placeholder"
|
||||
x-text="monacoPlaceholderText"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</x-dynamic-component>
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user