mirror of
https://github.com/pelican-dev/panel.git
synced 2025-06-26 14:01:08 +02:00
disaster of a merge.... main->filament-v4
This commit is contained in:
commit
5121cf7170
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@ -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
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,7 +1,6 @@
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
|
@ -82,6 +82,7 @@ RUN chown root:www-data ./ \
|
||||
&& 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 \
|
||||
&& 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
|
||||
|
@ -86,6 +86,7 @@ RUN chown root:www-data ./ \
|
||||
&& 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 \
|
||||
&& 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
|
||||
|
@ -18,6 +18,17 @@ class QueueWorkerServiceCommand extends Command
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (@file_exists('/.dockerenv')) {
|
||||
$result = Process::run('supervisorctl restart queue-worker');
|
||||
if ($result->failed()) {
|
||||
$this->error('Error restarting service: ' . $result->errorOutput());
|
||||
|
||||
return;
|
||||
}
|
||||
$this->line('Queue worker service file updated successfully.');
|
||||
|
||||
return;
|
||||
}
|
||||
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
||||
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
||||
|
||||
|
@ -24,6 +24,7 @@ class MakeNodeCommand extends Command
|
||||
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
|
||||
{--uploadSize= : Enter the maximum upload filesize.}
|
||||
{--daemonListeningPort= : Enter the daemon listening port.}
|
||||
{--daemonConnectingPort= : Enter the daemon connecting port.}
|
||||
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
||||
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
||||
{--daemonBase= : Enter the base folder.}';
|
||||
@ -57,6 +58,7 @@ class MakeNodeCommand extends Command
|
||||
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(trans('commands.make_node.cpu_overallocate'), '-1');
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(trans('commands.make_node.upload_size'), '256');
|
||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(trans('commands.make_node.daemonListen'), '8080');
|
||||
$data['daemon_connect'] = $this->option('daemonConnectingPort') ?? $this->ask(trans('commands.make_node.daemonConnect'), '8080');
|
||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(trans('commands.make_node.daemonSFTP'), '2022');
|
||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(trans('commands.make_node.daemonSFTPAlias'), '');
|
||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(trans('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||
|
@ -7,7 +7,6 @@ use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Jobs\NodeStatistics;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
@ -31,8 +30,11 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
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();
|
||||
@ -41,8 +43,6 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command(PruneImagesCommand::class)->daily();
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->hourly();
|
||||
|
||||
$schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping();
|
||||
|
||||
if (config('backups.prune_age')) {
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||
$schedule->command(PruneOrphanedBackupsCommand::class)->everyThirtyMinutes();
|
||||
|
@ -88,7 +88,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
|
||||
public function isStartable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isRestartable(): bool
|
||||
@ -97,18 +97,16 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
return true;
|
||||
}
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline]);
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isStoppable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]);
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isKillable(): bool
|
||||
{
|
||||
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created]
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
|
||||
}
|
||||
}
|
||||
|
9
app/Enums/HeaderActionPosition.php
Normal file
9
app/Enums/HeaderActionPosition.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderActionPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
9
app/Enums/HeaderWidgetPosition.php
Normal file
9
app/Enums/HeaderWidgetPosition.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderWidgetPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
@ -27,8 +27,16 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): ?string
|
||||
public function getColor(bool $hex = false): string
|
||||
{
|
||||
if ($hex) {
|
||||
return match ($this) {
|
||||
self::Normal, self::Installing, self::RestoringBackup => '#2563EB',
|
||||
self::Suspended => '#D97706',
|
||||
self::InstallFailed, self::ReinstallFailed => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Normal => 'primary',
|
||||
self::Installing => 'primary',
|
||||
|
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Events\Event;
|
||||
|
||||
class DirectLogin extends Event
|
||||
{
|
||||
public function __construct(public User $user, public bool $remember) {}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Auth;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class FailedPasswordReset extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public string $ip, public string $email) {}
|
||||
}
|
@ -8,13 +8,16 @@ 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\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
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 GSLToken extends FeatureProvider
|
||||
{
|
||||
@ -27,14 +30,14 @@ class GSLToken extends FeatureProvider
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,18 +49,19 @@ class GSLToken extends FeatureProvider
|
||||
$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()
|
||||
->modalHeading('Invalid GSL token')
|
||||
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
||||
->modalSubmitActionLabel('Update GSL Token')
|
||||
->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->schema([
|
||||
TextEntry::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.'),
|
||||
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->form([
|
||||
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([
|
||||
@ -105,13 +109,13 @@ class GSLToken extends FeatureProvider
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -6,6 +6,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;
|
||||
@ -25,10 +26,11 @@ class JavaVersion extends FeatureProvider
|
||||
{
|
||||
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',
|
||||
];
|
||||
}
|
||||
|
||||
@ -73,17 +75,18 @@ class JavaVersion extends FeatureProvider
|
||||
->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();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class MinecraftEula extends FeatureProvider
|
||||
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',
|
||||
];
|
||||
}
|
||||
|
||||
@ -38,31 +38,30 @@ class MinecraftEula extends FeatureProvider
|
||||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public static function register(Application $app): self
|
||||
|
@ -8,9 +8,15 @@ use App\Extensions\OAuth\Providers\OAuthProvider;
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use BackedEnum;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Group;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
@ -31,8 +37,8 @@ use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Support\Enums\Width;
|
||||
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;
|
||||
@ -43,9 +49,12 @@ use Filament\Schemas\Contracts\HasSchemas;
|
||||
*/
|
||||
class Settings extends Page implements HasSchemas
|
||||
{
|
||||
use CanCustomizeHeaderActions, InteractsWithHeaderActions {
|
||||
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
|
||||
}
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use EnvironmentWriterTrait;
|
||||
use InteractsWithForms;
|
||||
use InteractsWithHeaderActions;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
|
||||
|
||||
@ -138,8 +147,7 @@ class Settings extends Page implements HasSchemas
|
||||
->placeholder('/pelican.ico'),
|
||||
]),
|
||||
Group::make()
|
||||
->columnSpan(2)
|
||||
->columns(4)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Toggle::make('APP_DEBUG')
|
||||
->label(trans('admin/setting.general.debug_mode'))
|
||||
@ -159,6 +167,10 @@ class Settings extends Page implements HasSchemas
|
||||
])
|
||||
->stateCast(new BooleanStateCast(false, true))
|
||||
->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)
|
||||
@ -195,12 +207,18 @@ class Settings extends Page implements HasSchemas
|
||||
->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([
|
||||
Action::make('clear')
|
||||
->label(trans('admin/setting.general.clear'))
|
||||
@ -235,12 +253,6 @@ class Settings extends Page implements HasSchemas
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
]),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label(trans('admin/setting.general.display_width'))
|
||||
->native(false)
|
||||
->options(Width::class)
|
||||
->selectablePlaceholder(false)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
];
|
||||
}
|
||||
|
||||
@ -635,8 +647,8 @@ class Settings extends Page implements HasSchemas
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_INSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_INSTALL_NOTIFICATION', config('panel.email.send_install_notification'))),
|
||||
Toggle::make('PANEL_SEND_REINSTALL_NOTIFICATION')
|
||||
->label(trans('admin/setting.misc.mail_notifications.server_reinstalled'))
|
||||
@ -645,8 +657,8 @@ class Settings extends Page implements HasSchemas
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_SEND_REINSTALL_NOTIFICATION', (bool) $state))
|
||||
->default(env('PANEL_SEND_REINSTALL_NOTIFICATION', config('panel.email.send_reinstall_notification'))),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.connections.title'))
|
||||
@ -731,9 +743,17 @@ class Settings extends Page implements HasSchemas
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->live()
|
||||
->columnSpanFull()
|
||||
->stateCast(new BooleanStateCast(false))
|
||||
->columnSpan(1)
|
||||
->formatStateUsing(fn ($state): bool => (bool) $state)
|
||||
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
|
||||
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
|
||||
FileUpload::make('ConsoleFonts')
|
||||
->hint(trans('admin/setting.misc.server.console_font_hint'))
|
||||
->label(trans('admin/setting.misc.server.console_font_upload'))
|
||||
->directory('fonts')
|
||||
->columnSpan(1)
|
||||
->maxFiles(1)
|
||||
->preserveFilenames(),
|
||||
]),
|
||||
Section::make(trans('admin/setting.misc.webhook.title'))
|
||||
->description(trans('admin/setting.misc.webhook.helper'))
|
||||
@ -762,6 +782,7 @@ class Settings extends Page implements HasSchemas
|
||||
{
|
||||
try {
|
||||
$data = $this->form->getState();
|
||||
unset($data['ConsoleFonts']);
|
||||
|
||||
$data = array_map(function ($value) {
|
||||
// Convert bools to a string, so they are correctly written to the .env file
|
||||
@ -797,7 +818,8 @@ class Settings extends Page implements HasSchemas
|
||||
}
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
|
@ -6,11 +6,17 @@ use App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Form;
|
||||
@ -20,6 +26,11 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ApiKeyResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
|
||||
@ -56,7 +67,10 @@ class ApiKeyResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -79,7 +93,7 @@ class ApiKeyResource extends Resource
|
||||
TextColumn::make('user.username')
|
||||
->label(trans('admin/apikey.table.created_by'))
|
||||
->icon('tabler-user')
|
||||
->url(fn (ApiKey $apiKey) => auth()->user()->can('update user', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
->url(fn (ApiKey $apiKey) => auth()->user()->can('update', $apiKey->user) ? EditUser::getUrl(['record' => $apiKey->user]) : null),
|
||||
])
|
||||
->actions([
|
||||
DeleteAction::make(),
|
||||
@ -92,9 +106,12 @@ class ApiKeyResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function form(Form|\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function form(Form|\Filament\Schemas\Schema $form): \Filament\Schemas\Schema
|
||||
{
|
||||
return $schema
|
||||
return $form
|
||||
->schema([
|
||||
Fieldset::make('Permissions')
|
||||
->columns([
|
||||
@ -142,7 +159,8 @@ class ApiKeyResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListApiKeys::route('/'),
|
||||
|
@ -4,16 +4,24 @@ namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateApiKey extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ApiKeyResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -4,14 +4,22 @@ namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Models\ApiKey;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListApiKeys extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ApiKeyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
|
@ -3,22 +3,35 @@
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DatabaseHostResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = DatabaseHost::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||
@ -27,7 +40,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
|
||||
@ -50,7 +63,10 @@ class DatabaseHostResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -88,9 +104,12 @@ class DatabaseHostResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
return $schema
|
||||
return $form
|
||||
->components([
|
||||
Section::make()
|
||||
->columnSpanFull()
|
||||
@ -145,12 +164,21 @@ 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'))),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDatabaseHosts::route('/'),
|
||||
@ -159,4 +187,15 @@ class DatabaseHostResource extends Resource
|
||||
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->where(function (Builder $query) {
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,9 @@ namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Services\Databases\Hosts\HostCreationService;
|
||||
use Exception;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Forms\Components\Select;
|
||||
@ -18,6 +19,7 @@ use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
|
||||
use Filament\Schemas\Components\Wizard\Step;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Illuminate\Support\Str;
|
||||
@ -26,6 +28,8 @@ use Throwable;
|
||||
|
||||
class CreateDatabaseHost extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use HasWizard;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
@ -148,7 +152,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'))),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
@ -3,19 +3,24 @@
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Services\Databases\Hosts\HostUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
|
||||
class EditDatabaseHost extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
private HostUpdateService $hostUpdateService;
|
||||
@ -25,7 +30,8 @@ class EditDatabaseHost extends EditRecord
|
||||
$this->hostUpdateService = $hostUpdateService;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
@ -40,21 +46,6 @@ class EditDatabaseHost extends EditRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getRelationManagers(): array
|
||||
{
|
||||
if (DatabasesRelationManager::canViewForRecord($this->getRecord(), static::class)) {
|
||||
return [
|
||||
DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Halt
|
||||
* @throws Throwable
|
||||
*/
|
||||
protected function handleRecordUpdate(Model $record, array $data): Model
|
||||
{
|
||||
if (!$record instanceof DatabaseHost) {
|
||||
|
@ -4,14 +4,22 @@ namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListDatabaseHosts extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
|
@ -3,29 +3,25 @@
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource;
|
||||
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewDatabaseHost extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getRelationManagers(): array
|
||||
{
|
||||
if (DatabasesRelationManager::canViewForRecord($this->getRecord(), static::class)) {
|
||||
return [
|
||||
DatabasesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -68,11 +68,11 @@ class DatabasesRelationManager extends RelationManager
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->actions([
|
||||
\Filament\Actions\DeleteAction::make()
|
||||
->authorize(fn (Database $database) => auth()->user()->can('delete database', $database)),
|
||||
\Filament\Actions\ViewAction::make()
|
||||
DeleteAction::make()
|
||||
->authorize(fn (Database $database) => auth()->user()->can('delete', $database)),
|
||||
ViewAction::make()
|
||||
->color('primary')
|
||||
->hidden(fn () => !auth()->user()->can('viewList database')),
|
||||
->hidden(fn () => !auth()->user()->can('viewAny', Database::class)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,19 @@
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\EggResource\Pages;
|
||||
use App\Filament\Admin\Resources\EggResource\RelationManagers;
|
||||
use App\Models\Egg;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
class EggResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
|
||||
protected static ?string $model = Egg::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
|
||||
@ -44,7 +52,16 @@ class EggResource extends Resource
|
||||
return ['name', 'tags', 'uuid', 'id'];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\ServersRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListEggs::route('/'),
|
||||
|
@ -5,6 +5,10 @@ namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
@ -28,11 +32,15 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class CreateEgg extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -3,12 +3,15 @@
|
||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Admin\Resources\EggResource\RelationManagers\ServersRelationManager;
|
||||
use App\Filament\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Forms\Fields\CopyFrom;
|
||||
use App\Models\Egg;
|
||||
use App\Models\EggVariable;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
@ -31,6 +34,9 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
/**
|
||||
@ -250,7 +256,8 @@ class EditEgg extends EditRecord
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
@ -272,11 +279,4 @@ class EditEgg extends EditRecord
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getRelationManagers(): array
|
||||
{
|
||||
return [
|
||||
ServersRelationManager::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,10 @@ use App\Filament\Components\Tables\Actions\UpdateEggAction;
|
||||
use App\Filament\Components\Tables\Actions\UpdateEggBulkAction;
|
||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||
use App\Models\Egg;
|
||||
use Filament\Actions\CreateAction;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction as CreateHeaderAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
@ -23,6 +26,9 @@ use Illuminate\Support\Str;
|
||||
|
||||
class ListEggs extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
@ -95,7 +101,8 @@ class ListEggs extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ImportEggHeaderAction::make()
|
||||
|
@ -4,6 +4,10 @@ namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource\Pages;
|
||||
use App\Models\Mount;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
@ -12,15 +16,22 @@ use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Group;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class MountResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = Mount::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-layers-linked';
|
||||
@ -44,7 +55,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
|
||||
@ -52,7 +63,10 @@ class MountResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -75,7 +89,7 @@ class MountResource extends Resource
|
||||
->badge()
|
||||
->icon(fn ($state) => $state ? 'tabler-writing-off' : 'tabler-writing')
|
||||
->color(fn ($state) => $state ? 'success' : 'warning')
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writeable')),
|
||||
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make()
|
||||
@ -93,9 +107,12 @@ class MountResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function form(Form|\Filament\Schemas\Schema $schema): \Filament\Schemas\Schema
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
return $schema
|
||||
return $form
|
||||
->schema([
|
||||
Section::make()->schema([
|
||||
TextInput::make('name')
|
||||
@ -147,7 +164,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(),
|
||||
]),
|
||||
@ -161,7 +178,8 @@ class MountResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListMounts::route('/'),
|
||||
@ -170,4 +188,15 @@ class MountResource extends Resource
|
||||
'edit' => Pages\EditMount::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->where(function (Builder $query) {
|
||||
return $query->whereHas('nodes', function (Builder $query) {
|
||||
$query->whereIn('nodes.id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
})->orDoesntHave('nodes');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,25 @@
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateMount extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = MountResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditMount extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = MountResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
|
@ -4,14 +4,22 @@ namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Models\Mount;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListMounts extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = MountResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\MountResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MountResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewMount extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = MountResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
|
@ -5,10 +5,18 @@ namespace App\Filament\Admin\Resources;
|
||||
use App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
use App\Filament\Admin\Resources\NodeResource\RelationManagers;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class NodeResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
|
||||
protected static ?string $model = Node::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-server-2';
|
||||
@ -37,10 +45,11 @@ 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
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\AllocationsRelationManager::class,
|
||||
@ -48,7 +57,8 @@ class NodeResource extends Resource
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListNodes::route('/'),
|
||||
@ -56,4 +66,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'));
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ namespace App\Filament\Admin\Resources\NodeResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource;
|
||||
use App\Models\Node;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
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;
|
||||
@ -21,6 +23,9 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class CreateNode extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = NodeResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
@ -124,15 +129,10 @@ class CreateNode extends CreateRecord
|
||||
'lg' => 1,
|
||||
]),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->label(trans('admin/node.port'))
|
||||
->helperText(trans('admin/node.port_help'))
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
->helperText(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port_help') : trans('admin/node.port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
@ -150,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()) {
|
||||
@ -170,20 +171,43 @@ 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');
|
||||
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
]),
|
||||
Step::make('advanced')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
@ -399,4 +423,13 @@ class CreateNode extends CreateRecord
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
if (!$data['behind_proxy']) {
|
||||
$data['daemon_listen'] = $data['daemon_connect'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,15 @@ use App\Repositories\Daemon\DaemonConfigurationRepository;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use App\Services\Nodes\NodeAutoDeployService;
|
||||
use App\Services\Nodes\NodeUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@ -35,6 +38,9 @@ use Illuminate\Support\HtmlString;
|
||||
|
||||
class EditNode extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = NodeResource::class;
|
||||
|
||||
private DaemonConfigurationRepository $daemonConfigurationRepository;
|
||||
@ -184,10 +190,10 @@ class EditNode extends EditRecord
|
||||
0 => 'danger',
|
||||
])
|
||||
->columnSpan(1),
|
||||
TextInput::make('daemon_listen')
|
||||
TextInput::make('daemon_connect')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.port'))
|
||||
->helperText(trans('admin/node.port_help'))
|
||||
->label(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port') : trans('admin/node.port'))
|
||||
->helperText(fn (Get $get) => $get('connection') === 'https_proxy' ? trans('admin/node.connect_port_help') : trans('admin/node.port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
@ -203,7 +209,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()
|
||||
@ -218,20 +226,43 @@ 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');
|
||||
|
||||
$set('daemon_connect', $state === 'https_proxy' ? 443 : 8080);
|
||||
$set('daemon_listen', 8080);
|
||||
}),
|
||||
TextInput::make('daemon_listen')
|
||||
->columnSpan(1)
|
||||
->label(trans('admin/node.listen_port'))
|
||||
->helperText(trans('admin/node.listen_port_help'))
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->default(8080)
|
||||
->required()
|
||||
->integer()
|
||||
->visible(fn (Get $get) => $get('connection') === 'https_proxy'),
|
||||
]),
|
||||
Tab::make('adv')
|
||||
->label(trans('admin/node.tabs.advanced_settings'))
|
||||
->columns([
|
||||
@ -618,7 +649,8 @@ class EditNode extends EditRecord
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
@ -628,6 +660,15 @@ class EditNode extends EditRecord
|
||||
];
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
if (!$data['behind_proxy']) {
|
||||
$data['daemon_listen'] = $data['daemon_connect'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
|
@ -6,6 +6,9 @@ use App\Filament\Admin\Resources\NodeResource;
|
||||
use App\Filament\Components\Tables\Columns\NodeHealthColumn;
|
||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||
use App\Models\Node;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\EditAction;
|
||||
@ -15,6 +18,9 @@ use Filament\Tables\Table;
|
||||
|
||||
class ListNodes extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = NodeResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
@ -72,7 +78,8 @@ class ListNodes extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
|
@ -13,7 +13,6 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\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()
|
||||
@ -79,7 +72,7 @@ class AllocationsRelationManager extends RelationManager
|
||||
->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([
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('update node')),
|
||||
]),
|
||||
->authorize(fn () => auth()->user()->can('update', $this->getOwnerRecord())),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
|
||||
|
||||
use App\Models\Node;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Number;
|
||||
@ -16,22 +15,34 @@ class NodeCpuChart extends ChartWidget
|
||||
|
||||
public Node $node;
|
||||
|
||||
/**
|
||||
* @var array<int, array{cpu: string, timestamp: string}>
|
||||
*/
|
||||
protected array $cpuHistory = [];
|
||||
|
||||
protected int $threads = 0;
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$threads = $this->node->systemInformation()['cpu_count'] ?? 0;
|
||||
$sessionKey = "node_stats.{$this->node->id}";
|
||||
|
||||
$cpu = collect(cache()->get("nodes.{$this->node->id}.cpu_percent"))
|
||||
->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'cpu' => round($value * $threads, 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
])
|
||||
->all();
|
||||
$data = $this->node->statistics();
|
||||
|
||||
$this->threads = session("{$sessionKey}.threads", $this->node->systemInformation()['cpu_count'] ?? 0);
|
||||
|
||||
$this->cpuHistory = session("{$sessionKey}.cpu_history", []);
|
||||
$this->cpuHistory[] = [
|
||||
'cpu' => round($data['cpu_percent'] * $this->threads, 2),
|
||||
'timestamp' => now(auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
];
|
||||
|
||||
$this->cpuHistory = array_slice($this->cpuHistory, -60);
|
||||
session()->put("{$sessionKey}.cpu_history", $this->cpuHistory);
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'data' => array_column($cpu, 'cpu'),
|
||||
'data' => array_column($this->cpuHistory, 'cpu'),
|
||||
'backgroundColor' => [
|
||||
'rgba(96, 165, 250, 0.3)',
|
||||
],
|
||||
@ -39,7 +50,7 @@ class NodeCpuChart extends ChartWidget
|
||||
'fill' => true,
|
||||
],
|
||||
],
|
||||
'labels' => array_column($cpu, 'timestamp'),
|
||||
'labels' => array_column($this->cpuHistory, 'timestamp'),
|
||||
'locale' => auth()->user()->language ?? 'en',
|
||||
];
|
||||
}
|
||||
@ -69,10 +80,10 @@ class NodeCpuChart extends ChartWidget
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
$threads = $this->node->systemInformation()['cpu_count'] ?? 0;
|
||||
$data = array_slice(end($this->cpuHistory), -60);
|
||||
|
||||
$cpu = Number::format(collect(cache()->get("nodes.{$this->node->id}.cpu_percent"))->last() * $threads, maxPrecision: 2, locale: auth()->user()->language);
|
||||
$max = Number::format($threads * 100, locale: auth()->user()->language);
|
||||
$cpu = Number::format($data['cpu'], maxPrecision: 2, locale: auth()->user()->language);
|
||||
$max = Number::format($this->threads * 100, locale: auth()->user()->language);
|
||||
|
||||
return trans('admin/node.cpu_chart', ['cpu' => $cpu, 'max' => $max]);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
|
||||
|
||||
use App\Models\Node;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Number;
|
||||
@ -16,19 +15,36 @@ class NodeMemoryChart extends ChartWidget
|
||||
|
||||
public Node $node;
|
||||
|
||||
/**
|
||||
* @var array<int, array{memory: string, timestamp: string}>
|
||||
*/
|
||||
protected array $memoryHistory = [];
|
||||
|
||||
protected int $totalMemory = 0;
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$memUsed = collect(cache()->get("nodes.{$this->node->id}.memory_used"))->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'memory' => round(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
])
|
||||
->all();
|
||||
$sessionKey = "node_stats.{$this->node->id}";
|
||||
|
||||
$data = $this->node->statistics();
|
||||
|
||||
$this->totalMemory = session("{$sessionKey}.total_memory", $data['memory_total']);
|
||||
|
||||
$this->memoryHistory = session("{$sessionKey}.memory_history", []);
|
||||
$this->memoryHistory[] = [
|
||||
'memory' => round(config('panel.use_binary_prefix')
|
||||
? $data['memory_used'] / 1024 / 1024 / 1024
|
||||
: $data['memory_used'] / 1000 / 1000 / 1000, 2),
|
||||
'timestamp' => now(auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
];
|
||||
|
||||
$this->memoryHistory = array_slice($this->memoryHistory, -60);
|
||||
session()->put("{$sessionKey}.memory_history", $this->memoryHistory);
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'data' => array_column($memUsed, 'memory'),
|
||||
'data' => array_column($this->memoryHistory, 'memory'),
|
||||
'backgroundColor' => [
|
||||
'rgba(96, 165, 250, 0.3)',
|
||||
],
|
||||
@ -36,7 +52,7 @@ class NodeMemoryChart extends ChartWidget
|
||||
'fill' => true,
|
||||
],
|
||||
],
|
||||
'labels' => array_column($memUsed, 'timestamp'),
|
||||
'labels' => array_column($this->memoryHistory, 'timestamp'),
|
||||
'locale' => auth()->user()->language ?? 'en',
|
||||
];
|
||||
}
|
||||
@ -66,16 +82,15 @@ class NodeMemoryChart extends ChartWidget
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
$latestMemoryUsed = collect(cache()->get("nodes.{$this->node->id}.memory_used"))->last();
|
||||
$totalMemory = collect(cache()->get("nodes.{$this->node->id}.memory_total"))->last();
|
||||
$latestMemoryUsed = array_slice(end($this->memoryHistory), -60);
|
||||
|
||||
$used = config('panel.use_binary_prefix')
|
||||
? Number::format($latestMemoryUsed / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($latestMemoryUsed / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
? Number::format($latestMemoryUsed['memory'], maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($latestMemoryUsed['memory'], maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
|
||||
$total = config('panel.use_binary_prefix')
|
||||
? Number::format($totalMemory / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($totalMemory / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
? Number::format($this->totalMemory / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
|
||||
: Number::format($this->totalMemory / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
|
||||
|
||||
return trans('admin/node.memory_chart', ['used' => $used, 'total' => $total]);
|
||||
}
|
||||
|
@ -10,10 +10,16 @@ use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
@ -26,6 +32,11 @@ use Spatie\Permission\Contracts\Permission;
|
||||
|
||||
class RoleResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = Role::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-users-group';
|
||||
@ -57,7 +68,7 @@ class RoleResource extends Resource
|
||||
return static::getModel()::count() ?: null;
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -69,6 +80,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')
|
||||
@ -94,7 +110,7 @@ class RoleResource extends Resource
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function form(Schema $schema): Schema
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
$permissionSections = [];
|
||||
|
||||
@ -108,7 +124,7 @@ class RoleResource extends Resource
|
||||
$permissionSections[] = self::makeSection($model, $options);
|
||||
}
|
||||
|
||||
return $schema
|
||||
return $form
|
||||
->columns(1)
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
@ -128,6 +144,14 @@ class RoleResource extends Resource
|
||||
->label(trans('admin/role.permissions'))
|
||||
->state(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),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -138,6 +162,8 @@ class RoleResource extends Resource
|
||||
*/
|
||||
private static function makeSection(string $model, array $options): Section
|
||||
{
|
||||
$model = ucwords($model);
|
||||
|
||||
$icon = null;
|
||||
|
||||
if (class_exists('\App\Filament\Admin\Resources\\' . $model . 'Resource')) {
|
||||
@ -189,7 +215,8 @@ class RoleResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListRoles::route('/'),
|
||||
|
@ -4,6 +4,10 @@ namespace App\Filament\Admin\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\RoleResource;
|
||||
use App\Models\Role;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -14,13 +18,17 @@ use Spatie\Permission\Models\Permission;
|
||||
*/
|
||||
class CreateRole extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
public Collection $permissions;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -4,6 +4,10 @@ namespace App\Filament\Admin\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\RoleResource;
|
||||
use App\Models\Role;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -15,6 +19,9 @@ use Spatie\Permission\Models\Permission;
|
||||
*/
|
||||
class EditRole extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
public Collection $permissions;
|
||||
@ -45,7 +52,8 @@ class EditRole extends EditRecord
|
||||
$this->record->syncPermissions($permissionModels);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\RoleResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListRoles extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\RoleResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewRole extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
|
@ -3,11 +3,23 @@
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\ServerResource\Pages;
|
||||
use App\Filament\Admin\Resources\ServerResource\RelationManagers;
|
||||
use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ServerResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
|
||||
protected static ?string $model = Server::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-brand-docker';
|
||||
@ -36,10 +48,42 @@ class ServerResource extends Resource
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
return (string) static::getEloquentQuery()->count() ?: null;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
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();
|
||||
}
|
||||
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\AllocationsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListServers::route('/'),
|
||||
@ -47,4 +91,11 @@ class ServerResource extends Resource
|
||||
'edit' => Pages\EditServer::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
return $query->whereIn('node_id', auth()->user()->accessibleNodes()->pluck('id'));
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ use App\Services\Allocations\AssignmentService;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
use App\Services\Servers\ServerCreationService;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
@ -26,7 +27,6 @@ use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
@ -46,6 +46,9 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class CreateServer extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ServerResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
@ -109,14 +112,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) {
|
||||
@ -139,6 +148,7 @@ class CreateServer extends CreateRecord
|
||||
->relationship('user', 'username')
|
||||
->searchable(['username', 'email'])
|
||||
->getOptionLabelFromRecordUsing(fn (User $user) => "$user->username ($user->email)")
|
||||
->createOptionAction(fn (Action $action) => $action->authorize(fn () => auth()->user()->can('create', User::class)))
|
||||
->createOptionForm([
|
||||
TextInput::make('username')
|
||||
->label(trans('admin/user.username'))
|
||||
@ -183,10 +193,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'));
|
||||
|
||||
@ -203,6 +210,7 @@ class CreateServer extends CreateRecord
|
||||
->where('node_id', $get('node_id'))
|
||||
->whereNull('server_id'),
|
||||
)
|
||||
->createOptionAction(fn (Action $action) => $action->authorize(fn (Get $get) => auth()->user()->can('create', Node::find($get('node_id')))))
|
||||
->createOptionForm(function (Get $get) {
|
||||
$getPage = $get;
|
||||
|
||||
@ -212,7 +220,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 +271,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 +749,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 +803,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,10 +2,9 @@
|
||||
|
||||
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;
|
||||
use App\Filament\Components\Forms\Actions\PreviewStartupAction;
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
@ -13,7 +12,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;
|
||||
@ -27,10 +25,11 @@ use App\Services\Servers\ServerDeletionService;
|
||||
use App\Services\Servers\SuspensionService;
|
||||
use App\Services\Servers\ToggleInstallService;
|
||||
use App\Services\Servers\TransferServerService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Schemas\Components\Actions;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
@ -51,6 +50,7 @@ use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -62,6 +62,9 @@ use Random\RandomException;
|
||||
|
||||
class EditServer extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ServerResource::class;
|
||||
|
||||
private DaemonServerRepository $daemonServerRepository;
|
||||
@ -140,6 +143,51 @@ class EditServer extends EditRecord
|
||||
// 'lg' => 1,
|
||||
// ]),
|
||||
|
||||
ToggleButtons::make('condition')
|
||||
->label(trans('admin/server.server_status'))
|
||||
->formatStateUsing(fn (Server $server) => $server->condition)
|
||||
->options(fn ($state) => [$state->value => $state->getLabel()])
|
||||
->colors(fn ($state) => [$state->value => $state->getColor()])
|
||||
->icons(fn ($state) => [$state->value => $state->getIcon()])
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 1,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
])
|
||||
->hintAction(
|
||||
Action::make('view_install_log')
|
||||
->label(trans('admin/server.view_install_log'))
|
||||
//->visible(fn (Server $server) => $server->isFailedInstall())
|
||||
->modalHeading('')
|
||||
->modalSubmitAction(false)
|
||||
->modalFooterActionsAlignment(Alignment::Right)
|
||||
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
|
||||
->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'))
|
||||
->columnSpanFull(),
|
||||
@ -178,7 +226,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,
|
||||
@ -667,17 +715,11 @@ 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'))
|
||||
->hidden(fn () => !auth()->user()->can('viewAny', Database::class))
|
||||
->icon('tabler-database')
|
||||
->columns(4)
|
||||
->schema([
|
||||
@ -707,8 +749,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();
|
||||
@ -754,7 +796,7 @@ class EditServer extends EditRecord
|
||||
->columnSpan(4),
|
||||
Actions::make([
|
||||
Action::make('createDatabase')
|
||||
->authorize(fn () => auth()->user()->can('create database'))
|
||||
->authorize(fn () => auth()->user()->can('create', Database::class))
|
||||
->disabled(fn () => DatabaseHost::query()->count() < 1)
|
||||
->label(fn () => DatabaseHost::query()->count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
|
||||
->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary')
|
||||
@ -825,12 +867,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);
|
||||
|
||||
@ -842,7 +884,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();
|
||||
}
|
||||
@ -889,7 +931,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();
|
||||
}
|
||||
}),
|
||||
@ -910,7 +952,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();
|
||||
}
|
||||
}),
|
||||
@ -973,7 +1015,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();
|
||||
}
|
||||
@ -1021,7 +1063,8 @@ class EditServer extends EditRecord
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = $this->getRecord();
|
||||
@ -1091,7 +1134,7 @@ class EditServer extends EditRecord
|
||||
$data['description'] = '';
|
||||
}
|
||||
|
||||
unset($data['docker'], $data['condition']);
|
||||
unset($data['docker'], $data['status'], $data['allocation_id']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ namespace App\Filament\Admin\Resources\ServerResource\Pages;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
use App\Filament\Admin\Resources\ServerResource;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\Action;
|
||||
@ -17,6 +19,9 @@ use Filament\Tables\Table;
|
||||
|
||||
class ListServers extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ServerResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
@ -68,13 +73,13 @@ class ListServers extends ListRecords
|
||||
->searchable(),
|
||||
SelectColumn::make('allocation_id')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->hidden(!auth()->user()->can('update server'))
|
||||
->hidden(!auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
|
||||
->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
|
||||
->selectablePlaceholder(false)
|
||||
->sortable(),
|
||||
TextColumn::make('allocation_id_readonly')
|
||||
->label(trans('admin/server.primary_allocation'))
|
||||
->hidden(auth()->user()->can('update server'))
|
||||
->hidden(auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
|
||||
->state(fn (Server $server) => $server->allocation->address),
|
||||
TextColumn::make('image')->hidden(),
|
||||
TextColumn::make('backups_count')
|
||||
@ -101,7 +106,8 @@ class ListServers extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
|
@ -34,15 +34,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 +61,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 +75,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 +91,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,6 +6,10 @@ use App\Filament\Admin\Resources\UserResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource\RelationManagers;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@ -22,6 +26,11 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-users';
|
||||
@ -53,7 +62,7 @@ class UserResource extends Resource
|
||||
return static::getModel()::count() ?: null;
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -101,9 +110,9 @@ class UserResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
return $schema
|
||||
return $form
|
||||
->columns(['default' => 1, 'lg' => 3])
|
||||
->components([
|
||||
TextInput::make('username')
|
||||
@ -148,14 +157,16 @@ class UserResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
RelationManagers\ServersRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
|
@ -5,11 +5,18 @@ namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
@ -21,7 +28,8 @@ class CreateUser extends CreateRecord
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -5,12 +5,19 @@ namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
private UserUpdateService $service;
|
||||
@ -20,7 +27,8 @@ class EditUser extends EditRecord
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewUser extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
|
@ -6,9 +6,14 @@ use App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
@ -19,6 +24,11 @@ use Filament\Schemas\Schema;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = WebhookConfiguration::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-webhook';
|
||||
@ -50,7 +60,7 @@ class WebhookResource extends Resource
|
||||
return trans('admin/dashboard.advanced');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
@ -76,9 +86,9 @@ class WebhookResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
public static function form(Schema $form): Schema
|
||||
{
|
||||
return $schema
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
@ -99,7 +109,8 @@ class WebhookResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListWebhookConfigurations::route('/'),
|
||||
|
@ -3,15 +3,23 @@
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebhookConfiguration extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
|
@ -4,14 +4,22 @@ namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListWebhookConfigurations extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
|
@ -3,14 +3,22 @@
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewWebhookConfiguration extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
|
@ -3,14 +3,13 @@
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Widgets\Widget;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
class CanaryWidget extends Widget
|
||||
class CanaryWidget extends FormWidget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.canary-widget';
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static ?int $sort = 1;
|
||||
|
||||
public static function canView(): bool
|
||||
@ -18,14 +17,28 @@ class CanaryWidget extends Widget
|
||||
return config('app.version') === 'canary';
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return [
|
||||
'action' => Action::make('github')
|
||||
return $form
|
||||
->schema([
|
||||
Section::make(trans('admin/dashboard.sections.intro-developers.heading'))
|
||||
->icon('tabler-code')
|
||||
->iconColor('primary')
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->persistCollapsed()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-developers.content')),
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-developers.extra_note')),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('issues')
|
||||
->label(trans('admin/dashboard.sections.intro-developers.button_issues'))
|
||||
->icon('tabler-brand-github')
|
||||
->url('https://github.com/pelican-dev/panel/issues', true)
|
||||
->toHtmlString(),
|
||||
];
|
||||
->url('https://github.com/pelican-dev/panel/issues', true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
16
app/Filament/Admin/Widgets/FormWidget.php
Normal file
16
app/Filament/Admin/Widgets/FormWidget.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Widgets\Widget;
|
||||
|
||||
abstract class FormWidget extends Widget implements HasForms
|
||||
{
|
||||
use InteractsWithForms;
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static string $view = 'filament.admin.widgets.form-widget';
|
||||
}
|
@ -2,25 +2,34 @@
|
||||
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Widgets\Widget;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
class HelpWidget extends Widget
|
||||
class HelpWidget extends FormWidget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.help-widget';
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static ?int $sort = 4;
|
||||
|
||||
public function getViewData(): array
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return [
|
||||
'action' => Action::make('docs')
|
||||
return $form
|
||||
->schema([
|
||||
Section::make(trans('admin/dashboard.sections.intro-help.heading'))
|
||||
->icon('tabler-question-mark')
|
||||
->iconColor('info')
|
||||
->collapsible()
|
||||
->persistCollapsed()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-help.content')),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('docs')
|
||||
->label(trans('admin/dashboard.sections.intro-help.button_docs'))
|
||||
->icon('tabler-speedboat')
|
||||
->url('https://pelican.dev/docs', true)
|
||||
->toHtmlString(),
|
||||
];
|
||||
->url('https://pelican.dev/docs', true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,13 @@ namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use App\Filament\Admin\Resources\NodeResource\Pages\CreateNode;
|
||||
use App\Models\Node;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Widgets\Widget;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
class NoNodesWidget extends Widget
|
||||
class NoNodesWidget extends FormWidget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.no-nodes-widget';
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
public static function canView(): bool
|
||||
@ -20,14 +18,25 @@ class NoNodesWidget extends Widget
|
||||
return Node::count() <= 0;
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return [
|
||||
'action' => Action::make('create-node')
|
||||
return $form
|
||||
->schema([
|
||||
Section::make(trans('admin/dashboard.sections.intro-first-node.heading'))
|
||||
->icon('tabler-server-2')
|
||||
->iconColor('primary')
|
||||
->collapsible()
|
||||
->persistCollapsed()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-first-node.content')),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('create-node')
|
||||
->label(trans('admin/dashboard.sections.intro-first-node.button_label'))
|
||||
->icon('tabler-server-2')
|
||||
->url(CreateNode::getUrl())
|
||||
->toHtmlString(),
|
||||
];
|
||||
->url(CreateNode::getUrl()),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,26 +2,37 @@
|
||||
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Widgets\Widget;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
class SupportWidget extends Widget
|
||||
class SupportWidget extends FormWidget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.support-widget';
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static ?int $sort = 3;
|
||||
|
||||
public function getViewData(): array
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return [
|
||||
'action' => Action::make('donate')
|
||||
return $form
|
||||
->schema([
|
||||
Section::make(trans('admin/dashboard.sections.intro-support.heading'))
|
||||
->icon('tabler-heart-filled')
|
||||
->iconColor('danger')
|
||||
->collapsible()
|
||||
->persistCollapsed()
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-support.content')),
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-support.extra_note')),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('donate')
|
||||
->label(trans('admin/dashboard.sections.intro-support.button_donate'))
|
||||
->icon('tabler-cash')
|
||||
->url('https://pelican.dev/donate', true)
|
||||
->color('success')
|
||||
->toHtmlString(),
|
||||
];
|
||||
->color('success'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,13 @@
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Widgets\Widget;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Form;
|
||||
|
||||
class UpdateWidget extends Widget
|
||||
class UpdateWidget extends FormWidget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.update-widget';
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static ?int $sort = 0;
|
||||
|
||||
private SoftwareVersionService $softwareVersionService;
|
||||
@ -21,18 +19,34 @@ class UpdateWidget extends Widget
|
||||
$this->softwareVersionService = $softwareVersionService;
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return [
|
||||
'version' => $this->softwareVersionService->currentPanelVersion(),
|
||||
'latestVersion' => $this->softwareVersionService->latestPanelVersion(),
|
||||
'isLatest' => $this->softwareVersionService->isLatestPanel(),
|
||||
'action' => Action::make('update')
|
||||
$isLatest = $this->softwareVersionService->isLatestPanel();
|
||||
|
||||
return $form
|
||||
->schema([
|
||||
$isLatest
|
||||
? Section::make(trans('admin/dashboard.sections.intro-no-update.heading'))
|
||||
->icon('tabler-checkbox')
|
||||
->iconColor('success')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-no-update.content', ['version' => $this->softwareVersionService->currentPanelVersion()])),
|
||||
])
|
||||
: Section::make(trans('admin/dashboard.sections.intro-update-available.heading'))
|
||||
->icon('tabler-info-circle')
|
||||
->iconColor('warning')
|
||||
->schema([
|
||||
Placeholder::make('')
|
||||
->content(trans('admin/dashboard.sections.intro-update-available.content', ['latestVersion' => $this->softwareVersionService->latestPanelVersion()])),
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('update')
|
||||
->label(trans('admin/dashboard.sections.intro-update-available.heading'))
|
||||
->icon('tabler-clipboard-text')
|
||||
->url('https://pelican.dev/docs/panel/update', true)
|
||||
->color('warning')
|
||||
->toHtmlString(),
|
||||
];
|
||||
->color('warning'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,13 @@ 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 App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Support\Enums\TextSize;
|
||||
@ -16,48 +22,64 @@ 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
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ServerResource::class;
|
||||
|
||||
public const DANGER_THRESHOLD = 0.9;
|
||||
|
||||
public const WARNING_THRESHOLD = 0.7;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$baseQuery = auth()->user()->accessibleServers();
|
||||
private DaemonPowerRepository $daemonPowerRepository;
|
||||
|
||||
$viewOne = [
|
||||
public function boot(): void
|
||||
{
|
||||
$this->daemonPowerRepository = new DaemonPowerRepository();
|
||||
}
|
||||
|
||||
/** @return Stack[] */
|
||||
protected function gridColumns(): array
|
||||
{
|
||||
return [
|
||||
Stack::make([
|
||||
ServerEntryColumn::make('server_entry')
|
||||
->searchable(['name']),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return Column[] */
|
||||
protected function tableColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make('condition')
|
||||
->label('')
|
||||
->default('unknown')
|
||||
->wrap()
|
||||
->size(TextSize::Medium)
|
||||
->badge()
|
||||
->alignCenter()
|
||||
->tooltip(fn (Server $server) => $server->formatResource('uptime', type: ServerResourceType::Time))
|
||||
->icon(fn (Server $server) => $server->condition->getIcon())
|
||||
->color(fn (Server $server) => $server->condition->getColor()),
|
||||
];
|
||||
|
||||
$viewTwo = [
|
||||
TextColumn::make('name')
|
||||
->description(fn (Server $server) => $server->description)
|
||||
->grow()
|
||||
->label('')
|
||||
->size(TextSize::Medium)
|
||||
->searchable(),
|
||||
TextColumn::make('iNeedAName')
|
||||
TextColumn::make('allocation.address')
|
||||
->label('')
|
||||
->badge()
|
||||
->size(TextSize::Medium)
|
||||
->copyable(request()->isSecure())
|
||||
->copyMessage(fn (Server $server, string $state) => 'Copied ' . $server->allocation->address)
|
||||
->state(fn (Server $server) => $server->allocation->address),
|
||||
];
|
||||
|
||||
$viewThree = [
|
||||
->visibleFrom('md')
|
||||
->copyable(request()->isSecure()),
|
||||
TextColumn::make('cpuUsage')
|
||||
->label('Resources')
|
||||
->label('')
|
||||
->size(TextSize::Medium)
|
||||
->icon('tabler-cpu')
|
||||
@ -66,6 +88,7 @@ class ListServers extends ListRecords
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'cpu')),
|
||||
TextColumn::make('memoryUsage')
|
||||
->label('')
|
||||
->icon('tabler-device-desktop-analytics')
|
||||
->size(TextSize::Medium)
|
||||
->icon('tabler-memory')
|
||||
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('memory', limit: true))
|
||||
@ -73,42 +96,30 @@ class ListServers extends ListRecords
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'memory')),
|
||||
TextColumn::make('diskUsage')
|
||||
->label('')
|
||||
->icon('tabler-device-sd-card')
|
||||
->size(TextSize::Medium)
|
||||
->icon('tabler-device-floppy')
|
||||
->tooltip(fn (Server $server) => 'Usage Limit: ' . $server->formatResource('disk', limit: true))
|
||||
->state(fn (Server $server) => $server->formatResource('disk_bytes'))
|
||||
->color(fn (Server $server) => $this->getResourceColor($server, 'disk')),
|
||||
];
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
$baseQuery = auth()->user()->accessibleServers();
|
||||
|
||||
$usingGrid = (auth()->user()->getCustomization()['dashboard_layout'] ?? 'grid') === 'grid';
|
||||
|
||||
return $table
|
||||
->paginated(false)
|
||||
->query(fn () => $baseQuery)
|
||||
->poll('15s')
|
||||
->columns(
|
||||
(auth()->user()->getCustomization()['dashboard_layout'] ?? 'grid') === 'grid'
|
||||
? [
|
||||
Stack::make([
|
||||
ServerEntryColumn::make('server_entry')
|
||||
->searchable(['name']),
|
||||
]),
|
||||
]
|
||||
: [
|
||||
ColumnGroup::make('Status')
|
||||
->label('Status')
|
||||
->columns($viewOne),
|
||||
ColumnGroup::make('Server')
|
||||
->label('Servers')
|
||||
->columns($viewTwo),
|
||||
ColumnGroup::make('Resources')
|
||||
->label('Resources')
|
||||
->columns($viewThree),
|
||||
]
|
||||
)
|
||||
->recordUrl(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
|
||||
->contentGrid([
|
||||
'default' => 1,
|
||||
'md' => 2,
|
||||
])
|
||||
->columns($usingGrid ? $this->gridColumns() : $this->tableColumns())
|
||||
->recordUrl(!$usingGrid ? (fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)) : null)
|
||||
->actions(!$usingGrid ? ActionGroup::make(static::getPowerActions()) : [])
|
||||
->actionsAlignment(Alignment::Center->value)
|
||||
->contentGrid($usingGrid ? ['default' => 1, 'md' => 2] : null)
|
||||
->emptyStateIcon('tabler-brand-docker')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(fn () => $this->activeTab === 'my' ? 'You don\'t own any servers!' : 'You don\'t have access to any servers!')
|
||||
@ -151,36 +162,33 @@ class ListServers extends ListRecords
|
||||
];
|
||||
}
|
||||
|
||||
public function getResourceColor(Server $server, string $resource): ?string
|
||||
protected function getResourceColor(Server $server, string $resource): ?string
|
||||
{
|
||||
$current = null;
|
||||
$limit = null;
|
||||
|
||||
switch ($resource) {
|
||||
case 'cpu':
|
||||
$current = $server->resources()['cpu_absolute'] ?? 0;
|
||||
$current = $server->retrieveResources()['cpu_absolute'] ?? 0;
|
||||
$limit = $server->cpu;
|
||||
if ($server->cpu === 0) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'memory':
|
||||
$current = $server->resources()['memory_bytes'] ?? 0;
|
||||
$current = $server->retrieveResources()['memory_bytes'] ?? 0;
|
||||
$limit = $server->memory * 2 ** 20;
|
||||
if ($server->memory === 0) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'disk':
|
||||
$current = $server->resources()['disk_bytes'] ?? 0;
|
||||
$current = $server->retrieveResources()['disk_bytes'] ?? 0;
|
||||
$limit = $server->disk * 2 ** 20;
|
||||
if ($server->disk === 0) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -194,6 +202,60 @@ 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();
|
||||
|
||||
cache()->forget("servers.$server->uuid.status");
|
||||
|
||||
$this->redirect(self::getUrl(['activeTab' => $this->activeTab]));
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title(trans('exceptions.node.error_connecting', ['node' => $server->node->name]))
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
/** @return Action[] */
|
||||
public static function getPowerActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('start')
|
||||
->color('primary')
|
||||
->icon('tabler-player-play-filled')
|
||||
->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_START, $server))
|
||||
->visible(fn (Server $server) => !$server->isInConflictState() & $server->retrieveStatus()->isStartable())
|
||||
->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'start']),
|
||||
Action::make('restart')
|
||||
->color('gray')
|
||||
->icon('tabler-reload')
|
||||
->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_RESTART, $server))
|
||||
->visible(fn (Server $server) => !$server->isInConflictState() & $server->retrieveStatus()->isRestartable())
|
||||
->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'restart']),
|
||||
Action::make('stop')
|
||||
->color('danger')
|
||||
->icon('tabler-player-stop-filled')
|
||||
->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server))
|
||||
->visible(fn (Server $server) => !$server->isInConflictState() & $server->retrieveStatus()->isStoppable())
|
||||
->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'stop']),
|
||||
Action::make('kill')
|
||||
->color('danger')
|
||||
->icon('tabler-alert-square')
|
||||
->tooltip('This can result in data corruption and/or data loss!')
|
||||
->authorize(fn (Server $server) => auth()->user()->can(Permission::ACTION_CONTROL_STOP, $server))
|
||||
->visible(fn (Server $server) => !$server->isInConflictState() & $server->retrieveStatus()->isKillable())
|
||||
->dispatch('powerAction', fn (Server $server) => ['server' => $server, 'action' => 'kill']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class RotateDatabasePasswordAction extends Action
|
||||
|
||||
$this->icon('tabler-refresh');
|
||||
|
||||
$this->authorize(fn (Database $database) => auth()->user()->can('update database', $database));
|
||||
$this->authorize(fn (Database $database) => auth()->user()->can('update', $database));
|
||||
|
||||
$this->modalHeading(trans('admin/databasehost.rotate_password'));
|
||||
|
||||
|
@ -6,5 +6,5 @@ use Filament\Tables\Columns\Column;
|
||||
|
||||
class ServerEntryColumn extends Column
|
||||
{
|
||||
protected string $view = 'tables.columns.server-entry-column';
|
||||
protected string $view = 'livewire.columns.server-entry-column';
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ use App\Services\Helpers\LanguageService;
|
||||
use App\Services\Users\ToggleTwoFactorService;
|
||||
use App\Services\Users\TwoFactorSetupService;
|
||||
use App\Services\Users\UserUpdateService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use chillerlan\QRCode\Common\EccLevel;
|
||||
use chillerlan\QRCode\Common\Version;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
@ -41,6 +43,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;
|
||||
@ -50,6 +53,9 @@ use Laravel\Socialite\Facades\Socialite;
|
||||
*/
|
||||
class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
private ToggleTwoFactorService $toggleTwoFactorService;
|
||||
|
||||
public function boot(ToggleTwoFactorService $toggleTwoFactorService): void
|
||||
@ -289,6 +295,8 @@ class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
);
|
||||
|
||||
Activity::event('user:api-key.create')
|
||||
->actor($user)
|
||||
->subject($user)
|
||||
->subject($token->accessToken)
|
||||
->property('identifier', $token->accessToken->identifier)
|
||||
->log();
|
||||
@ -363,6 +371,95 @@ class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
'grid' => trans('profile.grid'),
|
||||
'table' => trans('profile.table'),
|
||||
]),
|
||||
Section::make(trans('profile.console'))
|
||||
->collapsible()
|
||||
->icon('tabler-brand-tabler')
|
||||
->columns(4)
|
||||
->schema([
|
||||
TextInput::make('console_font_size')
|
||||
->label(trans('profile.font_size'))
|
||||
->columnSpan(1)
|
||||
->minValue(1)
|
||||
->numeric()
|
||||
->required()
|
||||
->default(14),
|
||||
Select::make('console_font')
|
||||
->label(trans('profile.font'))
|
||||
->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, Set $set) => $set('font_preview', $state)),
|
||||
Placeholder::make('font_preview')
|
||||
->label(trans('profile.font_preview'))
|
||||
->columnSpan(2)
|
||||
->content(function (Get $get) {
|
||||
$fontName = $get('console_font') ?? 'monospace';
|
||||
$fontSize = $get('console_font_size') . 'px';
|
||||
$style = <<<CSS
|
||||
.preview-text {
|
||||
font-family: $fontName;
|
||||
font-size: $fontSize;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
CSS;
|
||||
if ($fontName !== 'monospace') {
|
||||
$fontUrl = asset("storage/fonts/$fontName.ttf");
|
||||
$style = <<<CSS
|
||||
@font-face {
|
||||
font-family: $fontName;
|
||||
src: url("$fontUrl");
|
||||
}
|
||||
$style
|
||||
CSS;
|
||||
}
|
||||
|
||||
return new HtmlString(<<<HTML
|
||||
<style>
|
||||
{$style}
|
||||
</style>
|
||||
<span class="preview-text">The quick blue pelican jumps over the lazy pterodactyl. :)</span>
|
||||
HTML);
|
||||
}),
|
||||
TextInput::make('console_graph_period')
|
||||
->label(trans('profile.graph_period'))
|
||||
->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()
|
||||
->columnSpan(2)
|
||||
->default(30),
|
||||
]),
|
||||
]),
|
||||
Section::make(trans('profile.console'))
|
||||
->collapsible()
|
||||
@ -435,7 +532,8 @@ class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<HeaderAction|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
@ -446,12 +544,14 @@ class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
$moarbetterdata = [
|
||||
'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'],
|
||||
];
|
||||
|
||||
unset($data['dashboard_layout'], $data['console_font_size'], $data['console_rows']);
|
||||
unset($data['console_font'],$data['console_font_size'], $data['console_rows'], $data['dashboard_layout']);
|
||||
$data['customization'] = json_encode($moarbetterdata);
|
||||
|
||||
return $data;
|
||||
@ -461,8 +561,10 @@ class EditProfile extends \Filament\Auth\Pages\EditProfile
|
||||
{
|
||||
$moarbetterdata = json_decode($data['customization'], true);
|
||||
|
||||
$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;
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Filament\Pages\Auth;
|
||||
|
||||
use App\Events\Auth\ProvidedAuthenticationToken;
|
||||
use App\Extensions\Captcha\Providers\CaptchaProvider;
|
||||
use App\Extensions\OAuth\Providers\OAuthProvider;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\User;
|
||||
use Filament\Auth\Http\Responses\LoginResponse;
|
||||
use Filament\Facades\Filament;
|
||||
@ -54,15 +56,38 @@ class Login extends \Filament\Auth\Pages\Login
|
||||
if ($token === null) {
|
||||
$this->verifyTwoFactor = true;
|
||||
|
||||
Activity::event('auth:checkpoint')
|
||||
->withRequestMetadata()
|
||||
->subject($user)
|
||||
->log();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$isValidToken = false;
|
||||
if (strlen($token) === $this->google2FA->getOneTimePasswordLength()) {
|
||||
$isValidToken = $this->google2FA->verifyKey(
|
||||
$user->totp_secret,
|
||||
$token,
|
||||
Config::integer('panel.auth.2fa.window'),
|
||||
);
|
||||
|
||||
if ($isValidToken) {
|
||||
event(new ProvidedAuthenticationToken($user));
|
||||
}
|
||||
} else {
|
||||
foreach ($user->recoveryTokens as $recoveryToken) {
|
||||
if (password_verify($token, $recoveryToken->token)) {
|
||||
$isValidToken = true;
|
||||
$recoveryToken->delete();
|
||||
|
||||
event(new ProvidedAuthenticationToken($user, true));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isValidToken) {
|
||||
// Buffer to prevent bruteforce
|
||||
Sleep::sleep(1);
|
||||
@ -103,7 +128,9 @@ class Login extends \Filament\Auth\Pages\Login
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
@ -2,59 +2,29 @@
|
||||
|
||||
namespace App\Filament\Server\Components;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Concerns\CanOpenUrl;
|
||||
use Filament\Schemas\Components\Concerns\HasDescription;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Filament\Support\Concerns\EvaluatesClosures;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
class SmallStatBlock extends Component
|
||||
{
|
||||
use CanOpenUrl;
|
||||
use HasDescription;
|
||||
use EvaluatesClosures;
|
||||
|
||||
protected string $view = 'filament.components.server-small-data-block';
|
||||
protected bool|Closure $copyOnClick = false;
|
||||
|
||||
protected string|BackedEnum|null $icon = null;
|
||||
public function copyOnClick(bool|Closure $copyOnClick = true): static
|
||||
{
|
||||
$this->copyOnClick = $copyOnClick;
|
||||
|
||||
protected string $value;
|
||||
|
||||
final public function __construct(string $label, string $value)
|
||||
public function shouldCopyOnClick(): bool
|
||||
{
|
||||
$this->label($label);
|
||||
$this->value($value);
|
||||
return $this->evaluate($this->copyOnClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SmallStatBlock
|
||||
*/
|
||||
public static function make(string $label, string $value): static
|
||||
{
|
||||
return app(static::class, ['label' => $label, 'value' => $value]);
|
||||
}
|
||||
|
||||
public function icon(string|BackedEnum|null $icon): static
|
||||
{
|
||||
$this->icon = $icon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SmallStatBlock
|
||||
*/
|
||||
private function value(string $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return scalar | Htmlable | Closure
|
||||
*/
|
||||
public function getValue(): mixed
|
||||
public function render(): View
|
||||
{
|
||||
return value($this->value);
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Components;
|
||||
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class StatBlock extends Stat
|
||||
{
|
||||
protected string|\Closure|Htmlable|null $label;
|
||||
|
||||
protected string $view = 'filament.components.server-data-block';
|
||||
|
||||
protected $value;
|
||||
|
||||
public function label(string $label): static
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function value($value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getValue(): mixed
|
||||
{
|
||||
return value($this->value);
|
||||
}
|
||||
|
||||
public function toHtml(): string
|
||||
{
|
||||
return $this->render()->render();
|
||||
}
|
||||
}
|
@ -9,13 +9,16 @@ use App\Extensions\Features\FeatureProvider;
|
||||
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\ServerNetworkChart;
|
||||
use App\Filament\Server\Widgets\ServerOverview;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\Size;
|
||||
use Filament\Widgets\Widget;
|
||||
@ -24,7 +27,8 @@ use Livewire\Attributes\On;
|
||||
|
||||
class Console extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-brand-tabler';
|
||||
use CanCustomizeHeaderActions;
|
||||
use InteractsWithActions;
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
@ -109,7 +113,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] ?? []);
|
||||
@ -142,7 +146,8 @@ class Console extends Page
|
||||
$this->cacheHeaderActions();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
@ -3,6 +3,9 @@
|
||||
namespace App\Filament\Server\Pages;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Schemas\Components\Form;
|
||||
@ -15,6 +18,9 @@ use Filament\Schemas\Schema;
|
||||
*/
|
||||
abstract class ServerFormPage extends Page
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use InteractsWithFormActions;
|
||||
use InteractsWithForms;
|
||||
|
||||
@ -52,17 +58,4 @@ abstract class ServerFormPage extends Page
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,40 @@
|
||||
|
||||
namespace App\Filament\Server\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\ActivityResource\Pages;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ActivityResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = ActivityLog::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Activity';
|
||||
@ -27,12 +48,96 @@ class ActivityResource extends Resource
|
||||
|
||||
protected static bool $isScopedToTenant = false;
|
||||
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return $table
|
||||
->paginated([25, 50])
|
||||
->defaultPaginationPageOption(25)
|
||||
->columns([
|
||||
TextColumn::make('event')
|
||||
->html()
|
||||
->description(fn ($state) => $state)
|
||||
->icon(fn (ActivityLog $activityLog) => $activityLog->getIcon())
|
||||
->formatStateUsing(fn (ActivityLog $activityLog) => $activityLog->getLabel()),
|
||||
TextColumn::make('user')
|
||||
->state(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
|
||||
// Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin
|
||||
if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) {
|
||||
$user .= " ({$activityLog->actor->email})";
|
||||
}
|
||||
|
||||
return $user;
|
||||
})
|
||||
->tooltip(fn (ActivityLog $activityLog) => auth()->user()->can('seeIps activityLog') ? $activityLog->ip : '')
|
||||
->url(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update', $activityLog->actor) ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin') : '')
|
||||
->grow(false),
|
||||
DateTimeColumn::make('timestamp')
|
||||
->since()
|
||||
->sortable()
|
||||
->grow(false),
|
||||
])
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->actions([
|
||||
ViewAction::make()
|
||||
//->visible(fn (ActivityLog $activityLog) => $activityLog->hasAdditionalMetadata())
|
||||
->form([
|
||||
Placeholder::make('event')
|
||||
->content(fn (ActivityLog $activityLog) => new HtmlString($activityLog->getLabel())),
|
||||
TextInput::make('user')
|
||||
->formatStateUsing(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
|
||||
// Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin
|
||||
if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) {
|
||||
$user .= " ({$activityLog->actor->email})";
|
||||
}
|
||||
|
||||
if (auth()->user()->can('seeIps activityLog')) {
|
||||
$user .= " - $activityLog->ip";
|
||||
}
|
||||
|
||||
return $user;
|
||||
})
|
||||
->hintAction(
|
||||
Action::make('edit')
|
||||
->label(trans('filament-actions::edit.single.label'))
|
||||
->icon('tabler-edit')
|
||||
->visible(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update', $activityLog->actor))
|
||||
->url(fn (ActivityLog $activityLog) => EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin'))
|
||||
),
|
||||
DateTimePicker::make('timestamp'),
|
||||
KeyValue::make('properties')
|
||||
->label('Metadata')
|
||||
->formatStateUsing(fn ($state) => Arr::dot($state)),
|
||||
]),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('event')
|
||||
->options(fn (Table $table) => $table->getQuery()->pluck('event', 'event')->unique()->sort())
|
||||
->searchable()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): Builder
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return ActivityLog::whereHas('subjects', fn (Builder $query) => $query->where('subject_id', $server->id))
|
||||
return ActivityLog::whereHas('subjects', fn (Builder $query) => $query->where('subject_id', $server->id)->where('subject_type', $server->getMorphClass()))
|
||||
->whereNotIn('activity_logs.event', ActivityLog::DISABLED_EVENTS)
|
||||
->when(config('activity.hide_admin_activity'), function (Builder $builder) use ($server) {
|
||||
// We could do this with a query and a lot of joins, but that gets pretty
|
||||
@ -58,7 +163,8 @@ class ActivityResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListActivities::route('/'),
|
||||
|
@ -2,114 +2,18 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\ActivityResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource\Pages\EditUser;
|
||||
use App\Filament\Server\Resources\ActivityResource;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ListActivities extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ActivityResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return $table
|
||||
->paginated([25, 50])
|
||||
->defaultPaginationPageOption(25)
|
||||
->columns([
|
||||
TextColumn::make('event')
|
||||
->html()
|
||||
->description(fn ($state) => $state)
|
||||
->icon(fn (ActivityLog $activityLog) => $activityLog->getIcon())
|
||||
->formatStateUsing(fn (ActivityLog $activityLog) => $activityLog->getLabel()),
|
||||
TextColumn::make('user')
|
||||
->state(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
|
||||
// Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin
|
||||
if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) {
|
||||
$user .= " ({$activityLog->actor->email})";
|
||||
}
|
||||
|
||||
return $user;
|
||||
})
|
||||
->tooltip(fn (ActivityLog $activityLog) => auth()->user()->can('seeIps activityLog') ? $activityLog->ip : '')
|
||||
->url(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user') ? EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin') : '')
|
||||
->grow(false),
|
||||
DateTimeColumn::make('timestamp')
|
||||
->since()
|
||||
->sortable()
|
||||
->grow(false),
|
||||
])
|
||||
->defaultSort('timestamp', 'desc')
|
||||
->actions([
|
||||
ViewAction::make()
|
||||
//->visible(fn (ActivityLog $activityLog) => $activityLog->hasAdditionalMetadata())
|
||||
->schema([
|
||||
TextEntry::make('event')
|
||||
->state(fn (ActivityLog $activityLog) => new HtmlString($activityLog->getLabel())),
|
||||
TextInput::make('user')
|
||||
->formatStateUsing(function (ActivityLog $activityLog) use ($server) {
|
||||
if (!$activityLog->actor instanceof User) {
|
||||
return $activityLog->actor_id === null ? 'System' : 'Deleted user';
|
||||
}
|
||||
|
||||
$user = $activityLog->actor->username;
|
||||
|
||||
// Only show the email if the actor is the server owner/ a subuser or if the viewing user is an admin
|
||||
if (auth()->user()->isAdmin() || $server->owner_id === $activityLog->actor->id || $server->subusers->where('user_id', $activityLog->actor->id)->first()) {
|
||||
$user .= " ({$activityLog->actor->email})";
|
||||
}
|
||||
|
||||
if (auth()->user()->can('seeIps activityLog')) {
|
||||
$user .= " - $activityLog->ip";
|
||||
}
|
||||
|
||||
return $user;
|
||||
})
|
||||
->hintAction(
|
||||
Action::make('edit')
|
||||
->label(trans('filament-actions::edit.single.label'))
|
||||
->icon('tabler-edit')
|
||||
->visible(fn (ActivityLog $activityLog) => $activityLog->actor instanceof User && auth()->user()->can('update user'))
|
||||
->url(fn (ActivityLog $activityLog) => EditUser::getUrl(['record' => $activityLog->actor], panel: 'admin'))
|
||||
),
|
||||
DateTimePicker::make('timestamp'),
|
||||
KeyValue::make('properties')
|
||||
->label('Metadata')
|
||||
->formatStateUsing(fn ($state) => Arr::dot($state)),
|
||||
]),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('event')
|
||||
->options(fn (Table $table) => $table->getQuery()->pluck('event', 'event')->unique()->sort())
|
||||
->searchable()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getBreadcrumbs(): array
|
||||
{
|
||||
return [];
|
||||
|
@ -2,16 +2,32 @@
|
||||
|
||||
namespace App\Filament\Server\Resources;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\AllocationResource\Pages;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DetachAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AllocationResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = Allocation::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Network';
|
||||
@ -22,17 +38,59 @@ class AllocationResource extends Resource
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-network';
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('ip')
|
||||
->label('Address')
|
||||
->formatStateUsing(fn (Allocation $allocation) => $allocation->alias),
|
||||
TextColumn::make('alias')
|
||||
->hidden(),
|
||||
TextColumn::make('port'),
|
||||
TextInputColumn::make('notes')
|
||||
->visibleFrom('sm')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server))
|
||||
->label('Notes')
|
||||
->placeholder('No Notes'),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
default => 'tabler-star',
|
||||
})
|
||||
->color(fn ($state) => match ($state) {
|
||||
true => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->action(function (Allocation $allocation) use ($server) {
|
||||
if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) {
|
||||
return $server->update(['allocation_id' => $allocation->id]);
|
||||
}
|
||||
})
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->label('Primary'),
|
||||
])
|
||||
->actions([
|
||||
DetachAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server))
|
||||
->label('Delete')
|
||||
->icon('tabler-trash')
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->action(function (Allocation $allocation) {
|
||||
Allocation::query()->where('id', $allocation->id)->update([
|
||||
'notes' => null,
|
||||
'server_id' => null,
|
||||
]);
|
||||
|
||||
return parent::canAccess();
|
||||
Activity::event('server:allocation.delete')
|
||||
->subject($allocation)
|
||||
->property('allocation', $allocation->address)
|
||||
->log();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
@ -55,7 +113,8 @@ class AllocationResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListAllocations::route('/'),
|
||||
|
@ -4,85 +4,31 @@ namespace App\Filament\Server\Resources\AllocationResource\Pages;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\AllocationResource;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Allocations\FindAssignableAllocationService;
|
||||
use Filament\Actions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\DetachAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListAllocations extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = AllocationResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('ip')
|
||||
->label('Address')
|
||||
->formatStateUsing(fn (Allocation $allocation) => $allocation->alias),
|
||||
TextColumn::make('alias')
|
||||
->hidden(),
|
||||
TextColumn::make('port'),
|
||||
TextInputColumn::make('notes')
|
||||
->visibleFrom('sm')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server))
|
||||
->label('Notes')
|
||||
->placeholder('No Notes'),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
default => 'tabler-star',
|
||||
})
|
||||
->color(fn ($state) => match ($state) {
|
||||
true => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->action(function (Allocation $allocation) use ($server) {
|
||||
if (auth()->user()->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server)) {
|
||||
return $server->update(['allocation_id' => $allocation->id]);
|
||||
}
|
||||
})
|
||||
->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->label('Primary'),
|
||||
])
|
||||
->actions([
|
||||
DetachAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_DELETE, $server))
|
||||
->label('Delete')
|
||||
->icon('tabler-trash')
|
||||
->hidden(fn (Allocation $allocation) => $allocation->id === $server->allocation_id)
|
||||
->action(function (Allocation $allocation) {
|
||||
Allocation::query()->where('id', $allocation->id)->update([
|
||||
'notes' => null,
|
||||
'server_id' => null,
|
||||
]);
|
||||
|
||||
Activity::event('server:allocation.delete')
|
||||
->subject($allocation)
|
||||
->property('allocation', $allocation->toString())
|
||||
->log();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return [
|
||||
Actions\Action::make('addAllocation')
|
||||
Action::make('addAllocation')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_ALLOCATION_CREATE, $server))
|
||||
->label(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'Allocation limit reached' : 'Add Allocation')
|
||||
->hidden(fn () => !config('panel.client_features.allocations.enabled'))
|
||||
@ -93,7 +39,7 @@ class ListAllocations extends ListRecords
|
||||
|
||||
Activity::event('server:allocation.create')
|
||||
->subject($allocation)
|
||||
->property('allocation', $allocation->toString())
|
||||
->property('allocation', $allocation->address)
|
||||
->log();
|
||||
}),
|
||||
];
|
||||
|
@ -2,16 +2,54 @@
|
||||
|
||||
namespace App\Filament\Server\Resources;
|
||||
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Enums\ServerState;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\BackupResource\Pages;
|
||||
use App\Http\Controllers\Api\Client\Servers\BackupController;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Backups\DownloadLinkService;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Services\Backups\DeleteBackupService;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use App\Traits\Filament\HasLimitBadge;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\ActionGroup;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BackupResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
use HasLimitBadge;
|
||||
|
||||
protected static ?string $model = Backup::class;
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
@ -20,45 +58,151 @@ class BackupResource extends Resource
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
public const WARNING_THRESHOLD = 0.7;
|
||||
|
||||
public static function getNavigationBadge(): string
|
||||
protected static function getBadgeCount(): int
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$limit = $server->backup_limit;
|
||||
|
||||
return $server->backups->count() . ($limit === 0 ? '' : ' / ' . $limit);
|
||||
return $server->backups->count();
|
||||
}
|
||||
|
||||
public static function getNavigationBadgeColor(): ?string
|
||||
protected static function getBadgeLimit(): int
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$limit = $server->backup_limit;
|
||||
$count = $server->backups->count();
|
||||
|
||||
if ($limit === 0) {
|
||||
return null;
|
||||
return $server->backup_limit;
|
||||
}
|
||||
|
||||
return $count >= $limit ? 'danger'
|
||||
: ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success');
|
||||
public static function defaultForm(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->columnSpanFull(),
|
||||
TextArea::make('ignored')
|
||||
->columnSpanFull()
|
||||
->label('Ignored Files & Directories'),
|
||||
Toggle::make('is_locked')
|
||||
->label('Lock?')
|
||||
->helperText('Prevents this backup from being deleted until explicitly unlocked.'),
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
BytesColumn::make('bytes')
|
||||
->label('Size'),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label('Created')
|
||||
->since()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge(),
|
||||
IconColumn::make('is_locked')
|
||||
->visibleFrom('md')
|
||||
->label('Lock Status')
|
||||
->trueIcon('tabler-lock')
|
||||
->falseIcon('tabler-lock-open'),
|
||||
])
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
Action::make('lock')
|
||||
->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server))
|
||||
->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock')
|
||||
->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup))
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('download')
|
||||
->color('primary')
|
||||
->icon('tabler-download')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server))
|
||||
->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true)
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('restore')
|
||||
->color('success')
|
||||
->icon('tabler-folder-up')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_RESTORE, $server))
|
||||
->form([
|
||||
Placeholder::make('')
|
||||
->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'),
|
||||
Checkbox::make('truncate')
|
||||
->label('Delete all files before restoring backup?'),
|
||||
])
|
||||
->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) {
|
||||
if (!is_null($server->status)) {
|
||||
return Notification::make()
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This server is not currently in a state that allows for a backup to be restored.')
|
||||
->send();
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
if (!$backup->is_successful && is_null($backup->completed_at)) {
|
||||
return Notification::make()
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This backup cannot be restored at this time: not completed or failed.')
|
||||
->send();
|
||||
}
|
||||
|
||||
$log = Activity::event('server:backup.restore')
|
||||
->subject($backup)
|
||||
->property(['name' => $backup->name, 'truncate' => $data['truncate']]);
|
||||
|
||||
$log->transaction(function () use ($downloadLinkService, $daemonRepository, $backup, $server, $data) {
|
||||
// If the backup is for an S3 file we need to generate a unique Download link for
|
||||
// it that will allow daemon to actually access the file.
|
||||
if ($backup->disk === Backup::ADAPTER_AWS_S3) {
|
||||
$url = $downloadLinkService->handle($backup, auth()->user());
|
||||
}
|
||||
|
||||
// Update the status right away for the server so that we know not to allow certain
|
||||
// actions against it via the Panel API.
|
||||
$server->update(['status' => ServerState::RestoringBackup]);
|
||||
|
||||
$daemonRepository->setServer($server)->restore($backup, $url ?? null, $data['truncate']);
|
||||
});
|
||||
|
||||
return Notification::make()
|
||||
->title('Restoring Backup')
|
||||
->send();
|
||||
})
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
DeleteAction::make('delete')
|
||||
->disabled(fn (Backup $backup) => $backup->is_locked)
|
||||
->modalDescription(fn (Backup $backup) => 'Do you wish to delete ' . $backup->name . '?')
|
||||
->modalSubmitActionLabel('Delete Backup')
|
||||
->action(function (Backup $backup, DeleteBackupService $deleteBackupService) {
|
||||
try {
|
||||
$deleteBackupService->handle($backup);
|
||||
} catch (ConnectionException) {
|
||||
Notification::make()
|
||||
->title('Could not delete backup')
|
||||
->body('Connection to node failed')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Activity::event('server:backup.delete')
|
||||
->subject($backup)
|
||||
->property(['name' => $backup->name, 'failed' => !$backup->is_successful])
|
||||
->log();
|
||||
})
|
||||
->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
@ -76,7 +220,8 @@ class BackupResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_BACKUP_DELETE, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListBackups::route('/'),
|
||||
|
@ -2,166 +2,37 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\BackupResource\Pages;
|
||||
|
||||
use App\Enums\BackupStatus;
|
||||
use App\Enums\ServerState;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\BackupResource;
|
||||
use App\Http\Controllers\Api\Client\Servers\BackupController;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Backups\DownloadLinkService;
|
||||
use App\Services\Backups\InitiateBackupService;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Http\Request;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ListBackups extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = BackupResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->columnSpanFull(),
|
||||
TextArea::make('ignored')
|
||||
->columnSpanFull()
|
||||
->label('Ignored Files & Directories'),
|
||||
Toggle::make('is_locked')
|
||||
->label('Lock?')
|
||||
->helperText('Prevents this backup from being deleted until explicitly unlocked.'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
BytesColumn::make('bytes')
|
||||
->label('Size'),
|
||||
DateTimeColumn::make('created_at')
|
||||
->label('Created')
|
||||
->since()
|
||||
->sortable(),
|
||||
TextColumn::make('status')
|
||||
->label('Status')
|
||||
->badge(),
|
||||
IconColumn::make('is_locked')
|
||||
->visibleFrom('md')
|
||||
->label('Lock Status')
|
||||
->trueIcon('tabler-lock')
|
||||
->falseIcon('tabler-lock-open'),
|
||||
])
|
||||
->actions([
|
||||
ActionGroup::make([
|
||||
Action::make('lock')
|
||||
->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock' : 'tabler-lock-open')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DELETE, $server))
|
||||
->label(fn (Backup $backup) => !$backup->is_locked ? 'Lock' : 'Unlock')
|
||||
->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->toggleLock($request, $server, $backup))
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('download')
|
||||
->color('primary')
|
||||
->icon('tabler-download')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server))
|
||||
->url(fn (DownloadLinkService $downloadLinkService, Backup $backup, Request $request) => $downloadLinkService->handle($backup, $request->user()), true)
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
Action::make('restore')
|
||||
->color('success')
|
||||
->icon('tabler-folder-up')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_RESTORE, $server))
|
||||
->schema([
|
||||
TextEntry::make('INeedAName')
|
||||
->helperText('Your server will be stopped. You will not be able to control the power state, access the file manager, or create additional backups until this process is completed.'),
|
||||
Checkbox::make('truncate')
|
||||
->label('Delete all files before restoring backup?'),
|
||||
])
|
||||
->action(function (Backup $backup, $data, DaemonBackupRepository $daemonRepository, DownloadLinkService $downloadLinkService) use ($server) {
|
||||
if (!is_null($server->status)) {
|
||||
return Notification::make()
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This server is not currently in a state that allows for a backup to be restored.')
|
||||
->send();
|
||||
}
|
||||
|
||||
if (!$backup->is_successful && is_null($backup->completed_at)) { //TODO Change to Notifications
|
||||
return Notification::make()
|
||||
->danger()
|
||||
->title('Backup Restore Failed')
|
||||
->body('This backup cannot be restored at this time: not completed or failed.')
|
||||
->send();
|
||||
}
|
||||
|
||||
$log = Activity::event('server:backup.restore')
|
||||
->subject($backup)
|
||||
->property(['name' => $backup->name, 'truncate' => $data['truncate']]);
|
||||
|
||||
$log->transaction(function () use ($downloadLinkService, $daemonRepository, $backup, $server, $data) {
|
||||
// If the backup is for an S3 file we need to generate a unique Download link for
|
||||
// it that will allow daemon to actually access the file.
|
||||
if ($backup->disk === Backup::ADAPTER_AWS_S3) {
|
||||
$url = $downloadLinkService->handle($backup, auth()->user());
|
||||
}
|
||||
|
||||
// Update the status right away for the server so that we know not to allow certain
|
||||
// actions against it via the Panel API.
|
||||
$server->update(['status' => ServerState::RestoringBackup]);
|
||||
|
||||
$daemonRepository->setServer($server)->restore($backup, $url ?? null, $data['truncate']);
|
||||
});
|
||||
|
||||
return Notification::make()
|
||||
->title('Restoring Backup')
|
||||
->send();
|
||||
})
|
||||
->visible(fn (Backup $backup) => $backup->status === BackupStatus::Successful),
|
||||
DeleteAction::make('delete')
|
||||
->disabled(fn (Backup $backup) => $backup->is_locked)
|
||||
->modalDescription(fn (Backup $backup) => 'Do you wish to delete, ' . $backup->name . '?')
|
||||
->modalSubmitActionLabel('Delete Backup')
|
||||
->action(fn (BackupController $backupController, Backup $backup, Request $request) => $backupController->delete($request, $server, $backup))
|
||||
->visible(fn (Backup $backup) => $backup->status !== BackupStatus::InProgress),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
CreateAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_BACKUP_CREATE, $server))
|
||||
->label(fn () => $server->backups()->count() >= $server->backup_limit ? 'Backup limit reached' : 'Create Backup')
|
||||
->disabled(fn () => $server->backups()->count() >= $server->backup_limit)
|
||||
|
@ -2,62 +2,118 @@
|
||||
|
||||
namespace App\Filament\Server\Resources;
|
||||
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\DatabaseResource\Pages;
|
||||
use App\Models\Database;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use App\Traits\Filament\HasLimitBadge;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
|
||||
|
||||
class DatabaseResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
use HasLimitBadge;
|
||||
|
||||
protected static ?string $model = Database::class;
|
||||
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
|
||||
|
||||
public const WARNING_THRESHOLD = 0.7;
|
||||
|
||||
public static function getNavigationBadge(): string
|
||||
protected static function getBadgeCount(): int
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$limit = $server->database_limit;
|
||||
|
||||
return $server->databases->count() . ($limit === 0 ? '' : ' / ' . $limit);
|
||||
return $server->databases->count();
|
||||
}
|
||||
|
||||
public static function getNavigationBadgeColor(): ?string
|
||||
protected static function getBadgeLimit(): int
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
$limit = $server->database_limit;
|
||||
$count = $server->databases->count();
|
||||
|
||||
if ($limit === 0) {
|
||||
return null;
|
||||
return $server->database_limit;
|
||||
}
|
||||
|
||||
return $count >= $limit
|
||||
? 'danger'
|
||||
: ($count >= $limit * self::WARNING_THRESHOLD ? 'warning' : 'success');
|
||||
}
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
public static function defaultForm(Form $form): Form
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('host')
|
||||
->formatStateUsing(fn (Database $database) => $database->address())
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('database')
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('username')
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('password')
|
||||
->password()->revealable()
|
||||
->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->hintAction(
|
||||
RotateDatabasePasswordAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server))
|
||||
)
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
TextInput::make('remote')
|
||||
->label('Connections From'),
|
||||
TextInput::make('max_connections')
|
||||
->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'),
|
||||
TextInput::make('jdbc')
|
||||
->label('JDBC Connection String')
|
||||
->password()->revealable()
|
||||
->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn (Database $database) => $database->jdbc),
|
||||
]);
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('host')
|
||||
->state(fn (Database $database) => $database->address())
|
||||
->badge(),
|
||||
TextColumn::make('database'),
|
||||
TextColumn::make('username'),
|
||||
TextColumn::make('remote'),
|
||||
DateTimeColumn::make('created_at')
|
||||
->sortable(),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make()
|
||||
->modalHeading(fn (Database $database) => 'Viewing ' . $database->database),
|
||||
DeleteAction::make()
|
||||
->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
@ -85,7 +141,8 @@ class DatabaseResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_DATABASE_DELETE, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDatabases::route('/'),
|
||||
|
@ -2,90 +2,30 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\DatabaseResource;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Form;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ListDatabases extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = DatabaseResource::class;
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return $schema
|
||||
->schema([
|
||||
TextInput::make('host')
|
||||
->formatStateUsing(fn (Database $database) => $database->address()),
|
||||
//TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('database'),
|
||||
//TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('username'),
|
||||
//TODO->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
|
||||
TextInput::make('password')
|
||||
->password()->revealable()
|
||||
->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->hintAction(
|
||||
RotateDatabasePasswordAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server))
|
||||
)
|
||||
//TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
TextInput::make('remote')
|
||||
->label('Connections From'),
|
||||
TextInput::make('max_connections')
|
||||
->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'),
|
||||
TextInput::make('jdbc')
|
||||
->label('JDBC Connection String')
|
||||
->password()->revealable()
|
||||
->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
//TODO ->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn (Database $database) => $database->jdbc),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('host')
|
||||
->state(fn (Database $database) => $database->address())
|
||||
->badge(),
|
||||
TextColumn::make('database'),
|
||||
TextColumn::make('username'),
|
||||
TextColumn::make('remote'),
|
||||
DateTimeColumn::make('created_at')
|
||||
->sortable(),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make()
|
||||
->modalHeading(fn (Database $database) => 'Viewing ' . $database->database),
|
||||
DeleteAction::make()
|
||||
->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
@ -5,13 +5,20 @@ namespace App\Filament\Server\Resources;
|
||||
use App\Filament\Server\Resources\FileResource\Pages;
|
||||
use App\Models\File;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FileResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
|
||||
protected static ?string $model = File::class;
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
@ -20,19 +27,6 @@ class FileResource extends Resource
|
||||
|
||||
protected static bool $isScopedToTenant = false;
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
return auth()->user()->can(Permission::ACTION_FILE_READ, Filament::getTenant());
|
||||
@ -53,7 +47,8 @@ class FileResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_FILE_DELETE, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'edit' => Pages\EditFiles::route('/edit/{path}'),
|
||||
|
@ -11,6 +11,8 @@ use App\Livewire\AlertBanner;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonFileRepository;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
@ -26,6 +28,7 @@ use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
@ -36,6 +39,8 @@ use Livewire\Attributes\Locked;
|
||||
*/
|
||||
class EditFiles extends Page
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
use InteractsWithFormActions;
|
||||
use InteractsWithForms;
|
||||
|
||||
@ -178,6 +183,15 @@ class EditFiles extends Page
|
||||
->info()
|
||||
->closable()
|
||||
->send();
|
||||
|
||||
try {
|
||||
$this->getDaemonFileRepository()->getDirectory('/');
|
||||
} catch (ConnectionException) {
|
||||
AlertBanner::make('node_connection_error')
|
||||
->title('Could not connect to the node!')
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,6 +246,14 @@ class EditFiles extends Page
|
||||
return $this->fileRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parameters
|
||||
*/
|
||||
public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string
|
||||
{
|
||||
return parent::getUrl($parameters, $isAbsolute, $panel, $tenant) . '/';
|
||||
}
|
||||
|
||||
public static function route(string $path): PageRegistration
|
||||
{
|
||||
return new PageRegistration(
|
||||
|
@ -40,20 +40,16 @@ use Livewire\Attributes\Locked;
|
||||
|
||||
class ListFiles extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
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();
|
||||
@ -321,9 +317,9 @@ class ListFiles extends ListRecords
|
||||
->label('')
|
||||
->icon('tabler-trash')
|
||||
->requiresConfirmation()
|
||||
->modalDescription(fn (File $file) => $file->name)
|
||||
->modalHeading('Delete file?')
|
||||
->modalHeading(fn (File $file) => trans('filament-actions::delete.single.modal.heading', ['label' => $file->name . ' ' . ($file->is_directory ? 'folder' : 'file')]))
|
||||
->action(function (File $file) {
|
||||
$this->deselectAllTableRecords();
|
||||
$this->getDaemonFileRepository()->deleteFiles($this->path, [$file->name]);
|
||||
|
||||
Activity::event('server:file.delete')
|
||||
@ -406,10 +402,8 @@ class ListFiles extends ListRecords
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<HeaderAction|HeaderActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
@ -507,8 +501,9 @@ class ListFiles extends ListRecords
|
||||
->schema([
|
||||
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'],
|
||||
|
@ -7,14 +7,20 @@ use App\Models\File;
|
||||
use App\Models\Server;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Facades\Filament;
|
||||
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
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = FileResource::class;
|
||||
|
||||
protected static ?string $title = 'Global Search';
|
||||
@ -22,15 +28,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
|
||||
{
|
||||
|
@ -2,12 +2,18 @@
|
||||
|
||||
namespace App\Filament\Server\Resources;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\ScheduleResource\Pages;
|
||||
use App\Filament\Server\Resources\ScheduleResource\RelationManagers\TasksRelationManager;
|
||||
use App\Helpers\Utilities;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Schedule;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
@ -21,33 +27,34 @@ use Filament\Schemas\Components\Form;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Enums\Operation;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ScheduleResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
|
||||
protected static ?string $model = Schedule::class;
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-clock';
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
return auth()->user()->can(Permission::ACTION_SCHEDULE_READ, Filament::getTenant());
|
||||
@ -310,14 +317,54 @@ class ScheduleResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('cron')
|
||||
->state(fn (Schedule $schedule) => $schedule->cron_minute . ' ' . $schedule->cron_hour . ' ' . $schedule->cron_day_of_month . ' ' . $schedule->cron_month . ' ' . $schedule->cron_day_of_week),
|
||||
TextColumn::make('status')
|
||||
->state(fn (Schedule $schedule) => !$schedule->is_active ? 'Inactive' : ($schedule->is_processing ? 'Processing' : 'Active')),
|
||||
IconColumn::make('only_when_online')
|
||||
->boolean()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('last_run_at')
|
||||
->label('Last run')
|
||||
->placeholder('Never')
|
||||
->since()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('next_run_at')
|
||||
->label('Next run')
|
||||
->placeholder('Never')
|
||||
->since()
|
||||
->sortable()
|
||||
->state(fn (Schedule $schedule) => $schedule->is_active ? $schedule->next_run_at : null),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->after(function (Schedule $schedule) {
|
||||
Activity::event('server:schedule.delete')
|
||||
->subject($schedule)
|
||||
->property('name', $schedule->name)
|
||||
->log();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return class-string<RelationManager>[] */
|
||||
public static function getDefaultRelations(): array
|
||||
{
|
||||
return [
|
||||
TasksRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListSchedules::route('/'),
|
||||
|
@ -6,11 +6,16 @@ use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Schedule;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateSchedule extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ScheduleResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
@ -6,10 +6,15 @@ use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Schedule;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditSchedule extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ScheduleResource::class;
|
||||
|
||||
protected function afterSave(): void
|
||||
@ -35,7 +40,8 @@ class EditSchedule extends EditRecord
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
|
@ -2,65 +2,27 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\ScheduleResource\Pages;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Schedule;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListSchedules extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ScheduleResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('cron')
|
||||
->state(fn (Schedule $schedule) => $schedule->cron_minute . ' ' . $schedule->cron_hour . ' ' . $schedule->cron_day_of_month . ' ' . $schedule->cron_month . ' ' . $schedule->cron_day_of_week),
|
||||
TextColumn::make('status')
|
||||
->state(fn (Schedule $schedule) => !$schedule->is_active ? 'Inactive' : ($schedule->is_processing ? 'Processing' : 'Active')),
|
||||
IconColumn::make('only_when_online')
|
||||
->boolean()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('last_run_at')
|
||||
->label('Last run')
|
||||
->placeholder('Never')
|
||||
->since()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('next_run_at')
|
||||
->label('Next run')
|
||||
->placeholder('Never')
|
||||
->since()
|
||||
->sortable()
|
||||
->state(fn (Schedule $schedule) => $schedule->is_active ? $schedule->next_run_at : null),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make()
|
||||
->after(function (Schedule $schedule) {
|
||||
Activity::event('server:schedule.delete')
|
||||
->subject($schedule)
|
||||
->property('name', $schedule->name)
|
||||
->log();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()->label('New Schedule'),
|
||||
CreateAction::make()
|
||||
->label('New Schedule'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Schedule;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Facades\Filament;
|
||||
@ -14,9 +16,13 @@ use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewSchedule extends ViewRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = ScheduleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('runNow')
|
||||
|
@ -8,6 +8,11 @@ use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Services\Subusers\SubuserDeletionService;
|
||||
use App\Services\Subusers\SubuserUpdateService;
|
||||
use App\Traits\Filament\BlockAccessInConflict;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use App\Traits\Filament\HasLimitBadge;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
@ -20,6 +25,7 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
@ -29,6 +35,12 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
use BlockAccessInConflict;
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyTable;
|
||||
use HasLimitBadge;
|
||||
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
@ -37,25 +49,12 @@ class UserResource extends Resource
|
||||
|
||||
protected static ?string $tenantOwnershipRelationshipName = 'subServers';
|
||||
|
||||
public static function getNavigationBadge(): string
|
||||
protected static function getBadgeCount(): int
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return (string) $server->subusers->count();
|
||||
}
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
return $server->subusers->count();
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
@ -78,11 +77,40 @@ class UserResource extends Resource
|
||||
return auth()->user()->can(Permission::ACTION_USER_DELETE, Filament::getTenant());
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
/** @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 +186,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,15 +202,10 @@ 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) {
|
||||
->schema($tabs),
|
||||
]),
|
||||
])
|
||||
->mutateRecordDataUsing(function ($data, User $user) use ($server) {
|
||||
$permissionsArray = $server->subusers->where('user_id', $user->id)->first()->permissions;
|
||||
|
||||
$transformedPermissions = [];
|
||||
@ -254,249 +216,16 @@ class UserResource extends Resource
|
||||
}
|
||||
|
||||
foreach ($transformedPermissions as $key => $value) {
|
||||
$set($key, $value);
|
||||
$data[$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'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
return $data;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
|
@ -7,6 +7,8 @@ use App\Filament\Server\Resources\UserResource;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Subusers\SubuserCreationService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Facades\Filament;
|
||||
@ -25,13 +27,46 @@ use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
/** @return array<Actions\Action|Actions\ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
/** @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 [
|
||||
CreateAction::make('invite')
|
||||
->label('Invite User')
|
||||
@ -60,72 +95,10 @@ class ListUsers extends ListRecords
|
||||
Actions::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 +111,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 ?string $heading = 'Network';
|
||||
protected static ?string $pollingInterval = '1s';
|
||||
|
||||
protected ?string $pollingInterval = '1s';
|
||||
|
||||
protected ?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
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ use App\Enums\ContainerStatus;
|
||||
use App\Filament\Server\Components\SmallStatBlock;
|
||||
use App\Models\Server;
|
||||
use Carbon\CarbonInterface;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Widgets\StatsOverviewWidget;
|
||||
use Illuminate\Support\Number;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class ServerOverview extends StatsOverviewWidget
|
||||
{
|
||||
@ -19,14 +21,10 @@ class ServerOverview extends StatsOverviewWidget
|
||||
{
|
||||
return [
|
||||
SmallStatBlock::make('Name', $this->server->name)
|
||||
->extraAttributes([
|
||||
'class' => 'overflow-x-auto',
|
||||
]),
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('Status', $this->status()),
|
||||
SmallStatBlock::make('Address', $this->server->allocation->address)
|
||||
->extraAttributes([
|
||||
'class' => 'overflow-x-auto',
|
||||
]),
|
||||
->copyOnClick(fn () => request()->isSecure()),
|
||||
SmallStatBlock::make('CPU', $this->cpuUsage()),
|
||||
SmallStatBlock::make('Memory', $this->memoryUsage()),
|
||||
SmallStatBlock::make('Disk', $this->diskUsage()),
|
||||
@ -93,4 +91,16 @@ class ServerOverview extends StatsOverviewWidget
|
||||
|
||||
return $used . ($this->server->disk > 0 ? ' / ' . $total : ' / ∞');
|
||||
}
|
||||
|
||||
#[On('copyClick')]
|
||||
public function copyClick(string $value): void
|
||||
{
|
||||
$this->js("window.navigator.clipboard.writeText('{$value}');");
|
||||
|
||||
Notification::make()
|
||||
->title('Copied to clipboard')
|
||||
->body($value)
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user